Swift 5 新特性详解:ABI 稳定终于来了!

阅读数:8575 2019 年 1 月 28 日 09:47

Swift 5新特性详解:ABI 稳定终于来了!

近日,苹果开发者博客更新了一篇关于 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<String, Int>) {}
}
let x = ToyCallable()
x(1, 2, 3)
// Desugars to `x.dynamicallyCall(withArguments: [1, 2, 3])`
x(label: 1, 2)
// Desugars to `x.dynamicallyCall(withKeywordArguments: ["label": 1, "": 2])`

现在支持标识 KeyPath(.self),一个引用其整个输入值的 WritableKeyPath

复制代码
let id = \Int.self
var x = 2
print(x[keyPath: id]) // Prints "2"
x[keyPath: id] = 3
print(x[keyPath: id]) // Prints "3"

在 Swift 5 之前,你可以编写一个带有可变参数的枚举:

复制代码
enum X {
    case foo(bar: Int...) 
}
func baz() -> X {
    return .foo(bar: 0, 1, 2, 3

现在如果这么做会出错。相反,现在参数改成了一个数组,并且需要显式传入数组:

复制代码
enum X {
    case foo(bar: [Int]) 
func baz() -> 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(<5)
extension MyType: _ExpressibleByStringInterpolation { /*...*/ }
#else
extension 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() -> SubSequence {
        return self.dropFirst(2)
    }
}

变为:

复制代码
extension Sequence {
    func dropTwo() -> DropFirstSequence<Self> { 
        return self.dropFirst(2)
    }
}

或者:

复制代码
extension Collection {
    func dropTwo() -> 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 <original URL> --mirror-url <mirror URL>
  • 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() -> Self { /*...*/ }
class Derived: Base {
    class override func factory() -> Derived { /*...*/
  • 在 Swift 5 模式下,现在明确禁止声明与嵌套类型同名的静态属性,而之前可以在泛型类型的扩展中进行这样的声明。

例如:

复制代码
struct Foo<T> {}
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 () -> Int) {}
func bar(_ fn: @autoclosure () -> Int) {
    foo(fn) // Incorrect, `fn` can’t be forwarded and has to be called.
    foo(fn()) // OK
  • 现在完全支持复杂的递归类型定义,包括之前在运行时会导致死锁的类和泛型。

  • 在 Swift 5 模式下,在将 Optional 值转换为通用占位符类型时,编译器在展开值时会更加保守。这种转换结果现在更接近于非通用上下文中获得的结果。

例如:

复制代码
func forceCast<U>(_ value: Any?, to type: U.Type) -> U {
    return value as! U 
let value: Any? = 42
print(forceCast(value, to: Any.self))
// Prints "Optional(42)"
// (Prior to Swift 5, this would print "42".)
print(value as! Any)
// Prints "Optional(42)"
  • 协议现在可以将符合类型限定为给定类的子类。支持两种等效形式:
复制代码
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 < 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 标头。

英文原文:
https://developer.apple.com/documentation/xcode_release_notes/xcode_10_2_beta_release_notes/swift_5_release_notes_for_xcode_10_2_beta?language=objc

更多内容,请关注前端之巅。

Swift 5新特性详解:ABI 稳定终于来了!

收藏

评论

微博

用户头像
发表评论

注册/登录 InfoQ 发表评论