Swift 5 新特性

概览

Swift 5 发布了,这是一个重要里程碑。

此版本终于迎来了 ABI 稳定,因此 Swift 运行时现在可以引入到 Apple 平台各类操作系统的不同版本中,包括 macOS、iOS、tvOS 与 watchOS。Swift 5 还引入了构建块的新功能,包括重新实现 String、在运行时对执行内存的独占访问与新数据类型,以及对动态可调用类型的支持。

Swift 5 兼容 Swift 4、Swift 4.1 和 Swift 4.2,Xcode 10.2 中包含了一个代码迁移器,可以自动处理许多迁移需要用到的源码更改。

最重要的更新:ABI稳定

  1. 什么是ABI。
    在运行时,Swift程序二进制文件通过ABI与其他库和组件交互,ABI定义了很多底层细节:如何调用函数,如何在内存中表示数据,甚至是元数据的位置以及如何访问它。
  2. 什么是ABI稳定
    此前发布的Swift版本中ABI还没稳定,Swift并没包含在操作系统中,所以每一个应用内部都包含其Swift版本所对应的动态链接库
    Swift 5将为Swift的标准库提供稳定的ABI,Swift将包含在操作系统中,新版本的编译器会根据稳定的ABI来编译生成的应用程序二进制文件。也就是说,以后新发布的编译器也可以编译旧版Swift 5代码,源代码实现兼容。
  3. ABI稳定带来的便利
    • 因为源代码兼容,开发者能够跨多个Swift版本维护单个代码库;
    • Swift应用程序包会变得更小;
    • Swift语言变化以及变化频率都会有所下降;
  4. ABI稳定下阶段的目标
    关于Swift ABI 稳定规划,Swift 5完成的是第一阶段的源兼容(Source compatibility),下半部分是二进制framework和运行时兼容(Binary framework & runtime compatibility),详细描述可见宣言Swift ABI Stability Manifesto

新特性

Raw Strings (原始字符串)

SE-0200,增加了创建原始字符串的功能,使得写带有特殊符号的字符串更加简单。

  • 要使用原始字符串, 可使用#将字符串包裹起来;
let str = #"This is also a Swift string literal"#
  • 原始字符串中反斜杠和双引号会被视为字符串中的文字字符;
// before
let rain = "The \"rain\" in \"Spain\" falls \\mainly on the Spaniards." 
// after
let rain = #"The "rain" in "Spain" falls \mainly on the Spaniards."#  
  • 由于反斜杠作为原始字符串中的字符,所以在插入值的时候需要在后面再加个 #;
let answer = 42
// before
let dontpanic = "The answer to life, the universe, and everything is \(answer)"
// after
let dontpanic = #"The answer to life, the universe, and everything is \#(answer)"#
  • 如果需要在原始字符串包含 # 时, 前后应用 ## 包裹字符串
