Swift 4是蘋(píng)果最新推出的一次語(yǔ)言升級(jí)轻局,計(jì)劃在2017年秋發(fā)布測(cè)試版腥寇。它的主要目標(biāo)是提供與Swift 3的源代碼兼容性缘挑,以及ABI的穩(wěn)定性。
本文重點(diǎn)介紹了Swift此次的變化绍刮,它將對(duì)你的代碼產(chǎn)生重大影響温圆。然后,讓我們開(kāi)始吧录淡!
開(kāi)始
Swift 4要求安裝Xcode 9捌木,你可以從蘋(píng)果的開(kāi)發(fā)者網(wǎng)站下載Xcode 9的最新版本(你必須有一個(gè)開(kāi)發(fā)者帳戶)。
閱讀此文時(shí)嫉戚,你會(huì)注意到有[SE-xxxx]格式的鏈接刨裆。這些鏈接將帶您進(jìn)入相關(guān)的Swift進(jìn)化建議。如果你想了解更多彬檀,一定要進(jìn)去看看帆啃。
我建議在playground里去嘗試每一個(gè)Swift 4的功能,這將有助于鞏固你頭腦中的知識(shí)窍帝,使你有能力深入每一個(gè)話題努潘。試著擴(kuò)展思考這些例子,祝你玩得開(kāi)心坤学!
升級(jí)到Swift 4
從Swift 3到4的遷移要比從2.2到3輕松得多疯坤。大多數(shù)變化都是附加的,不需要我們太多的介入深浮。正因?yàn)槿绱搜沟。焖龠w移工具將為您處理大部分更改。
Xcode 9同時(shí)支持Swift 4以及3飞苇,你的項(xiàng)目中的Target可以是Swift 3.2或Swift 4菌瘫,如果需要,可以逐步進(jìn)行遷移布卡。
當(dāng)你準(zhǔn)備遷移到Swift 4雨让,Xcode提供了遷移工具來(lái)幫助你。在Xcode中忿等,您可以通過(guò)Edit/Convert/To Current Swift Syntax…
來(lái)打開(kāi)轉(zhuǎn)換工具栖忠。
在選擇好需要轉(zhuǎn)換的Target之后,Xcode會(huì)提示你選擇在Objective-C中的偏好这弧。選擇推薦的選項(xiàng)可以減少你的二進(jìn)制文件的大型尴小(更多關(guān)于這個(gè)話題,看看限制@ objc Inference)
為了更好地理解你的代碼中會(huì)有哪些變化匾浪,我們將首先介紹Swift 4中API的更改皇帮。
API的變化
Strings
在Swift 4中,String當(dāng)之無(wú)愧獲得了非常多的關(guān)注蛋辈,它包含了很多變化属拾。 SE-0163 :
如果你是個(gè)懷舊的人将谊,String又變得像以前的Swift 2一樣了,此更改去掉了String中的characters數(shù)組渐白,你可以直接以數(shù)組的方式遍歷String對(duì)象:
let galaxy = "Milky Way ??"
for char in galaxy {
print(char)
}
不僅是遍歷尊浓,Sequence和Collection的一些特性也應(yīng)用到了String上:
galaxy.count // 11
galaxy.isEmpty // false
galaxy.dropFirst() // "ilky Way ??"
String(galaxy.reversed()) // "?? 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 "
另外,新增了StringProtocol接口纯衍,它聲明了在String上的大部分功能栋齿。這個(gè)變化的原因是為了增大slices的應(yīng)用范圍。Swift 4添加了Substring類(lèi)型來(lái)引用String的子序列襟诸。
String和Substring都實(shí)現(xiàn)了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??"
另一個(gè)偉大的改進(jìn)是String解釋字形集群功能瓦堵,這個(gè)決議來(lái)自于Unicode 9的改編。在以前歌亲,由多個(gè)代碼點(diǎn)組成的Unicode字符會(huì)引起大于1的計(jì)數(shù)菇用,例如帶膚色的表情符。下面是一些改進(jìn)前后對(duì)比的例子:
"?????".count // Now: 1, Before: 2
"????".count // Now: 1, Before: 2
"???????????".count // Now: 1, Before, 4
這只是String聲明中提到的一個(gè)更改子集陷揪,您可以去閱讀更多的動(dòng)機(jī)和建議的解決方案惋鸥。
Dictionary & Set
對(duì)于集合類(lèi)型,Set和Dictionary一直都不是很直觀悍缠。這一次卦绣,Swift給了他們一些關(guān)愛(ài) SE-0165 。
序列的初始化
首先是增加了通過(guò)鍵值對(duì)序列(元組)創(chuàng)建字典的能力:
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]
重復(fù)主鍵的解決方案
現(xiàn)在可以用任意方式處理主鍵重復(fù)的字典初始化過(guò)程:
// 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和+來(lái)處理飞蚓,表示當(dāng)主鍵有重復(fù)時(shí)把其內(nèi)容相加迎卤。
過(guò)濾
Dictionary和Set都有能力來(lái)過(guò)濾結(jié)果輸出到新的變量:
// 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 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"]
字典默認(rèn)值
獲取一個(gè)Dictionary的值的通常做法是用nil來(lái)賦默認(rèn)值。在Swift 4中玷坠,這種語(yǔ)法變得更加簡(jiǎn)潔:
// 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包裹起來(lái)劲藐,而Swift 4中只需要一行代碼即可完成八堡!
字典分組
// 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"]]
這個(gè)用在為數(shù)據(jù)按照特定模式分組時(shí)很方便
儲(chǔ)備容量
Sequence和Dictionary現(xiàn)在都具備了儲(chǔ)備容量的能力:
// Improved Set/Dictionary capacity reservation
starWordsCount.capacity // 6
starWordsCount.reserveCapacity(20) // reserves at _least_ 20 elements of capacity
starWordsCount.capacity // 24
重新分配容量是很消耗的操作,用reserveCapacity(_:)
能輕松提高代碼性能聘芜,前提是你知道你大概有多少數(shù)據(jù)量兄渺。
Private訪問(wèn)修飾符
Swift 3的fileprivate有一些讓人不是很喜歡的地方。從理論上講汰现,它的誕生是偉大的挂谍,但在實(shí)踐中,關(guān)于如何使用它常常令人困惑瞎饲。private的用途是保證在成員本身私有使用口叙,fileprivate則是當(dāng)你想在同一個(gè)文件內(nèi)共享訪問(wèn)成員時(shí)使用。
問(wèn)題是Swift鼓勵(lì)我們使用擴(kuò)展將代碼分解為不同的邏輯組嗅战。擴(kuò)展被認(rèn)為是原始成員聲明的范圍之外妄田,從而導(dǎo)致fileprivate被廣泛的需要俺亮,但這并不符合fileprivate被設(shè)計(jì)的初衷。
Swift 4認(rèn)識(shí)到了這種擴(kuò)展之間需要共享相同訪問(wèn)控制范圍的需求疟呐。 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!"
現(xiàn)在將允許你把fileprivate用于初衷的目的脚曾。
新增API
現(xiàn)在讓我們來(lái)看看Swift 4新增的功能,這些功能將不會(huì)影響你現(xiàn)有的代碼启具。
Archival & Serialization
以前的Swift本讥,如果你想要序列化、歸檔你的自定義類(lèi)型鲁冯,你需要做很多事情拷沸。例如對(duì)于class,你需要繼承自NSObject類(lèi)和NSCoding接口晓褪。
對(duì)于struct和enum堵漱,你需要黑科技,例如創(chuàng)造一個(gè)子對(duì)象繼承自NSObject和NSCoding涣仿。
Swift 4把序列化應(yīng)用到了這三個(gè)類(lèi)型勤庐,解決了這個(gè)問(wèn)題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])
在這個(gè)例子中,你能看到我們只需要繼承自Codable
接口好港,就可以讓Swift類(lèi)型Encodable
和Decodable
愉镰。如果所有的屬性都是Codable
,這個(gè)接口將自動(dòng)被編譯器實(shí)現(xiàn)钧汹。
為了編碼一個(gè)對(duì)象丈探,你需要把它傳入編碼器。Swift 4中實(shí)現(xiàn)了很多編碼器拔莱,他們能對(duì)你的對(duì)象進(jìn)行不同模式的編碼 SE-0167:
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"]}"
這將把對(duì)象編碼成JSON對(duì)象碗降,同樣我們可以在解碼變回原對(duì)象:
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]
Key-Value Coding
在以前,你可以可以在不調(diào)用函數(shù)的情況下引用函數(shù)塘秦,因?yàn)檫@些函數(shù)都是閉包讼渊。但是對(duì)于屬性,你只能通過(guò)實(shí)際訪問(wèn)他的數(shù)據(jù)而保存對(duì)屬性的引用尊剔。
令人興奮的是爪幻,Swift 4的key path具備這個(gè)能力SE-0161:
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)
上面創(chuàng)建了一些對(duì)象和實(shí)例,你只需要簡(jiǎn)單的用一個(gè)\
標(biāo)記在屬性前须误,來(lái)創(chuàng)建一個(gè)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"
在這個(gè)實(shí)例中挨稿,你創(chuàng)建了ForceUser的name屬性的一個(gè)key path,你可以以keyPath下標(biāo)的方式來(lái)使用它京痢,這個(gè)下標(biāo)默認(rèn)可以用在任何類(lèi)型奶甘。
下面是更多的例子:
// 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"
Swift的這種優(yōu)美的寫(xiě)法是強(qiáng)類(lèi)型的,不像Objective-C那樣是用字符串來(lái)表示历造!
Multi-line String Literals
很多語(yǔ)言都有的一種常用特性就是多行字符串甩十,Swift 4中增加了這種功能船庇,通過(guò)三個(gè)引號(hào)來(lái)使用SE-0168:
let 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
如果你有一個(gè)XML/JSON的長(zhǎng)消息體需要顯示在UI中,這將非常有用侣监。
One-Sided Ranges
為了減少冗長(zhǎng)的代碼鸭轮,提高可讀性,標(biāo)準(zhǔn)庫(kù)現(xiàn)在可以通過(guò)one-sided ranges來(lái)推斷開(kāi)始和結(jié)束的指標(biāo)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]
如你所見(jiàn)橄霉,通過(guò)one-sided ranges窃爷,你不需要再指定起始和結(jié)束的位置。
Infinite Sequence
當(dāng)你的序列起始值是可數(shù)類(lèi)型時(shí)姓蜂,你可以定義一個(gè)無(wú)窮序列:
// 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
one-sided ranges的另一個(gè)用法是模式匹配:
// 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
下標(biāo)是一種訪問(wèn)數(shù)據(jù)成員的重要方式按厘,為了提高使用范圍,下標(biāo)現(xiàn)在可以支持泛型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
}
}
在這個(gè)例子中钱慢,返回的類(lèi)型是泛型逮京,你可以像這樣使用泛型下標(biāo):
// 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"]
不止是返回值,下標(biāo)類(lèi)型同樣可以使用泛型:
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"]
這個(gè)例子中束莫,你可以看到傳入兩個(gè)不同序列類(lèi)型作為下標(biāo)(Array和Set)懒棉,會(huì)得到他們各自的值。
Miscellaneous
這是Swift 4變化最大的部分览绿,我們快速瀏覽一些片段策严。
MutableCollection.swapAt(::)
MutableCollection擁有了swapAt(::) 方法,交換對(duì)應(yīng)索引的值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
現(xiàn)在可以使用Where子句約束關(guān)聯(lián)類(lèi)型SE-0142:
protocol MyProtocol {
associatedtype Element
associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element
}
Class and Protocol Existential
標(biāo)識(shí)一個(gè)同時(shí)繼承了class和protocols的屬性有了一種新的寫(xiě)法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
我們用@objc來(lái)標(biāo)記Objective-C調(diào)用Swift的API方法饿敲。很多時(shí)候Swift編譯器會(huì)為你推斷出來(lái)妻导,但推斷卻會(huì)來(lái)帶下面三個(gè)問(wèn)題:
- 潛在可能會(huì)顯著增大你的二進(jìn)制文件
- 不是很明顯能確定什么時(shí)候@objc會(huì)被推斷
- 無(wú)意間創(chuàng)建一個(gè)Objective-C的方法,會(huì)增大沖突的概率
Swift 4通過(guò)限制@objc SE-0160的推斷來(lái)試圖解決這些問(wèn)題怀各。這意味著倔韭,當(dāng)你需要Objective-C的動(dòng)態(tài)調(diào)度能力時(shí),你需要顯示使用@objc瓢对。
NSNumber Bridging
NSNumber和Swift的一些數(shù)值已經(jīng)困擾了大家很長(zhǎng)時(shí)間狐肢,幸運(yùn)的是,Swift 4解決了這些問(wèn)題SE-0170:
let n = NSNumber(value: 999)
let v = n as? UInt8 // Swift 4: nil, Swift 3: 231
在Swift 3中沥曹,這個(gè)奇怪的行為表明了,如果數(shù)值溢出碟联,它簡(jiǎn)單的讓它從0重新開(kāi)始妓美,這個(gè)例子中:999 % 2^8 = 231。
Swift 4通過(guò)強(qiáng)制轉(zhuǎn)換為可選類(lèi)型鲤孵,只有當(dāng)數(shù)值能安全的包含在類(lèi)型中時(shí)才有值壶栋,解決了這個(gè)問(wèn)題。
Swift Package Manager
在過(guò)去的幾個(gè)月里普监,Swift Package Manager進(jìn)行了大量的更新贵试。其中最大的變化包括:
- 從分支或提交中獲取依賴(lài)資源
- 對(duì)可接受的包版本有了更多的控制
- 用一個(gè)更通用的解決方案取代不直觀的pinning命令
- 定義用于編譯的Swift版本的能力
- 為每個(gè)Target指定源文件的位置
這些都是走向SPM過(guò)程的重大步驟琉兜。SPM還有一段很長(zhǎng)的路要走,但是我們可以通過(guò)積極參與其中來(lái)幫助它毙玻。
更多內(nèi)容請(qǐng)查看Swift 4 Package Manager Update豌蟋。
展望
Swift語(yǔ)言這些年已經(jīng)真正地成長(zhǎng)起來(lái)并發(fā)展成熟。參與社區(qū)與提交建議使我們能非常容易的跟蹤它的發(fā)展變化桑滩,這也使我們?nèi)魏稳硕寄苤苯佑绊懖⑦M(jìn)化這門(mén)語(yǔ)言梧疲。
Swift 4有了這些變化,我們終于即將到達(dá)运准,ABI穩(wěn)定性就在眼前幌氮。升級(jí)Swift版本的痛苦將會(huì)越來(lái)越小,構(gòu)建性能的工具將越來(lái)越先進(jìn)胁澳,在蘋(píng)果生態(tài)系統(tǒng)以外使用Swift將變得越來(lái)越可行该互。
Swift未來(lái)還會(huì)有很多東西,想要關(guān)注最新的相關(guān)信息韭畸,請(qǐng)查看以下資源: