作者简介:王芳杰,老码团队(@未来眼之老码团队)成员,目前就职于叠拓信息技术有限公司,担任叠拓NGN中国人力资源培养经理、叠拓NGN中国售前经理,《老码说编程之玩转Swift江湖》一书作者。
每年的WWDC都是全球苹果开发者的一次大狂欢,今年的WWDC也一样,不过狂欢的主题之一则是:Xcode 8.0 Beta的发布和Swift 3. 0的引入。到底带来了那些变化和惊喜呢?笔者给你细细道来。
Xcode 8.0 Beta需要macOS 10.11.4或者更新的系统。请注意苹果的系统名称 “Mac OS X”正式更改为“macOS”, 这样的命名更加简洁,而且和整个苹果家族的系统名称保持一致。
Xcode 8.0 Beta中包含了最新的iOS 10.0、watchOS 3.0、macOS 10.12以及tvOS 10.0,如果小伙伴们想看具体的变化以及每个系统的细节可以在Xcode中:点击【帮助>What’s New in Xcode】菜单项,或者直接登陆Apple Developer查看。
有个小细节需要注意:Xcode 8可以和以前的Xcode版本共存。Xcode 8.0 Beta属于预发布版本,购买了开发者ID的小伙伴可以通过Apple Developer网站下载,这是一个打包为XIP的文件。对于还处于Beta阶段的Xcode,小伙伴们可以直接从官网下载然后解压这个XIP文件,再把里面的Xcode.app文件拖放到Application目录下即可。而对于发布后的版本则需要通过App Store下载。在此,笔者建议大家还是从官方渠道下载安装,慢就慢点,不要从第三方渠道获取,因为大名鼎鼎的 “XcodeGhost” 病毒就是通过非官方的Xcode渠道传播的。
1. 编辑器扩展插件
Xcode 8.0 Beta支持编辑器扩展插件了:应用程序扩展插件会在Xcode编辑器的菜单上增加一个命令项,这些扩展插件可以操作代码编辑器中文本或者文本选区。有了这个接口将会涌现出一大批优秀的扩展插件。我们可以通过macOS Application Extensions中的 “Xcode Source Editor Extensions” target模板来创建我们自己的扩展插件。
2. Interface Builder的变化
Interface Builder对于自动布局特性更加智能,对于一些视图我们不想显式的增加约束时,Interface Builder会自动处理增加自动布局特性。
Storyboard和XIB文件可以在iOS、tvOS和watchOS等不同平台间无缝的缩放。在我们编辑这些界面布局文件时可以按住Option键,然后通过鼠标滚轮任意缩放。
重新修正了Xcode开发的工作流方式,比如对于Size Classes,现在Xcode支持基于一系列真实的设备尺寸进行UI布局设计而不是基于一些抽象的矩形集合。这样的改变让开发者可以方便查看UI布局文件在多设备上的效果,比如方向,以及在iPad的上Slide Over和分割视图的适配情况等。
我们可以按住Control键然后从Interface Builder中向Swift 3的代码文件中拖拽可以创建一个IBAction,注意这时会在增加的Sender参数前增加一个下划线,此举的目的可以使相关的Objective-C选择器能够适配Swift 2的语法行为。因此在Swift 3和Swift 2语法共存的情况下,可以通过增加下划线的方式把@IBAction转换为Swift 3的语法,从而保留当前现有的IBAction链接。小伙伴们这种情况大多会发生在旧代码向Swift 3转换的过程中,所以大家看看即可。风险极大。
Interface Builder中的Canvas会像应用程序运行时一样通过虚景交互方式渲染iOS的视图,包括采用UIVisualEffectView方式的试图以及视图与子视图。这种渲染方式在tvOS上渲染性能大大提高。
Interface Builder支持在tvOS上的Dark Interface Style中自定义UI元素以及通过辅助编辑器进行预览。这个特性在新的XIB和Storyboard文件默认开启。对于现有文件可以在Identity查看器上勾选 “Use Trait Variations” 开启。
Interface Builder现在支持Display P3颜色空间的颜色了。可以在系统颜色面板选择RGB或者HSB滑块,然后点击在弹出面板的列表中选择 “Display P3” 开启。
在Interface Builder的布局文件中的颜色值在渲染阶段和编译阶段会使用正确颜色空间了。以前的Xcode版本在iOS和tvOS布局文件中的颜色空间处理方面存在错误。现在Xcode 8.0会根绝终端设备的特性进行正确的适配。
3. Playground的变化
在Xcode中使用macOS target的Playground现在支持来自于Swift.org的开源的Swift toolchains。使用iOS或者tvOS的Playground则需要Xcode 8.0的toolchain。
Playground中的video标签支持远程的URL。
4. Swift语言的变化
Xcode 8.0 Beta中包含Swift 3和Swift 2. 3两个版本的预发布版本。如果没有特别说明的话,Xcode 8默认采用Swift 3语法版本。其中的“SE-XXXX” 数字指向的是 “Swift语法演进” 建议的序号,小伙伴们可以通过Swift Evolution查看具体的信息。对于现有的代码可以在Xcode中通过菜单“Edit -> Convert -> To Latest Swift Syntax …”转换到最新的语法。
Swift中的Objective-C导入机制和标准库接口已被修正并且遵循新的API规范,此举的目的是可以为小伙伴们提供统一、简洁一致性的接口。[SE-0023] [SE-0005] [SE-0006]
增加了一个新的属性swift_name。该属性允许C API的作者指定他们的API如何导入到Swift中。[SE-0044]
增加了新的属性,对于Objective-C中的常量列表,这个属性可以将其以Enum枚举或者结构体Struct的方式导入到Swift中,并且可以通过RawRepresentable协议将其转换到它原始类型。[SE-0033]
隐式解封可选类型的行为有所变化,“!”语法(如NSObject!)现在只允许用于变量、常量、参数和返回的结果值类型上。隐式解封类可选类型的值依然可以用在非可选的类型上,但是对于泛型特性来说则需要指定明确的可选类型。
比如:
var x: Int! = nil // x是类型 Int?的隐式解封可选类型值
var y = x // y是Int?类型的基本可选类型值
var z = x ?? 0 // x将会被当作可选类型; 因为x是nil, z 将会赋值为0
print(x + 0) // x因为会被隐式解封为nil,所以这里将会导致异常中断。
函数参数具备行为一致的标签系统,这个更新将使第一个参数的标签声明与其他的参数声明保持一致。比如:
func foo(x: Int, y: Int) {}
foo(1, y: 2)
func bar(a a: Int, b: Int) {}
bar(a: 3, b: 4
现在改变为:
func foo(_ x: Int, y: Int) {}
foo(1, y: 2)
func bar(a: Int, b: Int) {}
bar(a: 3, b: 4)
小伙伴们看出差异了没?以前的Swift语法对于第一个参数的标签用法很奇怪,需要通过显式地提供外部参数名才可以使用指定参数标签,如果不指定则省略调用。而新的语法版本支持外部参数名和内部参数名一致,当然可以通过下划线来忽略外部参数标签。
对于未使用的结果值默认情况下编译器会报警。[SE-0047]
柯里化函数声明被移除,这么怪异用法留着何用,删除了更好。[SE-0002]
“++“和”—“自变量运算符被移除,遥想当年自增自减,前自增后自增,前减,后减害死了多少小伙伴啊。[SE-0004]
C风格的循环方式被移除。[SE-0007]
在调用中的元组的隐式扩展特性被移除,华而不实特性移除了语法系统会更清晰。[SE-0029]
很多关键字现在可以用作类型的成员名称而不需要用反引号特别标注。比如你的类型foo有一个成员名为default,但是default本身是一个关键字,你依然可以这么写foo.default。[SE-0071]
对于CF类型名称的Ref格式的命名方式从Swift中被移除,比如现在用CFString而不是用CFStringRef。有一个例外就是对于同时有类型Foo和FooRef名称时,而FooRef又是一个Core Function类型,这种情况下则会继续沿用的方式。
引入了新的表达式 #file, #sourceLocation, #column和 #function来替代现有的FILE, LINE, COLUMN和FUNCTION符号, FILE类型的符号被移除。[SE-0028] [SE-0034]
对于默认参数在调用时的顺序依然需要与函数声明时的顺序相一致。[SE-0060]
函数类型语法被标准化,需要在参数列表两侧必须有圆括号。[SE-0066]
对于Objective-C中轻量级的泛型类可以直接以泛型类型的方式导入到Swift中,因为Objective-C中的泛型不是运行时泛化的,所以在在Swift中行为有一些限制[SE-0057]:
let x = NSFoo(value: NSNumber(integer: 0))
let y: AnyObject = x
let z = y as! NSFoo // Succeeds
// 错误:因为这里泛型参数没有明确化
class SwiftFoo1<T>: NSFoo<T> { }
// 正确:这里的泛型参数明确化了
class SwiftFoo2<T>: NSFoo<NSString> { }
extension NSFoo {
//错误:不能访问泛型参数T
func foo() -> T
{ return T() } }
// 错误:不能为扩展增加约束
NSFoo where T: NSString { }
}
类型UnsafePointer、UnsafeMutablePointer、AutoreleasingUnsafeMutablePointer、OpaquePointer、Selector和NSZone被表示为非空指针,所谓非空指针指这些指针类型变量永远不能为nil。而可为空指针被表示为可选类型,例如:UnsafePointer?
。对于C类型指针中的基本类型指针要注意它们的可为空特性:
一个被标记为_Nonnull的指针或者放在NS_ASSUME_NONNULL_BEGIN/_END块中的指针将会以非可选类型的方式导入。
例如::NSInteger * _Nonnull 被导入为UnsafeMutablePointer。
一个被标注为_Nullable的指针被导入为可选类型值。例如:NSInteger * _Nullable将会被导入为UnsafeMutablePointer?。
一个被标记为_Null_unspecified的指针或者不处于任何NS_ASSUME_NONNULL_BEGIN/_END块中的指针将会以隐式解封可选类型值得方式导入到Swift中,例如:NSInteger * _Null_unspecified将会被导入为:UnsafeMutablePointer!。
如果想给一个使用C类型参数的函数传递一个可空类型的指针,Swift不允许直接传递需指定指针的宽度和类型,这里以Int类型的指针的为例:
Int(bitPattern: nullablePointer)
注释会被看作为空格,所以下面这种方式是正确的:
if /*comment*/!foo { ... }
1 +/*comment*/2
但是如果用在一元操作符中则不行,比如:
foo/* comment */! // no longer works
关于inout参数标记符号移到了“:“之后参数类型之前,如:
func foo(inout x: Int) { }
改为:
func foo(x: inout Int) { }
@noescape 和@autoclosure属性现在必须用在参数类型之前而不是参数名称之前。
被导入为NS_OPTIONS set类型中none成员现在不能用了,现在直接使用“[]”而不是调用none成员。
对于属性赋值现在现在采用”:”而不是“=”,这种用法和函数参数调用保持一致了。[SE-0040]
现在支持泛型别名了,例如:[SE-0048]
typealias StringDictionary = Dictionary
typealias IntFunction = (T) -> Int
typealias MatchingTriple = (T, T, T)
typealias BackwardTriple = (T3, T2, T1)
@noescape属性可以扩展到很多泛型类型中,你现在可以声明一个@noescape属性的函数类型值,比如对于柯里化函数的签名,当然你也可以声明具有@noescape属性的本地变量,也可以把@noescape用在类型别名中,例如:
func apply(f: @noescape T -> U,
g: @noescape (@noescape T -> U) -> U) -> U
{
return g(f)
}
对于异常处理模块,可以在catch语句的rethrows函数中抛出错误,例如:
func process(f: () throws -> Int)
rethrows -> Int {
do { return try f()
}catch is SomeError
{
throw OtherError()
} }
rethrow函数的闭包参数可以为可选类型:
func executeClosureIfNotNil(closure: (() throws -> Void)?) rethrows { try closure?() }
对于属性的getter/setter访问器的ObjC选择器可以通过#selector访问,例如:[SE-0064]
let sel1 = #selector(getter: UIView.backgroundColor) // sel1 具有类型访问器
let sel2 = #selector(setter: UIView.backgroundColor) // sel2 具有类型访问
Key Path可以采用#keypath的方式调用,例如:[SE-0062]
person.valueForKeyPath(#keyPath(Person.bestFriend.lastName))
从Swift抛出的错误,该错误中的domain域包含类型的模块名称,它和导出头文件生成的模块名称保持一致。[SR-700]
任何一门语言的壮大完善都需要一个漫长的过程,Swift出身显赫但是丝毫没有淡化它积极完善的步伐,从WWDC 2014面世到现在,从苹果独有到开源跨平台,我们看到了一个工业级开发语言影子。希望Chris大神更加奋进,希望苹果更加奋进,希望Swift语言更加完善。
相关阅读:
第一时间掌握最新移动开发相关信息和技术,请关注mobilehub公众微信号(ID: mobilehub)。