9 篇博文 含有标签「Xcode」
查看所有标签利用 Debug Memory Graph 检测内测泄漏
前言
平常我们都会用 Instrument 的 Leaks / Allocations 或其他一些开源库进行内存泄露的排查,但它们都存在各种问题和不便,
在这个 ARC 时代更常见的内存泄露是循环引用导致的 Abandoned memory,Leaks 工具查不出这类内存泄露,应用有限。
今天介绍一种简单直接的检测内测泄漏的方法:Debug Memory Graph
就是这货:
正文
我最近的项目中,退出登录后(跳转到登录页),发现首页控制器没有被销毁,依旧能接收通知。
退出登录代码:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Login" bundle:[NSBundle mainBundle]];
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
appDelegate.window.rootViewController = [storyboard instantiateViewControllerWithIdentifier:@"LoginVC"];
很明显发生了循环引用导致的内测泄漏。
接下来就使用 Debug Memory Graph 来查看内测泄漏了。
运行程序
首先启动 Xcode 运行程序。
Debug Memory Graph
点击 Debug Memory Graph 按钮后,可以看到红框内的是当前内存中存在的对象。其中,绿色的就是视图控制器。
这样,我们随时都可以查看内测中存在的对象,换句话说,就是可以通过观察 Memory Graph 查看内测泄漏。
调试你的App
继续运行你的程序
然后对App进行调试、push、pop 操作,再次点击 Debug Memory Graph 按钮。那些该释放而依旧在内测中的 控制器
或 对象
就能一一找出来了。
接下来,只要进入对应的控制器找到内测泄漏的代码就OK了,一般是Block里引用了 self
,改为 weakSelf
就解决了。
#define WS(weakSelf) __weak __typeof(&*self)weakSelf = self;
WS(weakSelf)
sView.btnBlock = ^(NSInteger idx){
[weakSelf.tableView reloadSections:[NSIndexSet indexSetWithIndex:idx] withRowAnimation:UITableViewRowAnimationAutomatic];
};
结语
就这样,利用 Debug Memory Graph,可以简单快速的检测内测泄漏。
一般由两个对象循环引用的内测泄漏是比较好发现的,如果是由三个及其三个以上的对象形成的大的循环引用,就会比较难排查了。
iTunes Connect 构建版本不显示
前言
今天新项目上架,在Xcode打包上传到App Store后,在iTunes Connect构建版本中居然找不到上传的App...
解决
从iOS10开始,苹果更加注重对用于隐私的保护,App 里边如果需要访问用户隐私,必须要做描述,所以要在 plist 文件中添加描述。
而这三个基础描述是必须添加的:
-
麦克风权限:
Privacy - Microphone Usage Description
是否允许此App使用你的麦克风? -
相机权限:
Privacy - Camera Usage Description
是否允许此App使用你的相机? -
相册权限:
Privacy - Photo Library Usage Description
是否允许此App访问你的媒体资料库?
其他的权限可以根据自己 APP 的情况来添加。
添加完权限之后然后继续提交 App 就可以了。
若还是找不到,返回 plist 文件中,删除之前的权限,重新添加一下,有可能你哪不小心添加的权限末尾有空格,或者字段不对。
End
Xcode9 无线调试功能
iOS自动打包
利用xcode的命令行工具
xcdeobulid
进行项目的编译打包,生成ipa包,并上传到fir
现在网上的自动打包教程几乎都还是xcodebuild + xcrun
的方式先生成.app
包 再生成.ipa
包,结果弄了一整天硬是没成功~
后来发现PackageApplication is deprecated
,悲剧。然后手动压缩的 .ipa
包因为签名问题无法装到手机上。
后来用了archive + -exportArchive
终于可以了~
正文
Xcodebuild
xcodebuild 的使用可以用 man xcodebuild
查看。
查看项目详情
# cd 项目主目录
xcodebuild -list
输出项目的信息
Information about project "StackGameSceneKit":
Targets:
StackGameSceneKit
StackGameSceneKitTests
Build Configurations:
Debug
Release
If no build configuration is specified and -scheme is not passed then "Release" is used.
Schemes:
StackGameSceneKit
要留意 Configurations
,Schemes
这两个属性。
自动打包流程
生成 archive
生成archive的命令是 xcodebuild archive
xcodebuild archive -workspace ${project_name}.xcworkspace \
-scheme ${scheme_name} \
-configuration ${build_configuration} \
-archivePath ${export_archive_path}
-
参数一:项目类型,,如果是混合项目 workspace 就用
-workspace
,如果是 project 就用-project
-
-scheme
:项目名,上面xcodebuild -list
中的Schemes
-
-configuration
:编译类型,在configuration
选择,Debug
或者Release
-
-archivePath
:生成 archive 包的路径,需要精确到xx/xx.archive
首先需要创建一个AdHocExportOptions.plist
文件
导出ipa包
导出.ipa
包经常会出现错误,在ruby2.4.0版本中会报错,所以请使用其他版本的ruby,最初的原因是使用了 ruby2.4.0 进行编译时出现的错误。
解决方法是低版本的 ruby 进行编译,如使用系统版本:rvm use system
。后面升级macOS系统(10.12.5)后发现 ruby2.4.0 能成功 导出ipa包了。
导出ipa包使用命令:xcodebuild -exportArchive
xcodebuild -exportArchive \
-archivePath ${export_archive_path} \
-exportPath ${export_ipa_path} \
-exportOptionsPlist ${ExportOptionsPlistPath}
archivePath
:上面生成 archive 的路径-exportPath
:导出 ipa包 的路径exportOptionsPlist
:导出 ipa包 类型,需要指定格式的plist
文件,分别是AppStroe
、AdHoc
、Enterprise
,如下图
选择这三个类别需要分别创建三个plist
文件:
-
AdHocExportOptionsPlist.plist
-
AppStoreExportOptionsPlist.plist
-
EnterpriseExportOptionsPlist.plist
上传到 Fir
将项目上传到 Fir
下载 fir 命令行工具
gem install fir-cli
获取 fir 的 API Token(右上角)
上传
fir publish "ipa_Path" -T "firApiToken"
自动打包脚本
再次提醒,请不要使用 ruby 2.4.0 运行该脚本!,若在 ruby 2.4.0 下编译失败,请切换低版本的ruby。
切换完毕记得重新安装 fir 命令行工具。
脚本我fork了 jkpang 的脚本进行修改,添加了自动上传到 fir 的功能。
使用方法在Github上有详细介绍。
GitHub:<https://github.com/qiubaiying/iOSAutoArchiveScript>
利用 自定义终端指令 简化打包过程
以zsh为例:
open ~/.zshrc
添加自定义命令 cd + sh
alias mybuild='cd 项目地址/iOSAutoArchiveScript/ && sh 项目地址/iOSAutoArchiveScript/iOSAutoArchiveScript.sh'
这样打开终端输入mybuild
,就可以轻松实现一键打包上传了
CocoaPods 安装和使用
最近换了新机器,重新搭建了开发环境,其中当然包括 CocoaPods。
装完顺便更新下 CocoaPods 安装文档。
正文
安装
CocoaPods 是用 ruby 实现的,要想使用它首先需要有 ruby 的环境。
升级ruby
查看ruby版本 $ ruby -v
ruby 2.0.0p648 (2015-12-16 revision 53162) [universal.x86_64-darwin16]
CocoaPods需要2.2.2版本及以上的,我们先升级ruby。
使用 rvm 安装 ruby
curl -L get.rvm.io | bash -s stable source ~/.bashrc source ~/.bash_profile
切换 ruby 源
ruby 下载源使用亚马逊的云服务被墙了,切换国内的 ruby-china源 (<https://ruby.taobao.org/>
已经停止维护,详情查看公告):
$ gem sources --add https://gems.ruby-china.org/ --remove https://rubygems.org/
$ gem sources -l
*** CURRENT SOURCES ***
https://gems.ruby-china.org
安装并切换 ruby
这里不建议安装最新的 2.4.0 版本,因为次版本的 ruby,在xcodebuild 自动打包时,会出现问题! 所以退一步,安装 2.3.3版本~
rvm install 2.3.3 --disable-binary
rvm use 2.3.3 --default
到此ruby升级完毕.
有关RVM的使用可以看这篇 RVM 使用指南
安装CocoaPods
- 安装
sudo gem install -n /usr/local/bin cocoapods
- 升级版本库
pod setup
这里需要下载版本库(非常庞大),需要等很久
Receiving objects: 72% (865815/1197150), 150.07 MiB | 190.00 KiB/s
或者直接从其他装有cocoapod的电脑中拷贝~/.cocoapods
到你的用户目录,然后再 pod setup
会节省不少时间
使用
创建 podfile
文件
绝大多数人创建podfile
都是用 vim Podfile
命令
其实pod 已经提供了创建 podfile
文件的命令,在工程目录下
pod init
将会自动生成 podfile
文件,并且为你写好了格式,稍做修改就能使用
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'projectName' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
# Pods for projectName
target 'projectNameTests' do
inherit! :search_paths
# Pods for testing
end
target 'projectNameUITests' do
inherit! :search_paths
# Pods for testing
end
end
其中的
target 'projectNameTests' do
inherit! :search_paths
# Pods for testing
end
target 'projectNameUITests' do
inherit! :search_paths
# Pods for testing
end
是指定在单元测试和UI测试时导入的测试框架,若没有使用测试框架可以删除。
修改iOS版本,添加Alamofire
库
# Uncomment the next line to define a global platform for your project
# platform :ios, '8.0'
target 'projectName' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
# Pods for projectName
pod 'Alamofire', '~> 4.4'
end
加载代码库
使用下面的命令,直接在本地版本库中查找对应的代码库信息,不升级版本库,节省时间
pod install --verbose --no-repo-update
若找不到库,再使用下面的命令
pod install
版本号
对版本号的操作除了指定与不指定,你还可以做其他操作:
\>0.1
高于0.1的任何版本\>=0.1
版本0.1和任何更高版本<0.1
低于0.1的任何版本<=0.1
版本0.1和任何较低的版本〜>0.1.2
版本 0.1.2的版本到0.2 ,不包括0.2。 这个基于你指定的版本号的最后一个部分。这个例子等效于>= 0.1.2
并且<0.2.0
,并且始终是你指定范围内的最新版本
结语
关于CocoaPods的安装和使用就这样简单的介绍完了,至于更多使用的方法(平时也用不到~)你可以用下面命令查看
$ pod
若对 CocoaPods 的个人仓库感兴趣,也可以看看我的这两篇博客
强化 swift 中的 print
在 Swift 中,最简单的输出方法就是使用 print()
,在我们关心的地方输出字符串和值。
当程序变得非常复杂的时候,我们可能会输出很多内容,而想在其中寻找到我们希望的输出其实并不容易。我们往往需要更好更精确的输出,这包括输出这个 log 的文件,调用的行号以及所处的方法名字等等。
在 Swift 中,编译器为我们准备了几个很有用的编译符号,它们分别是:
符号 | 类型 | 描述 |
---|---|---|
#file | String | 包含这个符号的文件的路径 |
#line | Int | 符号出现处的行号 |
#column | Int | 符号出现处的列 |
#function | String | 包含这个符号的方法名字 |
有了上面的这些编译符号,我们就可以自定义一个输出函数:printm
public func printm(items: Any..., filename: String = #file, function: String = #function, line: Int = #line) {
print("[\((filename as NSString).lastPathComponent) \(line) \(function)]\n",items)
}
因为输出是一个很消耗性能的操作,所以在releass环境下需要将输出函数去掉,将上面的函数换成:
#if DEBUG
public func printm(items: Any..., filename: String = #file, function: String = #function, line: Int = #line) {
print("[\((filename as NSString).lastPathComponent) \(line) \(function)]\n",items)
}
#else
public func printm(items: Any..., filename: String = #file, function: String = #function, line: Int = #line) { }
#endif
参考:
- 《LOG 输出》 - 王巍 (@ONEVCAT)
Swift 3.1 的新变化「译」
Xcode 8.3 和 Swift 3.1 现在已经发布了(3/28)!
可以通过 AppStore 或 Apple Developer 进行下载
Xcode 8.3 优化了 Objective-C 与 Swift 混编项目的编译速度.
Swift 3.1 版本包含一些期待已久的 Swift package manager 功能和语法本身的改进。
如果您没有密切关注 Swift Evolution 进程,请继续阅读 - 本文非常适合您!
在本文中,我将强调Swift 3.1中最重要的变化,这将对您的代码产生重大影响。我们来吧!😃
开始
Swift 3.1与Swift 3.0源代码兼容,因此如果您已经使用Xcode 中的 Edit \ Convert \ To Current Swift Syntax ...
将项目迁移到Swift 3.0,新功能将不会破坏您的代码。不过,苹果已经在Xcode 8.3中支持Swift 2.3。所以如果你还没有从Swift 2.3迁移,现在是时候这样做了!
在下面的部分,您会看到链接的标签,如[SE-0001]
。这些是 Swift Evolution 提案号码。我已经列出了每个提案的链接,以便您可以发现每个特定更改的完整详细信息。我建议您尝试在Playground上验证新的功能,以便更好地了解所有更改的内容。
Note:如果你想了解 swift 3.0 中的新功能,可以看这篇文章。
语法改进
首先,我们来看看这个版本中的语法改进,包括关于数值类型的可失败构造器
(Failable Initializers
),新的序列函数等等。
可失败的数值转换构造器(Failable Numeric Conversion Initializers)
Swift 3.1 为所有数值类型 (Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Float, Float80, Double)
添加了可失败构造器。
这个功能非常有用,例如,以安全、可恢复的方式处理外源松散类型数据的转换,下面来看 Student 的 JSON 数组的处理:
class Student {
let name: String
let grade: Int
init?(json: [String: Any]) {
guard let name = json["name"] as? String,
let gradeString = json["grade"] as? String,
let gradeDouble = Double(gradeString),
let grade = Int(exactly: gradeDouble) // <-- 3.1 的改动在这
else {
return nil
}
self.name = name
self.grade = grade
}
}
func makeStudents(with data: Data) -> [Student] {
guard let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments),
let jsonArray = json as? [[String: Any]] else {
return []
}
return jsonArray.flatMap(Student.init)
}
let rawStudents = "[{\"name\":\"Ray\", \"grade\":\"5.0\"}, {\"name\":\"Matt\", \"grade\":\"6\"},
{\"name\":\"Chris\", \"grade\":\"6.33\"}, {\"name\":\"Cosmin\", \"grade\":\"7\"},
{\"name\":\"Steven\", \"grade\":\"7.5\"}]"
let data = rawStudents.data(using: .utf8)!
let students = makeStudents(with: data)
dump(students) // [(name: "Ray", grade: 5), (name: "Matt", grade: 6), (name: "Cosmin", grade: 7)]
在 Student
类中使用了一个可失败构造器将 grade
属性从 Double
转变为 Int
,像这样
let grade = Int(exactly: gradeDouble)
如果gradeDouble
不是整数,例如6.33,它将失败。如果它可以用一个正确的表示Int,例如6.0,它将成功。
Note:虽然
throwing initializers
可以用来替代failable initializers
。但是使用failable initializers
会更好,更符合人的思维。
新的序列函数(Sequence Functions)
swift3.1添加了两个新的标准库函数在 Sequence
协议中:prefix(while:)``和prefix(while:)
[SE-0045]。
构造一个斐波纳契无限序列:
let fibonacci = sequence(state: (0, 1)) {
(state: inout (Int, Int)) -> Int? in
defer {state = (state.1, state.0 + state.1)}
return state.0
}
在Swift 3.0中,您只需指定迭代次数
即可遍历fibonacci序列:
// Swift 3.0
for number in fibonacci.prefix(10) {
print(number) // 0 1 1 2 3 5 8 13 21 34
}
在swift 3.1中,您可以使用prefix(while:)
和drop(while:)
获得符合条件在两个给定值之间的序列中的所有元素,就像这样:
// Swift 3.1
let interval = fibonacci.prefix(while: {$0 < 1000}).drop(while: {$0 < 100})
for element in interval {
print(element) // 144 233 377 610 987
}
prefix(while:)
返回满足某个谓词的最长子序列。它从序列的开头开始,并停在给定闭包返回false的第一个元素上。
drop(while:)
相反:它返回从给定关闭返回false的第一个元素开始的子序列,并在序列结尾完成。
Note:这种情况,可以使用尾随闭包的写法:
let interval = fibonacci.prefix{$0 < 1000}.drop{$0 < 100}
Concrete Constrained Extensions(姑且翻译为类的约束扩展吧)
Swift 3.1允许您扩展具有类型约束的通用类型。以前,你不能像这样扩展类型,因为约束必须是一个协议。我们来看一个例子。
例如,Ruby on Rails提供了一种isBlank
检查用户输入的非常有用的方法。以下是在Swift 3.0中用 String
类型的扩展实现这个计算型属性:
// Swift 3.0
extension String {
var isBlank: Bool {
return trimmingCharacters(in: .whitespaces).isEmpty
}
}
let abc = " "
let def = "x"
abc.isBlank // true
def.isBlank // false
如果你希望isBlank
计算型属性为一个可选值所用,在swift 3.0中,你将要这样做
// Swift 3.0
protocol StringProvider {
var string: String {get}
}
extension String: StringProvider {
var string: String {
return self
}
}
extension Optional where Wrapped: StringProvider {
var isBlank: Bool {
return self?.string.isBlank ?? true
}
}
let foo: String? = nil
let bar: String? = " "
let baz: String? = "x"
foo.isBlank // true
bar.isBlank // true
baz.isBlank // false
这创建了一个采用 String
的 StringProvider
协议而在你使用StringProvider扩展可选的 wrapped 类型时,添加isBlank方法。
Swift 3.1中,用来替代协议方法,扩展具体类型的方法像这样:
// Swift 3.1
extension Optional where Wrapped == String {
var isBlank: Bool {
return self?.isBlank ?? true
}
}
这就用更少的代码实现了和原先相同的功能~
泛型嵌套(Nested Generics)
Swift 3.1允许您将嵌套类型与泛型混合。作为一个练习,考虑这个(不是太疯狂)的例子。每当某个团队领导raywenderlich.com想在博客上发布一篇文章时,他会分配一批专门的开发人员来处理这个问题,以满足网站的高质量标准:
class Team<T> {
enum TeamType {
case swift
case iOS
case macOS
}
class BlogPost<T> {
enum BlogPostType {
case tutorial
case article
}
let title: T
let type: BlogPostType
let category: TeamType
let publishDate: Date
init(title: T, type: BlogPostType, category: TeamType, publishDate: Date) {
self.title = title
self.type = type
self.category = category
self.publishDate = publishDate
}
}
let type: TeamType
let author: T
let teamLead: T
let blogPost: BlogPost<T>
init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost<T>) {
self.type = type
self.author = author
self.teamLead = teamLead
self.blogPost = blogPost
}
}
将BlogPost
内部类嵌套在其对应的Team
外部类中,并使两个类都通用。这是团队如何寻找我在网站上发布的教程和文章:
Team(type: .swift, author: "Cosmin Pupăză", teamLead: "Ray Fix",
blogPost: Team.BlogPost(title: "Pattern Matching", type: .tutorial,
category: .swift, publishDate: Date()))
Team(type: .swift, author: "Cosmin Pupăză", teamLead: "Ray Fix",
blogPost: Team.BlogPost(title: "What's New in Swift 3.1?", type: .article,
category: .swift, publishDate: Date()))
但实际上,在这种情况下,您可以简化该代码。如果嵌套的内部类型使用通用外部类型,那么它默认继承父类的类型。因此,您不需要如此声明:
class Team<T> {
// original code
class BlogPost {
// original code
}
// original code
let blogPost: BlogPost
init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost) {
// original code
}
}
Note:如果您想了解更多关于Swift中的泛型,请阅读我们最近更新的Swift泛型入门的教程。
Swift版本的可用性
您可以使用**#if swift(>= N)** 静态构造
来检查特定的Swift版本:
// Swift 3.0
#if swift(>=3.1)
func intVersion(number: Double) -> Int? {
return Int(exactly: number)
}
#elseif swift(>=3.0)
func intVersion(number: Double) -> Int {
return Int(number)
}
#endif
然而,当使用Swift标准库时,这种方法有一个主要缺点。它需要为每个受支持的旧语言版本编译标准库。这是因为当您以向后兼容模式运行Swift编译器时,例如您要使用Swift 3.0行为,则需要使用针对该特定兼容性版本编译的标准库版本。如果您使用版本3.1模式编译的,那么您根本就没有正确的代码
因此,@available除了现有平台版本 [SE-0141] 之外,Swift 3.1扩展了该属性以支持指定Swift版本号:
// Swift 3.1
@available(swift 3.1)
func intVersion(number: Double) -> Int? {
return Int(exactly: number)
}
@available(swift, introduced: 3.0, obsoleted: 3.1)
func intVersion(number: Double) -> Int {
return Int(number)
}
这个新功能提供了与intVersionSwift
版本有关的方法相同的行为。但是,它只允许像标准库这样的库被编译一次。编译器然后简单地选择可用于所选择的给定兼容性版本的功能。
Note:注意:如果您想了解更多关于Swift 的
可用性属性( availability attributes)
,请参阅我们关于Swift中可用性属性的教程。
逃逸闭包(Escaping Closures)
在Swift 3.0 [ SE-0103 ] 中函数中的闭包的参数是默认是不逃逸的(non-escaping)。在Swift 3.1中,您可以使用新的函数withoutActuallyEscaping()
将非逃逸闭包转换为临时逃逸。
func perform(_ f: () -> Void, simultaneouslyWith g: () -> Void,
on queue: DispatchQueue) {
withoutActuallyEscaping(f) { escapableF in // 1
withoutActuallyEscaping(g) { escapableG in
queue.async(execute: escapableF) // 2
queue.async(execute: escapableG)
queue.sync(flags: .barrier) {} // 3
} // 4
}
}
此函数同时加载两个闭包,然后在两个完成之后返回。
f
与g
进入函数后由非逃逸状态,分别转换为逃逸闭包:escapableF
和escapableG
。- async(execute:) 的调用需要逃逸闭包,我们在上面已经进行了转换。
- 通过运行
sync(flags: .barrier)
,您确保async(execute:)
方法完全完成,稍后将不会调用闭包。 - 在范围内使用
escapableF
andescapableG
.
如果你存储临时逃离闭包(即真正逃脱)这将是一个Bug。未来版本的标准库可以检测这个陷阱,如果你试图调用它们。
Swift Package Manager 更新
啊,期待已久的 Swift Package Manage 的更新了!
可编辑软件包(Editable Packages)
Swift 3.1将可编辑软件包(editable packages)
的概念添加到Swift软件包管理器 [ SE-0082 ]。
该swift package edit
命令使用现有的Packages
并将其转换为editable Packages
。使用--end-edit
命令将 package manager
还原回 规范解析的软件包(canonical resolved packag)。
版本固定(Version Pinning)
Swift 3.1 添加了版本固定的概念[ SE-0145 ]。该 pin
命令 固定一个或所有依赖关系如下所示:
$ swift package pin --all // 固定所有的依赖
$ swift package pin Foo // 固定 Foo 在当前的闭包
$ swift package pin Foo --version 1.2.3 // 固定 Foo 在 1.2.3 版本
使用unpin
命令恢复到以前的包版本:
$ swift package unpin —all
$ swift package unpin Foo
Package manager 将每个依赖库的版本固定信息存储在 Package.pins
文件中。如果该文件不存在,则Package manager 会自动创建。
其他
swift package reset
命令将会把 Package 重置干净。
swift test --parallel
命令 执行测试。
其他改动
在 swift 3.1 中还有一些小改动
多重返回函数
C函数返回两次,例如vfork
和 vfork
已经不用了。他们以有趣的方式改变了程序的控制流程。所以 Swift 社区 已经禁止了该行为,以免导致编译错误。
自动链接失效(Disable Auto-Linking)
Swift Package Manager 禁用了在C语言 模块映射(module maps)中的自动链接的功能:
// Swift 3.0
module MyCLib {
header “foo.h"
link “MyCLib"
export *
}
// Swift 3.1
module MyCLib {
header “foo.h”
export *
}
结语
Swift 3.1改善了Swift 3.0的一些功能,为即将到来的Swift 4.0的大改动做准备。这些包括对泛型,正则表达式,更科学的String
等方面的作出极大的改进。
如果你想了解更多,请转到 Swift standard library diffs 或者查看官方的的Swift CHANGELOG,您可以在其中阅读所有更改的信息。或者您可以使用它来了解 Swift 4.0 中的内容!