好消息! Swift 5最终在Xcode 10.2中可用! 此版本带来了ABI的稳定性,并经过一些期待已久的功能改进了语言。git
注意: 当前版本为Swift 5, iOS 12, Xcode 10, 如转载本文章, 请联系做者, 并给出文章的源地址github
在本教程中,您将了解Swift 5中最重要的更改. Swift 5须要Xcode 10.2,所以请确保在开始以前已安装好。正则表达式
Swift 5与Swift 4.2兼容,但与早期的Swift版本不兼容。可是,因为ABI稳定性,将来版本将与Swift 5二进制兼容。算法
ABI稳定性支持使用不一样Swift版本编译的应用程序和库之间的二进制兼容性。 Swift标准库和运行时嵌入在操做系统中,所以应用程序不会在任何平台上分发本身的库副本。这致使更好的工具解耦和OS集成。express
您还须要ABI稳定性来分发跨多个Swift版本的二进制框架。这须要模块格式稳定性,这能够稳定包含编译器的框架公共接口表示的模块文件。swift
您将在本教程的每一个部分中找到相似**[SE-0001]**的Swift Evolution建议编号。您能够浏览每一个提案连接,详细了解每项新变化。api
遵循本教程的最佳方法是在操场上试用新功能。数组
启动Xcode 10.2并选择File ▸ New ▸ Playground。将平台设置为iOS,将模板设置为Blank。命名并将其保存在您想要的任何地方开始的时候了!app
注意:须要快速提醒一下Swift 4.2亮点? 查看Swift 4.2教程:Swift 4.2中有哪些新功能?框架
Swift 5中有许多语言功能,例如动态可调用类型,处理将来的枚举等等。
在Swift 4.2中,您可使用余数运算符肯定数字是不是另外一个的倍数:
let firstNumber = 4
let secondNumber = 2
if secondNumber != 0 && firstNumber % secondNumber == 0 {
print("\(secondNumber) * \(firstNumber / secondNumber) = \(firstNumber)")
}
复制代码
这段代码的工做原理:
您必须检查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的开头和结尾添加##
,以便您能够在字符串中表示#
。 字符串开头使用的#
必须与其末尾的数字相匹配。
在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)!]) }
复制代码
如下是此代码的工做原理:
matches(in: options: range:)
肯定次要版本范围。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来检查每一个字符是否都是数字。 查看您可使用的其余属性的提案。
在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))"
}
}
复制代码
如下是此代码的工做原理:
为Vector声明x,y和init(_: _ :)
。
实现init(integerLiteral :)
以使Vector符合ExpressibleByIntegerLiteral做为数字一致性的要求。
经过定义向量的大小,声明init(exactly:)
并实现+(lhs: rhs :)
,+=(lhs: rhs :)
, -(lhs: rhs :)
, -=(lhs: rhs: )
,*(lhs:rhs :)
,*=(lhs: rhs :)
。
实现描述以使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)
复制代码
这是代码的做用:
appendLiteral(_ :)
或appendInterpolation(_ :)
将文字和插值添加到插值中。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."
复制代码
如下是上面代码中发生的状况:
.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
]
复制代码
如下是此代码的工做原理:
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
复制代码
上面的代码以下:
将DynamicFeatures标记为**@dynamicCallable**以使其成为动态可调用类型。
要使DynamicFeatures符合**@dynamicCallable**,请实现dynamicCallwithArguments :
)和dynamicCall(withKeywordArguments :)
。
使用普通语法调用功能,编译器调用dynamicCall(withArguments :)
或dynamicCall(withKeywordArguments :)
。
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()
来设置Package所需的最低平台版本。
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:
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)
复制代码
您使用JSONEncoder对温度进行编码并使用JSONDecoder解码数据,由于默认状况下,范围在Swift 5中实现了Codable。
Swift 4.2使用**try?**建立嵌套的选项:
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)
}
复制代码
这是代码的做用:
divideBy(_ :)
若是number为0则抛出.divisionByZero
。Int??
。Swift 5以不一样的方式处理SE-0230:
if let division = division {
print(division)
}
复制代码
尝试? 在Swift 5中不会建立嵌套的选项,所以你须要解包一次,由于它是一个Int?
。
您能够从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"
复制代码
在此代码中,首先返回姓名的姓氏,最后返回数组的第一个元素。
两个计算属性都不能按预期工做,所以Swift 5从集合SE-0232中删除了它们的自定义点。
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一次性更改教程的标题和做者。
Swift 5为值访问添加了identity key路径SE-0227:
tutorial[keyPath: \.self] = Tutorial(
title: "What's New in Swift 5?",
author: "Cosmin Pupăză")
复制代码
在此代码中,您使用\.self
来更新教程。
在Swift 5中,若是类型符合文字协议SE-0213,则文字初始化程序将文字强制转换为其类型:
let value = UInt64(0xFFFF_FFFF_FFFF_FFFF)
复制代码
在Swift 4.2中,上面的代码行在编译时产生溢出错误。
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...获取教程和文章详细信息。 这在Swift 5中是不可能的,因此你应该使用数组:
enum BlogPost {
case tutorial([String])
case article([String])
}
复制代码
此次使用**[String]**设置教程和文章详细信息。
Swift 4.2字符串使用UTF-16编码。 所以,encodedOffset将返回UTF-16字符串的偏移量:
let swiftVersion = "Swift 4.2"
let offset = swiftVersion.endIndex.encodedOffset
复制代码
在这里你能够得到swiftVersion中endIndex的偏移量。 这对Swift 5中使用的UTF-8字符串编码不起做用,所以Swift 5用utf16Offset(in :)
替换encodedOffset来处理这两种状况SE-0241:
let swiftVersion = "Swift 5"
let offset = swiftVersion.endIndex.utf16Offset(in: swiftVersion)
复制代码
Swift 5添加了ConttiguousStorageIfAvailable(_ :)
到Sequence和withContiguousMutableStorageIfAvailable(_ :)
到MutableCollection,为协议扩展SE-0237中的withUnsafeBufferPointer(_ :)
和withUnsafeMutableBufferPointer(_ :)
提供通用实现。
Swift 5将处理器的SIMD类型操做添加到标准库中。它们为SIMD向量和矩阵提供低级支持。它们还简化了<simd/simd.h>
SE-0229的Objective-C,C和**C++**实现。
您可使用本教程顶部或底部的“下载材料”连接下载最终的Playground。
Swift 5为Swift 4.2增长了许多很酷的功能,使语言ABI稳定。这是语言发展过程当中的一个重要里程碑,由于从如今开始,这一变化将会减小。
您能够在官方Swift CHANGELOG或Swift标准库差别上阅读更多关于此版本Swift的更改。
您还能够查看Swift Evolution提案,了解下一个Swift版本中会发生什么变化。在这里,您能够针对当前正在审核的提案提供反馈,甚至能够自行提交提案!
项目示例: 项目工程地址