写点什么

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

  • 2019-01-28
  • 本文字数:5203 字

    阅读完需:约 17 分钟

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.selfvar x = 2print(x[keyPath: id]) // Prints "2"x[keyPath: id] = 3print(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 { /*...*/ }#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() -> 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? = 42print(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 = <a href="">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 } } }}</a href="">
复制代码


  • 如果你使用 #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


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



2019-01-28 09:4716756
用户头像

发布了 731 篇内容, 共 451.8 次阅读, 收获喜欢 2002 次。

关注

评论

发布
暂无评论
发现更多内容

力扣解题:第三题(个人思路整理)

人语驿边桥

力扣

架构师训练营第三周课后作业

天涯若海

训练营第三周总结

大脸猫

极客大学架构师训练营

架构师训练营 -week07-作业

大刘

极客大学架构师训练营

TCP梳理总结

江龙

阿里P8大牛精心整理,GitHub上超火的《Java工程师成神之路》从基础,到高级、底层、架构、进阶、扩展,囊括了Java体系内的所有知识点。

Java架构之路

Java 程序员 架构 面试 编程语言

字节跳动HR:3年从4000人招到10万人,我经历了什么

Java架构师迁哥

从技术到应用实践 揭秘京东区块链布局全景

京东科技开发者

区块链 区块链方案 供应链

LeetCode题解:231. 2的幂,递归,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

NPC Follow

katichar

谈谈敏捷开发概念和迭代开发方案

Philips

敏捷开发 快速开发

HTTP2协议及websocket协议总结

江龙

ViewportFrame demo

katichar

架構師訓練營第 1 期 - 第 07 周作業

Panda

架構師訓練營第 1 期

应用层软件开发教父教你如何重构,资深程序员必备专业技能

小Q

Java 学习 架构 面试 重构

【得物技术】数据分析 - 生活品类社区内容精选池模型

得物技术

数据分析 得物技术部 得物技术 社区内容 精选池模型

这可能是关于编程指南的最实用指南了

华为云开发者联盟

开发者 软件开发 语言

我去!三面字节竟全败在Redis上,带薪摸鱼刷1949页进阶笔记

996小迁

Java redis 架构 面试 程序人生

隐私计算S2赛季 谁是真正的王者?

hellompc

学习 隐私计算

MySQL中特别实用的几种SQL语句送给大家

陈哈哈

SQL优化 实用SQl语句 高性能SQL

一周信创舆情观察(10.26~11.1)

统小信uos

华为发布5GtoB核心网建设白皮书

华为云开发者联盟

5G 边缘技术

快快使用ModelArts,零基础小白也能玩转AI!

华为云开发者联盟

人工智能 开发者 开发

干货 | 京东技术中台的Flutter实践之路

京东科技开发者

flutter

LeetCode题解:231. 2的幂,迭代,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

啥是数据库范式

Simon

MySQL 数据库 数据库设计

低代码开发不靠谱?看低代码开发在物联网APP开发中的应用

华为云开发者联盟

技术 软件开发 代码

“软件教父”花费20年,教你如何在应用层混迹的风生水起

小Q

Java 学习 架构 面试 应用

Redis最常见的16道面试题与详解

Java架构师迁哥

字节跳动大神亲自总结SpringBoot手册,让你可以在简历上写精通SpringBoot!

Java架构追梦

Java 架构 面试 微服务 springboot

《高效程序员的45个习惯:敏捷开发修炼之道》.pdf

田维常

电子书

Swift 5新特性详解:ABI 稳定终于来了!_语言 & 开发_苹果开发者博客_InfoQ精选文章