Swift 5新特性

在Xcode10.2终于可以用上Swift5了,这次发布带来了ABI(应用程序机器二元码界面(英语:application binary interface,缩写为ABI)是指两程

序模块间的接口;通常其中一个程序模块会是库或操作系统所提供的服务,而另一边的模块则是用户所运行的程序。)的稳定性以及一些期待已久的新特性。

Swift 5是与Swift 4.2是源代码兼容的,但与早期的Swift版本二进制不兼容。不过,由于ABI的稳定性,未来的版本将兼容Swift 5。

整数的倍数

在4.2中,用余数运算符确定一个数字是否是另一个数字的倍数需要这么做:

let firstNumber = 4
let secondNumber = 2
//你需要确保secondNumber不会是0,不然%会抛出异常
if secondNumber != 0 && firstNumber % secondNumber == 0 {
  print("\(secondNumber) * \(firstNumber / secondNumber) = \(firstNumber)")
}

但是在Swift5中可以这么写,即使传入的参数是0:

if firstNumber.isMultiple(of: secondNumber) {
  print("\(secondNumber) * \(firstNumber / secondNumber) = \(firstNumber)")
}

转义字符

Swift 4.2在字符串中使用反斜杠和引号来表达转义序列:

let escape = "You use escape sequences for \"quotes\"\\\"backslashes\" in Swift 4.2."
let multiline = """
                You use escape sequences for \"\"\"quotes\"\"\"\\\"\"\"backslashes\"\"\"
                on multiple lines
                in Swift 4.2.
                """

Swift 5添加了原始字符串。您可以在字符串的开头和结尾添加#,这样您就可以使用反斜杠和引号而不会出现问题:

let raw = #"You can create "raw"\"plain" strings in Swift 5."#
let multiline = #"""
                You can create """raw"""\"""plain""" strings
                on multiple lines
                in Swift 5.
                """#
let hashtag = ##"You can use the Swift "hashtag" #swift in Swift 5."##

新字符属性

在4.2中,如果需要计算字符串中数字的个数:

let id = "ID10"
var digits = 0
id.forEach { digits += Int(String($0)) != nil ? 1 : 0 }
print("Id has \(digits) digits.")

Swift5可以这么写:

id.forEach { digits += $0.isNumber ? 1 : 0 }

新的计算Unicode数量属性

4中如果需要计算字符串中unicode的字母数量,需要通过检查每个字符的unicode标量是表示小写字母还是大写字母,计算username有多少个字母。

let username = "bond007"
var letters = 0
username.unicodeScalars.forEach { 
  letters += (65...90) ~= $0.value || (97...122) ~= $0.value ? 1 : 0
}
print("Username has \(letters) letters.")

在Swift5可以这样写:

username.unicodeScalars.forEach { letters += $0.properties.isAlphabetic ? 1 : 0 }

移除子序列

Swift 4.2从序列移除子序列:

extension Sequence {
  func remove(_ s: String) -> SubSequence {
    guard let n = Int(s) else {
      return dropLast()
    }
    return dropLast(n)
  }
}

let sequence = [5, 2, 7, 4]
sequence.remove("2") // [5, 2]
sequence.remove("two") // [5, 2, 7]

在本例中,如果s是Int或最后一个元素,则remove(_:)从序列中删除最后n个元素。

Swift 5用序列中的具体类型替换子序列:

extension Sequence {
  func remove(_ s: String) -> [Element] {
    guard let n = Int(s) else {
      return dropLast()
    }
    return dropLast(n)
  }
}

字典比较

Swift 4.2使用mapValues, filter和reduce从字典中过滤nil:

let students = ["Oana": "10", "Nori": "ten"]
let filterStudents = students.mapValues(Int.init)
  .filter { $0.value != nil }
  .mapValues { $0! }
let reduceStudents = students.reduce(into: [:]) { $0[$1.key] = Int($1.value) }

这段代码使用带有filter或reduce的map值来从学生中确定成绩有效的学生。这两种方法都需要多个字典传递,并使代码复杂化。

Swift5可以这样写,一行代码搞定:

let mapStudents = students.compactMapValues(Int.init)

数值协议更新

4中对象如何想要实现数值运算:

// 1
struct Vector {
  let x, y: Int
  
  init(_ x: Int, _ y: Int) {
    self.x = x
    self.y = y
  }
}

// 2
extension Vector: ExpressibleByIntegerLiteral {
  init(integerLiteral value: Int) {
    x = value
    y = value
  }
}

// 3
extension Vector: Numeric {
  var magnitude: Int {
    return Int(sqrt(Double(x * x + y * y)))
  }  

  init?<T>(exactly value: T) {
    x = value as! Int
    y = value as! Int
  }
  
  static func +(lhs: Vector, rhs: Vector) -> Vector {
    return Vector(lhs.x + rhs.x, lhs.y + rhs.y)
  }
  
  static func +=(lhs: inout Vector, rhs: Vector) {
    lhs = lhs + rhs
  }
  
  static func -(lhs: Vector, rhs: Vector) -> Vector {
    return Vector(lhs.x - rhs.x, lhs.y - rhs.y)
  }
  
  static func -=(lhs: inout Vector, rhs: Vector) {
    lhs = lhs - rhs
  }
  
  static func *(lhs: Vector, rhs: Vector) -> Vector {
    return Vector(lhs.x * rhs.y, lhs.y * rhs.x)
  }
  
  static func *=(lhs: inout Vector, rhs: Vector) {
    lhs = lhs * rhs
  }
}

// 4
extension Vector: CustomStringConvertible {
  var description: String {
    return "(\(x) \(y))"
  }
}

然而在Swift5中只需要实现向量的AdditiveArithmetic协议:

extension Vector: AdditiveArithmetic {
  static var zero: Vector {
    return Vector(0, 0)
  }
  
  static func +(lhs: Vector, rhs: Vector) -> Vector {
    return Vector(lhs.x + rhs.x, lhs.y + rhs.y)
  }
  
  static func +=(lhs: inout Vector, rhs: Vector) {
    lhs = lhs + rhs
  }
  
  static func -(lhs: Vector, rhs: Vector) -> Vector {
    return Vector(lhs.x - rhs.x, lhs.y - rhs.y)
  }
  
  static func -=(lhs: inout Vector, rhs: Vector) {
    lhs = lhs - rhs
  }
}

枚举

Swift 4.2不能正确处理新的枚举,在下面代码中如果新增情况下,是执行default:

// 1
enum Post {
  case tutorial, article, screencast, course
}

// 2
func readPost(_ post: Post) -> String {
  switch post {
    case .tutorial:
      return "You are reading a tutorial."
    case .article:
      return "You are reading an article."
    default:
      return "You are watching a video."
  }
}

// 3
let screencast = Post.screencast
readPost(screencast) // "You are watching a video."
let course = Post.course
readPost(course) // "You are watching a video."

然而Swift 5会增加枚举用例:

func readPost(_ post: BlogPost) -> String {
  switch post {
    case .tutorial:
      return "You are reading a tutorial."
    case .article:
      return "You are reading an article."
    @unknown default:
      return "You are reading a blog post."
  }
}

readPost(screencast) // "You are reading a blog post."
readPost(course) // "You are reading a blog post."
readPost(podcast) // "You are reading a blog post."

在这段代码中,只需要将默认值标记为@unknown, Swift就会警告你这个切换并不彻底。

标准库新增Result

// 1
enum ConnectionError: Error {
  case noNetwork, noDatabase
}

// 2
let networkSuccess = Result<String, ConnectionError>.success("Network connected!")
let databaseSuccess = Result<String, ConnectionError>.success("Database connected!")
let networkFailure = Result<String, ConnectionError>.failure(.noNetwork)
let databaseFailure = Result<String, ConnectionError>.failure(.noDatabase)
let sameSuccess = networkSuccess == databaseSuccess
let sameFailure = networkFailure == databaseFailure
let success: Set = [networkSuccess, databaseSuccess]
let failure: Set = [networkFailure, databaseFailure]
let successDictionary = [
  networkSuccess: try! networkSuccess.get(),
  databaseSuccess: try! databaseSuccess.get()
]
let failureDictionary = [
  networkFailure: ConnectionError.noNetwork,
  databaseFailure: ConnectionError.noDatabase

Conforming Never to Equatable and Hashable

let alwaysSucceeds = Result<String, Never>.success("Network connected!")
let neverFails = Result<String, Never>.success("Database connected!")
let alwaysFails = Result<Never, ConnectionError>.failure(.noNetwork)
let neverSucceeds = Result<Never, ConnectionError>.failure(.noDatabase)
let sameValue = alwaysSucceeds == neverFails
let sameError = alwaysFails == neverSucceeds
let alwaysSuccess: Set = [alwaysSucceeds, neverFails]
let alwaysFailure: Set = [alwaysFails, neverSucceeds]
let alwaysSuccessDictionary = [
  alwaysSucceeds: try! alwaysSucceeds.get(),
  neverFails: try! neverFails.get()
]
let alwaysFailureDictionary = [
  alwaysFails: ConnectionError.noNetwork,
  neverSucceeds: ConnectionError.noDatabase
]

Dynamically Callable Types

Swift 5定义了与Python或Ruby等脚本语言互操作的动态可调用类型

// 1
@dynamicCallable
class DynamicFeatures {
  // 2
  func dynamicallyCall(withArguments params: [Int]) -> Int? {
    guard !params.isEmpty else {
      return nil
    }
    return params.reduce(0, +)
  }
  
  func dynamicallyCall(withKeywordArguments params: KeyValuePairs<String, Int>) -> Int? {
    guard !params.isEmpty else {
      return nil
    }
    return params.reduce(0) { $1.key.isEmpty ? $0 : $0 + $1.value }
  }
}

// 3
let features = DynamicFeatures()
features() // nil
features(3, 4, 5) // 12
features(first: 3, 4, second: 5) // 8

实现说明:

  1. 在DynamicFeatures中声名@dynamicCallable为动态调用类型
  2. 实现该协议的 dynamicallyCall(withArguments:) & dynamicallyCall(withKeywordArguments:)方法
  3. 正常调用features,编译器会自动调用内部方法实现

降维嵌套可选

如果我们在4.2中使用try?,division会变成恶心的 Int??,然后需要我们解包两次:

extension Int {
  // 1
  enum DivisionError: Error {
    case divisionByZero
  }
  
  // 2
  func divideBy(_ number: Int) throws -> Int {
    guard number != 0 else {
      throw DivisionError.divisionByZero
    }
    return self / number
  }
}

// 3
let number: Int? = 10
let division = try? number?.divideBy(2)
if let division = division, 
   let final = division {
  print(final)
}

Swift5却不需要这样了,try?不再会创建嵌套可选链,所以只需要解包一次就好:

if let division = division {
  print(division)
}

统一Key Paths

在4.2中,可以使用.self取获取和更新值:

class Tutorial {
  let title: String
  let author: String
  init(title: String, author: String) {
    self.title = title
    self.author = author
  }
}

var tutorial = Tutorial(title: "What's New in Swift 5.0?", author: "Cosmin Pupaza")
tutorial.self = Tutorial(title: "What's New in Swift 5?", author: "Cosmin Pupăză")

Swift5中新增了通过identity key paths方式使用 \.self 获取设置该对象:

tutorial[keyPath: \.self] = Tutorial(
  title: "What's New in Swift 5?",
  author: "Cosmin Pupăză")

枚举的可变参数

在4.2中如果枚举为可变参数是这么写的:

enum BlogPost {
  case tutorial(_: String...)
  case article(_: String...)
}

在Swift5中需要使用Array替换:

enum BlogPost {
  case tutorial([String])
  case article([String])
}

废弃String Index Encoded Offsets

在Swift4.2中使用UTF-16编码,调用 encodedOffset 会返回UTF-16字符串的偏移量:

let swiftVersion = "Swift 4.2"
let offset = swiftVersion.endIndex.encodedOffset

但是在Swift5中,如果使用UTF-8时这个方法是不管用的,使用utf16Offset(in:)替换:

let swiftVersion = "Swift 5"
let offset = swiftVersion.endIndex.utf16Offset(in: swiftVersion)

写在最后 : Swift 5在Swift 4.2的基础上增加了很多新特性,也使得ABI更加稳定。这是发展过程中的一个重要里程碑,从现在开始将会有更少的变化。Swift CHANGELOG & Swift standard library diffs 可以获取更多细节。

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