let str = ##"My dog said "woof"#gooddog"##
  • 多行原始字符串用 #""" 开头 """#结尾
  let multiline = #"""
  The answer to life,
  the universe,
  and everything is \#(answer).
  """#
  • 由于不用反斜杠转义,使得正则表达式更加简洁明了
// before
let regex1 = "\\\\[A-Z]+[A-Za-z]+\\.[a-z]+"
// after
let regex2 = #"\\[A-Z]+[A-Za-z]+\.[a-z]+"#

标准类型Result

SE-0235,增加Result类型到Swift标准库,用于统一在异步完成处理程序中看到的笨拙的不同参数。

public enum Result<Success, Failure: Error> {
    case success(Success), failure(Failure)
}

例如,在URLSession完成处理包含三个参数,处理起来就不怎么优雅:

URLSession.shared.dataTask(with: url) { (data, response, error) in
    guard error != nil else { self.handleError(error!) }
    
    guard let data = data, let response = response else { return /* Impossible? */ }
    
    handleResponse(response, data: data)
}

这几行代码暴露了Swift缺乏对错误自动处理的缺点,在这里不仅是因为需要对error做处理,而且缺少比如data和response都为空等特殊情况的处理,使用Result则会非常的优雅:

URLSession.shared.dataTask(with: url) { (result: Result<(response: URLResponse, data: Data), Error>) in 
    switch result {
    case let .success(success):
        handleResponse(success.response, data: success.data)
    case let .error(error):
        handleError(error)
    }
}

自定义字符串插值

SE-0228大大改进了Swift的字符串插值系统,使其更高效,更灵活。

主要用法就是在String.StringInterpolation的拓展里添加自定义的插值方法,比如加一个格式化字符串的方法:

extension String.StringInterpolation {
    public mutating func appendInterpolation(_ number: Double?, format formatString: String) {
        if let number = number{
            return appendLiteral(String(format: formatString, number))
        } else {
            return appendLiteral("nil")
        }
    }
}

然后就可以在字符串中使用自定义的插值方法

let number = 123.666
print("Hello, number is \(number")
print("Hello, number is \(number, format:"%.2f")")
print("Hello, number is \(nil, format:"%.2f")")
============================================================================
Hello, number is 123.666
Hello, number is 123.67
Hello, number is nil

所有字符串的处理、统计、添加html属性等操作都可以直接通过新的插值方式实现。

Dynamically callable types(动态可调用类型)

SE-0216@dynamicCallable为Swift 添加了一个新属性,它带来了将类型标记为可直接调用的功能,支持应用于结构,枚举,类和协议,但扩展不可以。

如果需要添加@dynamicCallable属性, 就必须要实现以下方法中的一个或者两个:

// 不需要指定参数名
func dynamicallyCall(withArguments args: [Int]) -> Double
// 指定参数名的方法
func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Int>) -> Double

对比Swift 5之前的定义和调用方式:

// 定义方式
struct RandomNumberGenerator {
    func generate(numberOfZeroes: Int) -> Double {
        let maximum = pow(10, Double(numberOfZeroes))
        return Double.random(in: 0...maximum)
    }
}

// 调用方式
let random = RandomNumberGenerator()
let num = random.generate(numberOfZeroes: 2)

使用Swift 5 的@dynamicCallable属性

// 定义方式
@dynamicCallable
struct RandomNumberGenerator {
    func dynamicallyCall(withArguments args: [Int]) -> Double {
        let numberOfZeroes = Double(args.first ?? 0)
        let maximum = pow(10, numberOfZeroes)
        return Double.random(in: 0...maximum)
    }
}

// 调用方式
let random = RandomNumberGenerator()
let num = random(2) // random(2)等同于random.dynamicallyCall(withArguments: [2])

处理未来添加的枚举类型

SE-0192 在枚举新增加一个 @unknown 属性,用于区分两种稍有不同的情况:1. default 的 case 里面处理所有其他的 case;2. 需要单独处理所有 case,只有真正不符合所有 case,才会进入 default提示。比如:

enum PasswordError: Error {
    case short
    case obvious
    case simple
}

func showOld(error: PasswordError){
    switch error {
        case .short:
        print("Your password was too short.")
        case .obvious:
        print("Your password was too obvious.")
        default:
        print("Your password was too simple.")
    }
}

上面代码假如我们再加个 case old,执行代码时它会自动进入到default分支,这是显然是,因为这个密码是一个旧密码而不是密码太简单,这时候可以用 @unknown,如下

func showNew(error: PasswordError) {
    switch error {
    case .short:
        print("Your password was too short.")
    case .obvious:
        print("Your password was too obvious.")
    @unknown default:
        print("Your password wasn't suitable.")
    }
}

这时Xcode会产生一个警告⚠️Switch must be exhaustive. Do you want to add missing cases?,因为新增了case old ,switch没有明确地处理每一个分支。

可变参数

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

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

Swift 5之后, 上述定义改成数组参数, 而不是可变参数, 如下:

enum X {
    case foo(bar: [Int]) 
} 

func baz() -> X {
    return .foo(bar: [0, 1, 2, 3]) 
} 

try?可选值嵌套

SE-0230try?返回的多层 optional value嵌套值变成只有一层,无论有多少可嵌套的可选值,返回值永远只是一个可选值。先看一下这个例子

struct User {
    var id: Int

    init?(id: Int) {
        if id < 1 {
            return nil
        }

        self.id = id
    }

    func getMessages() throws -> String {
        // complicated code here
        return "No messages"
    }
}

let user = User(id: 1)
let messages = try? user?.getMessages()

Swift4.2及其之前的版本中,上面返回的是一个String??,2层嵌套的可选值,如果有多层嵌套处理起来也是相当更麻烦。在Swift 5中就完美的解决了这个问题,如果当前值是可选的,那么try?将不会将值包装在可选值中,因此最终结果只是一个String?

整型倍数判断

SE-0225为整数类型添加了一个方法isMultiple(of:),可以检查一个整数是否为另一个整数的倍数。

let rowNumber = 4

if rowNumber.isMultiple(of: 2) {
    print("Even")
} else {
    print("Odd")
}

count函数

SE-0220,在Swift之前的版本中,有一个函数filter可以过滤出数组中符合条件的的元素,组成一个新的数组,在Swift 5中新增了一个函数count(where:), 可以获取数组中符合条件的元素的个数。

let arr = [1, 2, 34, 5, 6, 7, 8, 12, 45, 6, 9]

let filter = arr.filter({ $0 > 10 })
print(filter)  // [34, 12, 45]

let count = arr.count(where: { $0 > 10 })
print(count)   // 3

compactMapValues过滤字典元素

SE-0218compactMapValues()为字典添加了一种新方法,将Swift4.x的版本有两个函数compactMapmapValue结合在一起。

  • compactMap: 返回一个操作后得到的新的数组, 类似flatMap
  • mapValues: 字典中的函数, 对字典的value值执行操作, 返回改变value后的新的字典
let times = [
    "first": 2,
    "second": 43,
    "three": 12,
    "four": 3
]

let compact = times.compactMap({ $0.value > 10 }) 
// [true, false, true, false]

let mapValues = times.mapValues({ $0 + 2 }) 
// ["second": 45, "first": 4, "three": 14, "four": 5]

compactMapValues是将上述两个方法的功能合并在一起, 返回一个对value操作后的新字典, 并且自动过滤不符合条件的键值对,下面的例子展示把非整形类型的键值对过滤掉:

let times = [
    "Hudson": "38",
    "Clarke": "42",
    "Robinson": "35",
    "Hartis": "DNF"
]
let finishers1 = times.compactMapValues { Int($0) }
// ["Clarke": 42, "Robinson": 35, "Hudson": 38]

参看文献:

  1. swift-evolution
  2. Swift5.0新特性
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容