内容简介:What's New in Swift4?
翻译自 Raywenderlich 的文章: What’s New in Swift 4?
Swift 4 是Swift下一个大版本更新,预计在2017年秋推出Beta版。其重点在与Swift3的兼容性和ABI稳定性。
本文重点介绍一些这个版本中显著影响你代码的变化。好了,让我们开始吧。
Getting Started
Swift 4包含在Xcode 9。你可以从苹果的 developer portal 下载最新的Xcode9(需要一个有效的开发者账号)。每个Beta版的Xcode 都会捆绑最新的Swift 4 快照。
在你阅读时,你会注意到 [SE-xxxx] 格式的链接,这些链接将带你进去相关的 Swift Evolution 提案。如果你想获得很多和主题相关的信息,请务必点开它们。
我推荐在 Playground 中尝试Swift 4 的新特性和更新,这有助于在脑海中巩固知识,并使你深入每个主题。把玩这些例子,并尝试扩展/打断他们。玩得开心。
注:这篇文件将在为每个Bate版的Xcode更新,如果你使用的是不同的Swift快照,这些代码不保证可以运行。
Migrating to Swift 4
从Swift 3到 4 的迁移比 Swift 2.2 到 3 少了很多麻烦,一般来说,绝大多数的改变都是附加的,并不需要大量的人为修改。因此,Swift 迁移 工具 为你处理多数的改变。
Xcode 9 同时支持 Swift 4 和 Swift 3.2。如果需要,你可以逐个指定项目中的每个 target 使用 Swift 3.2 还是 Swift 4。 迁移到 并不是完全不受限制的,你可能需要重新部分代码来兼容修的 SDK .而且由于 Swift ABI 仍然尚未稳定所以你需要用 Xcode 9 重新编译你依赖。
当你准备迁移到 Swift 4 , Xcode 9 再次提供一个工具来帮助你,你可以点击 Edit/Convert/To Current Swift Syntax… 来呼出转换工具。
选择你想要转换的 targets 后,Xcode 将提示你 Objective-C 推导中的偏好, 选择推荐的选项通过限制推断来减少你的二进制文件的大小。(关于这个的主题的更多信息,参阅 Limiting @objc Inference ).
为了更好地理解你代码中预期的变化,我们将首先介绍Swift 4 中变更的API。
API Changes
在跳转到 Swift 4 加入的新特新前,让我们首先来看一下它对现有的 API 有哪些改成/改善。
String
Swift 4 中的 String 获得了很多应得的称赞。这个提案包含大量的改变,让我们来分解下 [SE-0163] :
怕你怀旧,strings 再次变为集合像之前的 Swift2.0 一样。这个改变使你不在依赖 characters 数组,现在你直接遍历 String 对象 :
let galaxy = "Milky Way :cow:"
for char in galaxy {
print(char)
}
不仅仅是循环,你还可以获得 Sequence 和 Collection 所有附加功能;
galaxy.count // 11
galaxy.isEmpty // false
galaxy.dropFirst() // "ilky Way :cow:"
String(galaxy.reversed()) // ":cow: yaW ykliM"
// Filter out any none ASCII characters
galaxy.filter { char in
let isASCII = char.unicodeScalars.reduce(true, { $0 && $1.isASCII })
return isASCII
} // "Milky Way "
ASCII 的例子演示了对 Character 一个小改进,现在你可以从 Character 直接访问 UnicodeScalarView ,以前你需要实例化一个新的 String [SE-0178] 。
还有一个附加的时候 StringProtocol ,
它声明以前在 String 上声明的大部分功能。这种变化的原因是为了改善切片工作。Swift 4 在 String 中增加了 Substring 类型,继承自 StringProtocol 。
String 和 Substring 都实现了 StringProtocol ,这是两者几乎拥有相同的功能。
// Grab a subsequence of String let endIndex = galaxy.index(galaxy.startIndex, offsetBy: 3) var milkSubstring = galaxy[galaxy.startIndex...endIndex] // "Milk" type(of: milkSubstring) // Substring.Type // Concatenate a String onto a Substring milkSubstring += "" // "Milk" // Create a String from a Substring let milkString = String(milkSubstring) // "Milk"
另一项改进是 String 对字形簇的解读,这个决议来自于Unicode 9的改编。在这之前,多码点(multiple code points)构成的 unicode 字符的 count 结果大于1,这通常发生在选定肤色的emoji表情,下面是一些前后对比:
":woman::computer:".count // Now: 1, Before: 2 ":+1|type_4:".count // Now: 1, Before: 2 ":couplekiss_man_man:".count // Now: 1, Before, 4
这只是 [String Manifesto] 的一部分,你可以阅读预期改动的原始动机和被提议后的答复。
Dictionary 和 Set
至于 Collection 类型, Set 和 Dictionary 不是很直观,幸运的是,Swift 团队给了它们很多必要的关照 [SE-0165] .
Sequence Based Initialization
第一个是可以创建一个dictionary 从序列的键值对(元组):
let nearestStarNames = ["Proxima Centauri", "Alpha Centauri A", "Alpha Centauri B", "Barnard's Star", "Wolf 359"] let nearestStarDistances = [4.24, 4.37, 4.37, 5.96, 7.78] // Dictionary from sequence of keys-values let starDistanceDict = Dictionary(uniqueKeysWithValues: zip(nearestStarNames, nearestStarDistances)) // ["Wolf 359": 7.78, "Alpha Centauri B": 4.37, "Proxima Centauri": 4.24, "Alpha Centauri A": 4.37, "Barnard's Star": 5.96]
Duplicate Key Resolution
现在你可以用喜欢的方式复制 keys 实例化一个 dictionary,这使我们不需要写繁琐的键值对关系语句:
// Random vote of people's favorite stars let favoriteStarVotes = ["Alpha Centauri A", "Wolf 359", "Alpha Centauri A", "Barnard's Star"] // Merging keys with closure for conflicts let mergedKeysAndValues = Dictionary(zip(favoriteStarVotes, repeatElement(1, count: favoriteStarVotes.count)), uniquingKeysWith: +) // ["Barnard's Star": 1, "Alpha Centauri A": 2, "Wolf 359": 1]
上面的代码使用了 zip 和 缩写 + 处理重复的 keys 和冲突的 values 。
注: 如果你不熟悉 zip 你可以快速的在Apple的 [Swift Documentation] 学习它。
Filtering
Dictionary 和 Set 现在可以筛选将结果赋给一个新的同类型对象。
// Filtering results into dictionary rather than array of tuples
let closeStars = starDistanceDict.filter { $0.value < 5.0 }
closeStars // Dictionary: ["Proxima Centauri": 4.24, "Alpha Centauri A": 4.37, "Alpha Centauri B": 4.37]
Dictionary Mapping
Dictionary 获得了一个非常有用的方法用来 map 它的 values:
// Mapping values directly resulting in a dictionary
let mappedCloseStars = closeStars.mapValues { "\($0)" }
mappedCloseStars // ["Proxima Centauri": "4.24", "Alpha Centauri A": "4.37", "Alpha Centauri B": "4.37"]
Dictionary Default Values
访问 Dictionary 的 value 时,常见的做法是使用 nil coalescing operator 来给一个默认值(译者注 : a != nil ? a! : b 的方式,见 [Basic Operators] 。在 Swift 4 现在有了简洁的多的方式:
// Subscript with a default value
let siriusDistance = mappedCloseStars["Wolf 359", default: "unknown"] // "unknown"
// Subscript with a default value used for mutating
var starWordsCount: [String: Int] = [:]
for starName in nearestStarNames {
let numWords = starName.split(separator: " ").count
starWordsCount[starName, default: 0] += numWords // Amazing
}
starWordsCount // ["Wolf 359": 2, "Alpha Centauri B": 3, "Proxima Centauri": 2, "Alpha Centauri A": 3, "Barnard's Star": 2]
以前这种住转变需要包裹在臃肿的 if-let 语句中,在 Swift 4 中可能只需要一行。
Dictionary Grouping
另一个出奇有用的附加功能可以从 Sequence 实例化一个 Dictionary ,并将其分组:
// Grouping sequences by computed key
let starsByFirstLetter = Dictionary(grouping: nearestStarNames) { $0.first! }
// ["B": ["Barnard's Star"], "A": ["Alpha Centauri A", "Alpha Centauri B"], "W": ["Wolf 359"], "P": ["Proxima Centauri"]]
当对特定的数据进行归类时会变得很方便。
Reserving Capacity
Sequence 和 Dictionary 现在都具备保留容量的功能。
// Improved Set/Dictionary capacity reservation starWordsCount.capacity // 6 starWordsCount.reserveCapacity(20) // reserves at _least_ 20 elements of capacity starWordsCount.capacity // 24
在这些类型中,重新分配是代价高昂的任务。使用 reserveCapacity(_:) 可以很容易的改善执行效率。
这是一个巨大的改变,所以务必检查这两个类型,想办法使用这些新特性来优化你的代码。
Private Access Modifier
在 Swift 3 上大家并不是很喜欢加入的 fileprivate , 理论上,它很不错,但是实践中它的用法时常让人困惑。在成员内部使用 private ,当你在相同的文件共享成员变量的访问时很少使用 fileprivate 。
问题是 Swift 鼓励使用 extensions 来将代码按逻辑分组。extension 在成员变量的原始作用域之外,导致广泛地需要使用 fileprivate 。
Swift 4 意识到上述在类型和 extension 共享访问权的初衷,但是它只在相同的源文件中有效 [SE-0169] :
struct SpaceCraft{
private let warpCode: String
init(warpCode: String) {
self.warpCode = warpCode
}
}
extension SpaceCraft{
func goToWarpSpeed(warpCode: String) {
if warpCode == self.warpCode { // Error in Swift 3 unless warpCode is fileprivate
print("Do it Scotty!")
}
}
}
let enterprise = SpaceCraft(warpCode: "KirkIsCool")
//enterprise.warpCode // error: 'warpCode' is inaccessible due to 'private' protection level
enterprise.goToWarpSpeed(warpCode: "KirkIsCool") // "Do it Scotty!"
这让你可以实现原本 fileprivate 的目的,而不需要杂乱的代码。
API Additions
现在让我们一起来看一下 Swift 4 的新特性,这些改变只是一下简单的附加功能,不会破坏你现有的代码。
Archival and Serialization
目前为止,Swift 中自定义类型的序列化和归档有太多的坑,对于 class 类型,你需要子类化 NSObject 并且实现 NSCoding 协议。
而像 struct 和 enum 这样的值类型,需要创建一个子类通过扩展 NSObject 和 NSCoding 的 hacks 来实现。
Swift 4 解决了这三种类型的序列化问题 [SE-0166] :
struct CuriosityLog:Codable{
enum Discovery:String,Codable{
case rock, water, martian
}
var sol: Int
var discoveries: [Discovery]
}
// Create a log entry for Mars sol 42
let logSol42 = CuriosityLog(sol: 42, discoveries: [.rock, .rock, .rock, .rock])
上面的例子中,你可以看到在 Swift 中类型的 Encodable 和 Decodable 只需要实现 Codable 协议,如果所有的属性都实现了 Codable 协议,那么编译器将自动完成协议的实现。
对对象进行编码,你需要把它交给一个编码器,Swift 4 开始积极的实现编码器。每个编码器按照不同的 schemes 。(注:该提案的部分内容仍在开发中):
let jsonEncoder = JSONEncoder() // One currently available encoder
// Encode the data
let jsonData = try jsonEncoder.encode(logSol42)
// Create a String from the data
let jsonString = String(data: jsonData, encoding: .utf8) // "{"sol":42,"discoveries":["rock","rock","rock","rock"]}"
它将一个对象自编码成 JSON 对象,请务必检查 JSONEncoder 的属性来定制它的输出。
过程的最后一部分是解码数据为一个具体对象:
let jsonDecoder = JSONDecoder() // Pair decoder to JSONEncoder // Attempt to decode the data to a CuriosityLog object let decodedLog = try jsonDecoder.decode(CuriosityLog.self, from: jsonData) decodedLog.sol // 42 decodedLog.discoveries // [rock, rock, rock, rock]
通过使用 Swift 4的 encoding/decoding,获得Swift的类型安全性。同时不依赖 @objc 协议的开销和限制。
Key-Value Coding
目前为止,由于函数是一个闭包的缘故,你可以在不调用函数的情况下对函数进行引用。你不能做的通过属性访问是没有暴露借口的私有变量。
令人兴奋的是 Swift 4 可以用对象的 Key paths 来 get/set 私有变量。
struct Lightsaber{
enum Color{
case blue, green, red
}
let color: Color
}
class ForceUser{
var name: String
var lightsaber: Lightsaber
var master: ForceUser?
init(name: String, lightsaber: Lightsaber, master: ForceUser? = nil) {
self.name = name
self.lightsaber = lightsaber
self.master = master
}
}
let sidious = ForceUser(name: "Darth Sidious", lightsaber: Lightsaber(color: .red))
let obiwan = ForceUser(name: "Obi-Wan Kenobi", lightsaber: Lightsaber(color: .blue))
let anakin = ForceUser(name: "Anakin Skywalker", lightsaber: Lightsaber(color: .blue), master: obiwan)
在这里你创建了一些 ForceUser 实例,通过设置他们的 name 、 lightsaber 和 master 。创建 key path ,你只需使用一个反斜杠后面跟上你感兴趣的属性:
// Create reference to the ForceUser.name key path let nameKeyPath = \ForceUser.name // Access the value from key path on instance let obiwanName = obiwan[keyPath: nameKeyPath] // "Obi-Wan Kenobi"
在这个例子中,你给 ForceUser 的 name 属性创建了一个 key path。然后使用这个 key path 通过新的下标 keyPath 这个下下标现在在每种类型都可以用。
这里有一些通过 key path 访问子对象,设置属性,构建 key path 引用的例子:
// Use keypath directly inline and to drill down to sub objects
let anakinSaberColor = anakin[keyPath: \ForceUser.lightsaber.color] // blue
// Access a property on the object returned by key path
let masterKeyPath = \ForceUser.master
let anakinMasterName = anakin[keyPath: masterKeyPath]?.name // "Obi-Wan Kenobi"
// Change Anakin to the dark side using key path as a setter
anakin[keyPath: masterKeyPath] = sidious
anakin.master?.name // Darth Sidious
// Note: not currently working, but works in some situations
// Append a key path to an existing path
//let masterNameKeyPath = masterKeyPath.appending(path: \ForceUser.name)
//anakin[keyPath: masterKeyPath] // "Darth Sidious"
```
key path 之美在于它在 Swift 中是坚固的,不像 Objective-C 中的 string 那么凌乱。
### Multi-line String Literals
创建多行文本是很多编程语言一个非常普遍的特性。Swift 4 加入这个简单但是有用的语法,用三个引号包装文本[[SE-0168]](https://github.com/apple/swift-evolution/blob/master/proposals/0168-multi-line-string-literals.md):
```Swift
let star = ":star:️"
let introString = """
A long time ago in a galaxy far,
far away....
You could write multi-lined strings
without "escaping" single quotes.
The indentation of the closing quotes
below deside where the text line
begins.
You can even dynamically add values
from properties: \(star)
"""
print(introString) // prints the string exactly as written above with the value of star
当构建 XML/JSON 信息或者 UI 上的长文字排版时,这是极为有用的。
One-Sided Ranges
为了减少冗余和提高可读性,标准库现在可以使用半开区间来推断开始和结束的索引 [SE-0172] 。
从集合的一个索引到开始或者结束的索引创建一个区间,有了非常便利的方法:
// Collection Subscript var planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] let outsideAsteroidBelt = planets[4...] // Before: planets[4..<planets.endIndex] let firstThree = planets[..<4] // Before: planets[planets.startIndex..<4]
如你所见,半开区间不需要指明开始或者结束的索引。
Infinite Sequence
同时允许你从一个可计算的开始索引定义一个无限的 Sequence :
// Infinite range: 1...infinity
var numberedPlanets = Array(zip(1..., planets))
print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (8, "Neptune")]
planets.append("Pluto")
numberedPlanets = Array(zip(1..., planets))
print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (9, "Pluto")]
Pattern Matching
半开区间另一个很好的用法是模式匹配:
// Pattern matching
func temperature(planetNumber: Int) {
switch planetNumber {
case ...2: // anything less than or equal to 2
print("Too hot")
case 4...: // anything greater than or equal to 4
print("Too cold")
default:
print("Justtttt right")
}
}
temperature(planetNumber: 3) // Earth
Generic Subscripts
下标是一种直观且重要的访问数据的方式,为了改善效率,现在可以用在普通类型 [SE-0148] :
struct GenericDictionary<Key:Hashable,Value>{
private var data: [Key: Value]
init(data: [Key: Value]) {
self.data = data
}
subscript<T>(key: Key) -> T? {
return data[key] as? T
}
}
例子中的返回值是泛型,你可以在这个泛型中这样使用下标:
// Dictionary of type: [String: Any] var earthData = GenericDictionary(data: ["name": "Earth", "population": 7500000000, "moons": 1]) // Automatically infers return type without "as? String" let name: String? = earthData["name"] // Automatically infers return type without "as? Int" let population: Int? = earthData["population"]
不仅仅是返回值可以是泛型的,下标也可以是泛型的:
extension GenericDictionary {
subscript<Keys: Sequence>(keys: Keys) -> [Value] where Keys.Iterator.Element == Key {
var values: [Value] = []
for key in keys {
if let value = data[key] {
values.append(value)
}
}
return values
}
}
// Array subscript value
let nameAndMoons = earthData[["moons", "name"]] // [1, "Earth"]
// Set subscript value
let nameAndMoons2 = earthData[Set(["moons", "name"])] // [1, "Earth"]
在这个例子中你可以看到,传递两个不同的 Sequence 类型( Array 和 Set )会得到各自的 vlaues 组成的数组。
Miscellaneous
以上列举的囊括了swift4中最大变化的部分, 现在让我们快速看看其他方面的小改动。
MutableCollection.swapAt( : :)
MutableCollection 现在有 swapAt(_:_:) 方法,正如它的命名,用来交换给定下标的值 [SE-0173] :
// Very basic bubble sort with an in-place swap
func bubbleSort<T: Comparable>(_array: [T]) -> [T] {
var sortedArray = array
for i in 0..<sortedArray.count - 1 {
for j in 1..<sortedArray.count {
if sortedArray[j-1] > sortedArray[j] {
sortedArray.swapAt(j-1, j) // New MutableCollection method
}
}
}
return sortedArray
}
bubbleSort([4, 3, 2, 1, 0]) // [0, 1, 2, 3, 4]
Associated Type Constraints
现在你可以使用 where 从句约束相关的类型 [SE-0142] :
protocol MyProtocol{
associatedtype Element
associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element
}
通过约束协议, associatedtype 声明可以直接限制它们的值,不必在大费周折。
Class and Protocol Existential
最后一个从 Objective-C 搬过来的特性是可以定义一个类型遵守一类或一组协议 [SE-0156] :
protocol MyProtocol{ }
class View{ }
class ViewSubclass:View,MyProtocol{ }
class MyClass{
var delegate: (View & MyProtocol)?
}
let myClass = MyClass()
//myClass.delegate = View() // error: cannot assign value of type 'View' to type '(View & MyProtocol)?'
myClass.delegate = ViewSubclass()
Limiting @objc Inference
要向 Objective-C 暴露你的 Swift API ,可以使用 @objc 编译标志。在很多情况下编译器可以为你推导。但是大量的推理会导致三个主要的问题:
- 可能显著的增加你的二进制文件大小
- 有时候无法准确的推倒
- 增加构成 Objective-C 方法选择器冲突的风险
Swift 4 通过限制 @objc 的推导来解决这个问题,这意味着当你需要 Objective-C 所有的动态调度功能是,你主要明确的使用 @objc 。
举几个你需要修改的示例包括 private 方法,动态声明 和 NSObject 基类的一些方法。
NSNumber Bridging
在很长时间内, NSNumber 和 Swift numbers 有很多奇怪的行为都困扰这这门语言。Swift 4 干掉了这些bug [SE-0170] :
这里有一个示范:
let n = NSNumber(value: 999) let v = n as? UInt8 // Swift 4: nil, Swift 3: 231
在 Swift 3 中会出现的怪异现象,如果数字溢出,它会简单的从0开始。在这个例子中 999% 2 ^ 8 = 231。
Swift 4 解决了这个问题,只有当强转后的类型可以安全的容纳时,才会返回值。
Swift Package Manager
近几个月,Swift 包管理有许多更新,一些比较大的更新如下:
- 从 branch 或者 comint hash 中获取依赖
- 对可接受的包更多的支配
- 用更为常见的解决方案代替不直观的命令
- 能够定义用来编译的 Swift 版本
- 为每个 target 指明 source files 路径。
这些是 SPM 在必经之路上迈出的大步伐,它还有很长的路要走,我们可以通过积极参与提议来帮忙完善它。
有关最近已解决的提案的详细描述,请查看 Swift 4 Package Manager Update
Still In Progress
在撰写本文时,队列中仍有15个接受的提案。如果你想一睹为快,访问 Swift Evolution Proposals 然后用 Accepted 筛选.
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Beginning ARKit for iPhone and iPad
Wallace Wang / Apress / 2018-11-5 / USD 39.99
Explore how to use ARKit to create iOS apps and learn the basics of augmented reality while diving into ARKit specific topics. This book reveals how augmented reality allows you to view the screen on ......一起来看看 《Beginning ARKit for iPhone and iPad》 这本书的介绍吧!