美文网首页iOS 开发相关swift 文章收集
Swift 5 新增了什么?(下)

Swift 5 新增了什么?(下)

作者: 全站工程师 | 来源:发表于2019-04-01 00:56 被阅读767次

书接前文(Swift 5 新增了什么?(上))继续介绍 Swift 5 的新增特性。

字符串插值更新

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 进行插值 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(_:) 将字符串字面量和插补值添加到 interpolation
  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. 为了不遗漏任何枚举,给 switch 增加了 default 分支;
  3. default 分支处理 .screencast.course,因为它们都属于视频内容。
    如果后续在这段代码里增加 podcasts(播客)枚举成员:
enum Post {
  case tutorial, article, podcast, screencast, course
}

let podcast = Post.podcast
readPost(podcast) // "You are watching a video."

这段代码里, .podcast 也会由 default 处理,尽量播客不是视频。Swift 4.2 不会产生任何警告因为这个 switch 是能完备处理所有的枚举成员。
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."

这段代码中 default@unknown标识,因此 Swift 会警告switch 处理不完备。

标准库增加 Result

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. 比较发起连接的结果,将它们加入到 Set,你可以将这些 Set 作为字典的键(key),因为 Result 实现了 EquatableHashable

使 Never 遵守 EquatableHashable

Swift 5 使 Never 遵守 EquatableHashable 协议[ 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. 为了遵从 @dynamicCallable 实现 dynamicallyCall(withArguments:)anddynamicallyCall(withKeywordArguments:)
  3. 用正常语法调用 features,编译器将转化为对dynamicallyCall(withArguments:)anddynamicallyCall(withKeywordArguments:) 的调用。

Swift 包管理器更新

Swift 5 为包管理器增加了一些新特性:

平台部署设置

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

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

可以使用 macOS()iOS()tvOS()watchOS()package 设置最低需要的平台版本。

目标构建设置

Swift 5 在 Package.Swift 中声明针对目标(Target)的构建设置,他们定制包管理器在目标构建期间调用构建工具的方式[ SE-0238 ].。

依赖镜像

Swift 5 的包管理器带有依赖镜像功能[ 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 还添加了其他一些急需的功能和改进:

Codable 支持范围

Swift 5 使 Codable 增加了对范围的支持[ SE-0239 ]:

let temperature = 0...10
let encoder = JSONEncoder()
let data = try! encoder.encode(temperature)
let decoder = JSONDecoder()
let temperatureRange = try! decoder.decode(ClosedRange<Int>.self, from: data)

因为 Swift 5 中 ClosedRange 默认已经实现了 Codable,所以代码中可以通过 JSONEncodertemperature 编码,并可以通过 JSONDecoder 解码。

展开多层的 Optional

Swift 4.2 使用 try? 创建多层 Optional:

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)
}

解读一下这段代码:

  1. 使用 DivisionError 扩展 Int
  2. divideBy(_:)number 为 0 时抛出 .divisionByZero
  3. 因为 divisionInt?? ,因此要解包装两次。

到了 Swift 5 处理就很简单了[ SE-0230 ]:

if let division = division {
  print(division)
}

因为 try? 在 Swift 5 里不再创建多层 optional,所以 division 在类型是 Int?,也就只用一次解包装了。

移除 Collection 的定制点

Swift 4.2 中可以访问 Collection 的定制点:

extension Array {
  var first: Element? {
    return !isEmpty ? self[count - 1] : nil
  }
  
  var last: Element? {
    return !isEmpty ? self[0] : nil
  }
}

let names = ["Cosmin", "Oana", "Sclip", "Nori"]
names.first // "Nori"
names.last // "Cosmin"

这段代码中,first 返回 names 数组里的最后一个名字,而 last 则返回第一个。如此将两个计算属性的行为都与预期不符,因此 Swift 5 从集合的协议中删除了它们的定制点 [ SE-0232 ]。

Key Path 标识

Swift 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ă")

这段代码使用 .self 一次性修改 tutorialtitleauthor
Swift 5 增加了 key path 标识 来修改成员值:

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

这段代码使用 \.self 来更新 tutorial

字面量初始化强制通过

在 Swift 5 中,只要类型遵守字面量协议,它的字面量初始化函数会强制将字面量转换为该类型[ SE-0213 ]:

let value = UInt64(0xFFFF_FFFF_FFFF_FFFF)

在 Swift 4.2 中这行代码会在编译时报溢出错误(Integer literal '18446744073709551615' overflows when stored into 'Int’),而 Swift 5 则会正确处理。

构建配置更新

Swift 4.2 在编译条件中使用 >=

let favoriteNumber = 10
var evenNumber = true

#if !swift(>=5)
  evenNumber = favoriteNumber % 2 == 0
#else 
  evenNumber = favoriteNumber.isMultiple(of: 2)
#endif

#if !compiler(>=5)
  evenNumber = favoriteNumber % 2 == 0  
#else
  evenNumber = favoriteNumber.isMultiple(of: 2)
#endif

这些条件检查 Swift 版本是否大于等于 5,并在条件满足时编译相应那些代码。

Swift 5 增加 < 使得条件描述更清晰[ SE-0224 ]:

#if swift(<5)
  evenNumber = favoriteNumber % 2 == 0   
#else
  evenNumber = favoriteNumber.isMultiple(of: 2)  
#endif

#if compiler(<5)
  evenNumber = favoriteNumber % 2 == 0 
#else
  evenNumber = favoriteNumber.isMultiple(of: 2)   
#endif

对具有关联值的枚举使用可变参数

对于在 Swift 4.2 中具有关联值的枚举情况,可以使用可变参数:

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

使用 String... 可设置教程 (tutorial) 和文章 (article) 详细信息。 这在 Swift 5 中是不可行的,取而代之的是使用数组:

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

弃用字符串索引编码偏移量

Swift 4.2字符串使用UTF-16编码。 因此 encodedOffset 将返回 UTF-16 字符串的偏移量:

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

这里可以获得 swiftVersionendIndex 的偏移量。 但对 Swift 5 中使用的 UTF-8 字符串编码不起作用,因此 Swift 5用 utf16Offset(in:) 替换encodedOffset 来处理这两种情况 [ SE-0241 ]:

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

新的指针方法

Swift 5 为 Sequence 增加了 withContiguousStorageIfAvailable(_:) ,以及为MutableCollection 增加了 withContiguousMutableStorageIfAvailable(_:),以便为协议扩展中的 withUnsafeBufferPointer(_:)withUnsafeMutableBufferPointer(_:) 提供通用实现[ SE-0237 ]。

SIMD 向量

Swift 5 将处理器的 SIMD 类型操作添加到标准库中,为 SIMD 向量和矩阵提供底层支持。 该特性也简化了<simd/simd.h> 中的 Objective-C、C 和 C++实现。

SIMD,即 Single Instruction, Multiple Data,一条指令操作多个数据.是CPU基本指令集的扩展,用于并行处理数据以提高执行性能。

后记

Swift 5 向 Swift 4.2添加了许多很酷的功能,并使语言ABI稳定,这是该语言发展过程中的一个重要里程碑,因为从现在起语言的变化将会减少。

你可以在官方 Swift 更改日志Swift 标准库差异中阅读更多有关此版本 Swift 更新的更多信息。

你还可以查看 Swift 发展提案,了解下一个 Swift 版本中会发生什么变化。 在这里,你可以针对当前正在审核的提案提供反馈,甚至还可以自行提交提案!

相关文章

网友评论

    本文标题:Swift 5 新增了什么?(下)

    本文链接:https://www.haomeiwen.com/subject/ssopbqtx.html