近日,苹果开发者博客更新了一篇关于Swift 5的文章,带来了Swift 5新特性的消息,其中最受开发期待的莫过于iOS 12.2将带来ABI 稳定性,这意味着基础库将植入系统中,不再包含在App中,应用程序的体积会更小,更多新功能请看下文。
App瘦身
新功能
Swift应用程序不再包含用于Swift标准库和Swift SDK(运行iOS 12.2、watchOS 5.2和tvOS 12.2的设备的构建变体)的动态链接库。因此,在使用TestFlight进行测试时,或者为本地开减小应用程序体积时,Swift应用程序可以变得更小。
要查看iOS 12.2和iOS 12.1(或更早版本)应用程序之间的文件大小差异,请将应用程序的部署目标设置为iOS 12.1或更早版本,将scheme设置为Generic iOS Device,然后创建应用程序压缩包。
在构建好压缩包之后,从压缩包管理器中选择Distribution App,然后选择Development Distribution。确保在App Thinning下拉菜单中选择特定的设备,比如iPhone XS。这个过程完成后,在新创建的文件夹中打开App Thinning Size Report。iOS 12.2的体积会比iOS 12.1或更早版本的体积小。具体的大小差异取决于应用程序使用的框架的数量。
Swift
@dynamicCallable属性允许你调用命名的类型,就像使用简单的语法糖调用函数一样。主要的应用场景是动态语言互操作性。
例如:
@dynamicCallable struct ToyCallable { func dynamicallyCall(withArguments: [Int]) {} func dynamicallyCall(withKeywordArguments: KeyValuePairs\u0026lt;String, Int\u0026gt;) {}}let x = ToyCallable()x(1, 2, 3)// Desugars to `x.dynamicallyCall(withArguments: [1, 2, 3])`x(label: 1, 2)// Desugars to `x.dynamicallyCall(withKeywordArguments: [\u0026quot;label\u0026quot;: 1, \u0026quot;\u0026quot;: 2])`
现在支持标识KeyPath(.self),一个引用其整个输入值的WritableKeyPath:
let id = \\Int.selfvar x = 2print(x[keyPath: id]) // Prints \u0026quot;2\u0026quot;x[keyPath: id] = 3print(x[keyPath: id]) // Prints \u0026quot;3\u0026quot;
在Swift 5之前,你可以编写一个带有可变参数的枚举:
enum X { case foo(bar: Int...) }func baz() -\u0026gt; X { return .foo(bar: 0, 1, 2, 3) }
现在如果这么做会出错。相反,现在参数改成了一个数组,并且需要显式传入数组:
enum X { case foo(bar: [Int]) } func baz() -\u0026gt; X { return .foo(bar: [0, 1, 2, 3]) }
在Swift 5模式下,可以用?和Optional类型表达式来扁平化生成的Optional,而不是返回嵌套的Optional。
如果类型T符合这些字面量初始化中的一个——例如ExpressibleByIntegerLiteral——并假设literal是一个字面量表达式,那么T(literal)就创建了一个T类型的字面量。
例如,UInt64(0xffff_ffff_ffff_ffff)现在是有效的,而之前它们会导致默认整型字面量类型Int溢出。
字符串插值的性能、清晰度和效率得到了改进。
旧的_ExpressibleByStringInterpolation协议被移除,如果你的代码使用了这个协议,需要更新这些代码,你可以使用#if在Swift 4.2和Swift 5之间条件化代码。例如:
#if compiler(\u0026lt;5)extension MyType: _ExpressibleByStringInterpolation { /*...*/ }#elseextension MyType: ExpressibleByStringInterpolation { /*...*/ }#endif
Swift标准库
DictionaryLiteral类型被重命为KeyValuePairs。
与Objective-C代码桥接的Swift字符串现在会在适当的时候从CFStringGetCStringPtr返回一个非空值,而且从-UTF8String返回的指针与字符串的生命周期(而不是最里面的autorelease pool)相关联。正确的程序应该不会有任何问题,而且还会带来性能方面的提升。但是,它可能会导致以前未经测试的代码暴露出潜在的错误。
Sequence协议不再具有SubSequence关联类型。之前返回SubSequence的Sequence方法现在返回的是具体的类型。例如,suffix(_:)现在返回Array。
使用SubSequence的Sequence扩展应该修改为使用具体的类型,或者修改为Collection的扩展(此时SubSequence仍然可用)。
例如:
extension Sequence { func dropTwo() -\u0026gt; SubSequence { return self.dropFirst(2) }}
变为:
extension Sequence { func dropTwo() -\u0026gt; DropFirstSequence\u0026lt;Self\u0026gt; { return self.dropFirst(2) }}
或者:
extension Collection { func dropTwo() -\u0026gt; SubSequence { return self.dropFirst(2) }}
- String结构的原生编码从UTF-16切换到UTF-8,这样提高了String.UTF8View的性能(相对于String.UTF16View)。
Swift包管理器
现在,在使用Swift 5 Package.swift工具版本时,可以声明一些常用的特定于目标的构建设置。新的设置也可以基于平台和构建配置进行条件化。构建设置支持Swift和C语言定义、C语言头文件搜索路径、链接库和链接框架。
现在,在使用Swift 5 Package.swift工具版本时,可以为Apple平台自定义最低部署目标。如果程序包的任何依赖项指定的最小部署目标大于程序包自身的最低部署目标,就会抛出错误。
新的依赖项镜像功能允许顶层包覆盖依赖项URL。
可以使用以下命令设置镜像:
$ swift package config set-mirror \\--package-url \u0026lt;original URL\u0026gt; --mirror-url \u0026lt;mirror URL\u0026gt;
swift test命令提供了–enable-code-coverage标志,它生成的代码覆盖率数据也适用于其他代码覆盖工具。生成的代码覆盖率数据放在//codecov目录中。
Swift 5不再支持Swift 3 Package.swift工具版本。Swift 3 Package.swift工具版本的软件包应该升级到更新的工具版本。
针对较大的程序包的包管理器操作现在明显更快。
Swift包管理器提供了一个新的–disable-automatic-resolution标志,当Package.resolved条目与Package.swift清单文件中指定的依赖项版本不兼容时,该标志会强制包解析失败。在进行持续集成时,如果需要检查包的Package.resolved是否已过期,这项功能会非常有用。
swift run命令提供了一个新的——repl选项,它将启动Swift REPL,支持导入包的库目标。这样你就可以轻松地试用API,而无需构建调用该API的可执行文件。
Swift编译器
现在,对于优化(-O和-Osize)构建,默认情况下在运行时强制执行独占内存访问。违反排他性的程序将在运行时出现“overlapping access”诊断消息。你可以使用命令行标志禁用它:-enforce-exclusivity = unchecked,但这样做可能会导致未定义的行为。运行时违反排他性通常是由于同时访问类属性、全局变量(包括顶层代码中的变量)或通过转义闭包捕获的变量。
Swift 3模式已被删除。-swift-version标志支持的值为4、4.2和5。
在Swift 5模式中,在迭代使用Objective-C声明或来自系统框架的枚举时需要处理未知的case——可能在将来添加的case,或者可能在Objective-C实现文件中私下定义的case。Objective-C允许在枚举中存储任意值,只要它们与底层类型匹配即可。可以使用新的@unknown来处理这些未知case,当然也可以使用普通的default来处理它们。
如果你已在Objective-C中定义了自己的枚举,并且不需要客户端处理未知case,那么可以使用NS_CLOSED_ENUM宏而不是NS_ENUM。Swift编译器就会识别出来,不要求在迭代时提供默认case。
在Swift 4和4.2模式下,你仍然可以使用@unknown。如果省略了它并传入了一个未知的值,程序将在运行时出错,这与Xcode 10.1中的Swift 4.2的行为是一样的。
现在,默认参数打印在SourceKit生成的Swift模块接口中,而不只是使用占位符。
unowned和unowned(unsafe)变量现在支持Optional。
已知问题
- 如果引用了UIAccessibility的成员,Swift编译器会在进行到“Merge swiftmodule”这个构建步骤时崩溃。构建日志会包含这样一条消息:
Cross-reference to module 'UIKit'... UIAccessibility... in an extension in module 'UIKit'... GuidedAccessError
包含NS_ERROR_ENUM枚举的其他类型也可能出现这个问题,但UIAccessibility是最常见的。
解决方法:使用“Swift Compiler - Code Generation”下的Whole Module编译模式选项重新构建,这是大多数发布配置的默认设置。
为了减少Swift元数据占用的空间,Swift中定义的便捷初始化器现在只在调用Objective-C中定义的指定初始化器时提前分配对象。在大多数情况下,这对程序没有任何影响,但是如果从Objective-C调用便捷初始化器,就会释放+alloc初始分配的资源。对于不希望发生对象替换的初始化器用户来说,这可能是有问题的。例如,在使用initWithCoder:时,NSKeyedUnarchiver的实现可能会不正确,如果它调用了init(coder:)的Swift实现,并且对象图中包含了环。
如果KeyPath字面量引用了在Objective-C中定义的属性或者在Swift中使用@objc和动态修饰符定义的属性,那么编译可能会失败,并抛出“unsupported relocation of local symbol ‘L_selector’”的错误,或者KeyPath可能无法在运行时生成正确的哈希值。
解决方法:你可以自己定义非@objc包装器属性,指向这个KeyPath。生成的KeyPath与引用原始Objective-C属性的KeyPath不一样,但使用效果是一样的。
某些项目可能会遇到编译时回归问题。
Swift命令行项目在启动时会崩溃,错误为“dyld: Library not loaded”。
解决方法:添加用户自定义的构建设置SWIFT_FORCE_STATIC_LINK_STDLIB = YES。
已解决的问题
扩展绑定现在支持嵌套类型的扩展,这些类型本身是在扩展内定义的。之前可能会因为声明顺序问题而失败,出现“undeclared type”错误。
在Swift 5模式下,返回Self的类方法不能再使用返回具体类类型(非final)的方法来覆盖。这类代码不是类型安全的,需要将它们改掉。
例如:
class Base { class func factory() -\u0026gt; Self { /*...*/ }} class Derived: Base { class override func factory() -\u0026gt; Derived { /*...*/ } }
- 在Swift 5模式下,现在明确禁止声明与嵌套类型同名的静态属性,而之前可以在泛型类型的扩展中进行这样的声明。
例如:
struct Foo\u0026lt;T\u0026gt; {}extension Foo { struct i {} // Error: Invalid redeclaration of 'i'. // (Prior to Swift 5, this didn’t produce an error.) static var i: Int { return 0 }}
现在可以在子类中正确继承具有可变参数的指定初始化器。
在Swift 5模式下,@autoclosure参数不能再被转发给另一个函数调用的@autoclosure参数。相反,你必须使用括号显式调用函数值。调用将被包含在一个隐式闭包中,保证了与Swift 4模式相同的行为。
例如:
func foo(_ fn: @autoclosure () -\u0026gt; Int) {}func bar(_ fn: @autoclosure () -\u0026gt; Int) { foo(fn) // Incorrect, `fn` can’t be forwarded and has to be called. foo(fn()) // OK}
现在完全支持复杂的递归类型定义,包括之前在运行时会导致死锁的类和泛型。
在Swift 5模式下,在将Optional值转换为通用占位符类型时,编译器在展开值时会更加保守。这种转换结果现在更接近于非通用上下文中获得的结果。
例如:
func forceCast\u0026lt;U\u0026gt;(_ value: Any?, to type: U.Type) -\u0026gt; U { return value as! U } let value: Any? = 42print(forceCast(value, to: Any.self))// Prints \u0026quot;Optional(42)\u0026quot;// (Prior to Swift 5, this would print \u0026quot;42\u0026quot;.)print(value as! Any)// Prints \u0026quot;Optional(42)\u0026quot;
- 协议现在可以将符合类型限定为给定类的子类。支持两种等效形式:
protocol MyView: UIView { /*...*/ }protocol MyView where Self: UIView { /*...*/ }
Swift 4.2接受了第二种形式,但还没有完全实现,在编译时或运行时偶尔会发生崩溃。
- 在Swift 5模式下,当在自己的didSet或willSet observer中设置属性时,observer现在只在self上设置属性(不管是隐式的还是显式的)时才会避免被递归调用。
例如:
class Node { var children = Node var depth: Int = 0 { didSet { if depth \u0026lt; 0 { // Won’t recursively call didSet, because this is setting depth on self. depth = 0 } // Will call didSet for each of the children, // as this isn’t setting the property on self. // (Prior to Swift 5, this didn’t trigger property // observers to be called again.) for child in children { child.depth = depth + 1 } } }}
如果你使用#sourceLocation将生成文件中的行映射回源代码,那么诊断信息将显示在源文件中而不是生成文件中。
使用泛型类型别名作为@objc方法的参数或返回类型不会再生成无效的Objective-C标头。
更多内容,请关注前端之巅。