专栏名称: CainLuo
iOS
目录
相关文章推荐
CFC农产品研究  ·  【会议邀请】油脂油料月月谈(第22期) ·  2 小时前  
CFC农产品研究  ·  中央一号文件发布 2025/02/24 ·  15 小时前  
兵团零距离  ·  万物竞发争春来 | ... ·  16 小时前  
兵团零距离  ·  万物竞发争春来 | ... ·  16 小时前  
寿光报  ·  火爆!远销40个国家…… ·  昨天  
寿光报  ·  火爆!远销40个国家…… ·  昨天  
微观三农  ·  “桂字号”农业品牌 | 容县沙田柚 ... ·  2 天前  
51好读  ›  专栏  ›  CainLuo

Swift 5有什么新功能?

CainLuo  · 掘金  ·  · 2019-08-07 12:25

正文

阅读 44

Swift 5有什么新功能?

好消息! Swift 5 最终在 Xcode 10.2 中可用! 此版本带来了 ABI**的稳定性,并通过一些期待已久的功能改进了语言。

注意: 当前版本为Swift 5, iOS 12, Xcode 10

在本教程中,您将了解 Swift 5 中最重要的更改. Swift 5 需要 Xcode 10.2 ,因此请确保在开始之前已安装好。

入门

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

ABI稳定性支持使用不同 Swift 版本编译的应用程序和库之间的二进制兼容性。 Swift 标准库和运行时嵌入在操作系统中,因此应用程序不会在任何平台上分发自己的库副本。这导致更好的工具解耦和 OS 集成。

您还需要 ABI 稳定性来分发跨多个 Swift 版本的二进制框架。这需要模块格式稳定性,这可以稳定包含编译器的框架公共接口表示的模块文件。

您将在本教程的每个部分中找到类似**[SE-0001]**的 Swift Evolution 建议编号。您可以浏览每个提案链接,详细了解每项新变化。

遵循本教程的最佳方法是在操场上试用新功能。

启动 Xcode 10.2 并选择 File ▸ New ▸ Playground 。将平台设置为 iOS ,将模板设置为 Blank 。命名并将其保存在您想要的任何地方开始的时候了!

注意:需要快速提醒一下 Swift 4.2 亮点? 查看 Swift 4.2 教程: Swift 4.2中有哪些新功能?

语言改进

Swift 5 中有许多语言功能,例如动态可调用类型,处理未来的枚举等等。

多种Integer的测试

Swift 4.2 中,您可以使用余数运算符确定数字是否是另一个的倍数:

let firstNumber = 4
let secondNumber = 2
if secondNumber != 0 && firstNumber % secondNumber == 0 {
  print("\(secondNumber) * \(firstNumber / secondNumber) = \(firstNumber)")
}
复制代码

这段代码的工作原理:

  1. 检查 secondNumber 是否为 0
  2. 检查将 firstNumber 除以 secondNumber 会返回 0 的余数。
  3. 执行除法运算。

您必须检查 secondNumber 是否为 0 ,因为 运算符会抛出错误。

Swift 5 通过向 BinaryInteger SE-0225 添加 isMultiple(of :) 来简化这一过程:

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

即使你将参数传递给 0 isMultiple(of :) 也可以工作,结果代码更清晰。

逃避原始字符串

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 添加原始字符串。 您在字符串的开头和结尾添加 # ,这样您就可以使用反斜杠和引号而不会出现问题。 SE-0200

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 track = "Nothing Else Matters"
print(#"My favorite tune\song is \#(track)."#)
复制代码

在某些情况下,您需要在字符串的开头和结尾使用多个 #

let hashtag = ##"You can use the Swift "hashtag" #swift in Swift 5."##
复制代码

在上面的代码中,您在 hashtag 的开头和结尾添加 ## ,以便您可以在字符串中表示 # 。 字符串开头使用的#s数必须与其末尾的数字相匹配。

Swift 4.2 中,您可以在正则表达式中转义反斜杠,如下所示:

// 1
let versions = "3 3.1 4 4.1 4.2 5"
let range = NSRange(versions.startIndex..., in: versions)
// 2
let regex = try! NSRegularExpression(pattern: "\\d\\.\\d")
// 3
let minorVersions = regex.matches(in: versions, range: range)
// 4
minorVersions.forEach { print(versions[Range($0.range, in:  versions)!]) }
复制代码

以下是此代码的工作原理:

  1. 声明版本并定义覆盖整个字符串的范围。
  2. 定义一个匹配版本中所有次要Swift版本的正则表达式。
  3. 使用 matches(in: options: range:) 确定次要版本范围。
  4. 使用范围从版本中获取次要版本。

Swift 5 使用原始字符串简化了正则表达式:

let regex = try! NSRegularExpression(pattern: #"\d\.\d"#)
复制代码

在此代码中,您使用反斜杠数量的一半来编写正则表达式,因为您不需要在原始字符串中转义反斜杠。

图片

注意:需要有关正则表达式如何在 Swift 中工作的更多详细信息? 查看正则表达式教程: 正则表达式简介

使用新字符属性

在处理字符时, Swift 4.2 需要常见任务的变通方法:

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

在此代码中,首先将每个字符转换为 String ,然后再转换为 Int ,以确定 id 的位数。

但是, Swift 5 Character 添加了属性,使角色更易于使用 SE-0221

id.forEach { digits += $0.isNumber ? 1 : 0 }
复制代码

在这种情况下,您使用 isNumber 来检查每个字符是否都是数字。 查看您可以使用的其他属性的提案。

使用新的Unicode标量属性

Swift 4.2 中,您为 unicode 标量实现了文本处理算法,如下所示:

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.")
复制代码

在此代码中,您可以通过检查每个字符的 unicode 标量是代表小写字母还是大写字母来计算用户名的字母数。

Swift 5 unicode 标量添加了属性,简化了文本处理 SE-0211

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

在此代码中,您使用 isAlphabetic 检查每个字符是否为数字。 链接的提案显示您可以检查的所有属性。

删除子序列

Swift 4.2 Sequence 自定义点返回 SubSequence ,如下所示:

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 用序列中的具体类型替换 SubSequence SE-0234

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

在此代码中, remove(_ :) 返回 [Element] ,因为 dropLast() dropLast(_ :) 返回 [Element]

字典更新

Swift 5 为词典带来了期待已久的改进:

压缩字典

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 mapValues 来确定学生的有效成绩。 这两种方法都需要多次字典传递并使代码复杂化。

Swift 5 使用 compactMapValues(_ :) 来获得更有效的解决方案 SE-0218

let mapStudents = students.compactMapValues(Int.init)
复制代码

它以更少的代码行完成同样的事情,整洁!

重命名字典文字

Swift 4.2 使用 DictionaryLiteral 来声明字典,如下所示

let pets: DictionaryLiteral = ["dog": "Sclip", "cat": "Peti"]
复制代码

DictionaryLiteral 不是字典或文字。 这是一个键值对列表。

Swift 5 DictionaryLiteral 重命名为 KeyValuePairs SE-0214

let pets: KeyValuePairs = ["dog": "Sclip", "cat": "Peti"]
复制代码

数字协议更新

Swift 4.2 为向量实现数值:

// 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))"
  }
}
复制代码

以下是此代码的工作原理:

  1. Vector 声明 x y init(_: _ :)

  2. 实现 init(integerLiteral :) 以使 Vector 符合 ExpressibleByIntegerLiteral 作为数字一致性的要求。

  3. 通过定义向量的大小,声明 init(exactly:) 并实现 +(lhs: rhs :) +=(lhs: rhs :) -(lhs: rhs :) -=(lhs: rhs: ) *(lhs:rhs :) * =(lhs: rhs :)

  4. 实现描述以使 Vector 符合 CustomStringConvertible

上面的代码使您可以轻松地使用向量:

var first = Vector(1, 2) // (1,2)
let second = Vector(3, 4) // (3,4)
let third = first + second // (4,6)
first += second // (4,6)
let fourth = first - second // (1,2)
first -= second // (1,2)
复制代码

Swift 5 实现了向量的 AdditiveArithmetic ,因为您无法定义 2D 向量的叉积 SE-0233 。 它不需要 ExpressibleByIntegerLiteral 一致性:

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
  }
}
复制代码

在这段代码中,你通过定义零并实现 +(lhs: rhs :) +=(lhs: rhs :) -(lhs: rhs :) - =(lhs: rhs :) 来使 Vector 符合 AdditiveArithmetic

图片

注意:想要了解有关 Swift 中运算符重载的更多信息? 查看运算符重载教程: 在Swift中重载自定义运算符

字符串插值更新

Swift 4.2 通过插入段实现字符串插值:

let language = "Swift"
let languageSegment = String(stringInterpolationSegment: language)
let space = " "
let spaceSegment = String(stringInterpolationSegment: space)
let version = 4.2
let versionSegment = String(stringInterpolationSegment: version)
let string = String(stringInterpolation: languageSegment, spaceSegment, versionSegment)
复制代码

在此代码中,编译器首先包装每个文本段,然后使用 init(stringInterpolationSegment :) 插入一个。 然后,它用 init(stringInterpolation :) 将所有段包装在一起。

Swift 5 采用了完全不同的方法 SE-0228 :

// 1
var interpolation = DefaultStringInterpolation(
  literalCapacity: 7,
  interpolationCount: 1)
// 2
let language = "Swift"
interpolation.appendLiteral(language)
let space = " "
interpolation.appendLiteral(space)
let version = 5
interpolation.appendInterpolation(version)
// 3
let string = String(stringInterpolation: interpolation)
复制代码

这是代码的作用:

  1. 使用特定容量和插值计数定义 DefaultStringInterpolation 实例。
  2. 调用 appendLiteral(_ :) appendInterpolation(_ :) 将文字和插值添加到插值中。
  3. 通过调用 init(stringInterpolation :) 生成最终的插值字符串。

处理未来的查点案件

Swift 4.2 无法正确处理新的枚举案例,如下所示:

// 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."
复制代码

以下是上面代码中发生的情况:

  1. 在网站上定义所有类型的博客文章。
  2. 要使交换机无穷尽,请添加默认值。
  3. 由于截屏视频和课程是视频,因此默认处理 .screencast .course

以下是处理播客在 Swift 4.2 中的工作原理:

enum Post {
  case tutorial, article, podcast, screencast, course
}

let podcast = Post.podcast
readPost(podcast) // "You are watching a video."
复制代码

在此代码中,您使用默认处理 .podcast ,即使播客不是视频。 Swift 4.2 不会对此发出警告,因为该开关是详尽无遗的。

Swift 5 处理添加的枚举案例 SE-0192

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警告您切换并非详尽无遗。 默认处理 .screencast .course .podcast ,因为截屏视频,课程和播客是博客文章。

图片

将结果添加到标准库

Swift 5 Result 添加到标准库 SE-0235

// 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
]
复制代码

以下是此代码的工作原理:

  1. 声明最常见的连接错误。
  2. 比较连接结果,将它们添加到集合中。 您使用这些集作为字典的键,因为 Result 实现了 Equatable Hashable

从不符合Equatable和Hashable

Swift 5 符合 Never to Equatable Hashable SE-0215

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
]
复制代码

在此代码中,您定义始终返回值或错误的连接结果,比较它们,将它们添加到集合并将它们用作字典键。

动态可调用类型

Swift 5 定义了可与脚本语言(如 Python Ruby )互操作的动态可调用类型 SE-0216

// 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. 要使 DynamicFeatures 符合**@dynamicCallable**,请实现 dynamicCallwithArguments : )和 dynamicCall(withKeywordArguments :)

  3. 使用普通语法调用功能,编译器调用 dynamicCall(withArguments :) dynamicCall(withKeywordArguments :)

Swift Package Manager更新

Swift 5 Swift Package Manager 添加了一些功能:

平台部署设置

Swift 5 允许您在 Package.swift SE-0236 中定义所需的最低平台部署目标版本:

let package = Package(name: “Package”, platforms: [
  .macOS(.v10_14), 
  .iOS(.v12),
  .tvOS(.v12), 
  .watchOS(.v5)
])
复制代码

您可以在 SupportedPlatform 中使用 macOS() iOS() tvOS() watchOS() 来设置包所需的最低平台版本。

目标构建设置

Swift 5 Package.swift 中声明了特定于目标的构建设置。 它们定制包管理器在目标构建期间如何调用构建工具 SE-0238

依赖镜像

Swift 5 Swift Package Manager SE-0219 带来了依赖镜像。

swift package config set-mirror --package-url <package> --mirror-url <mirror>
复制代码

即使原始源不可用或被删除,镜像也允许您访问依赖项。

set-mirror 使用镜像更新依赖项,后者替换所有其他镜像。

使用 unset-mirror 从依赖项中删除镜像:

swift package config unset-mirror --package-url <package>
swift package config unset-mirror —mirror-url <mirror> 
swift package config unset-mirror --all
复制代码

其他的一些改进

Swift 5 还增加了一些其他急需的功能和改进:

制作可编码范围

Swift 5 增加了 Codable 对范围的一致性 SE-0239







请到「今天看啥」查看全文