naarray查询 swift_Swift 4最全的新特性详细解析(推荐)
引言
Swift,蘋果于2014年WWDC(蘋果開發(fā)者大會)發(fā)布的新開發(fā)語言,可與Objective-C共同運行于Mac OS和iOS平臺,用于搭建基于蘋果平臺的應(yīng)用程序。Swift吸收了眾多現(xiàn)代編程語言的優(yōu)點,盡力的提供簡潔的編程語言和強大的功能。
WWDC 2017 給大家?guī)砹撕芏囿@喜。Swift 4 也伴隨著 Xcode 9 測試版來到了我們的面前,很多強大的新特性非常值得我們期待在正式項目中去使用它。因為 Swift 4 是開源的,如果你關(guān)注 swift-evolution 這個項目的話,就應(yīng)該已經(jīng)提前了解到它的新特性了。本文參考了 WWDC 2017 以及各種資料,,從語法、字符串、標準庫、構(gòu)建過程等方面,把 Swift 4 的這些新特性一一列舉出來做介紹和分析,讓他們毫無保留地展現(xiàn)在你眼前,下面話不多說了,來隨著小編一起看看詳細的介紹吧。
一、語法改進
extension 中可以訪問 private 的屬性
考慮以下代碼:
struct Date: Equatable, Comparable {
private let secondsSinceReferenceDate: Double
static func ==(lhs: Date, rhs: Date) -> Bool {
return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
}
static func Bool {
return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
}
}
上面代碼定義了一個 Date 結(jié)構(gòu)體,并實現(xiàn) Equatable 和 Comparable 協(xié)議。為了讓代碼更清晰,可讀性更好,一般會把對協(xié)議的實現(xiàn)放在單獨的 extension 中,這也是一種非常符合 Swift 風格的寫法,如下:
struct Date {
private let secondsSinceReferenceDate: Double
}
extension Date: Equatable {
static func ==(lhs: Date, rhs: Date) -> Bool {
return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
}
}
extension Date: Comparable {
static func Bool {
return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
}
}
但是在 Swift 3 中,編譯就報錯了,因為 extension 中無法獲取到 secondsSinceReferenceDate 屬性,因為它是 private 的。于是在 Swift 3 中,必須把 private 改為 fileprivate。
struct Date {
fileprivate let secondsSinceReferenceDate: Double
}
...
但是如果用 fileprivate,屬性的作用域就會比我們需要的更大,可能會不小心造成屬性的濫用。
在 Swift 4 中,private 的屬性的作用域擴大到了 extension 中,并且被限定在了 struct 和 extension 內(nèi)部,這樣就不需要再改成 fileprivate 了,這是最好的結(jié)果。
類型和協(xié)議的組合類型
考慮以下代碼:
protocol Shakeable {
func shake()
}
extension UIButton: Shakeable { /* ... */ }
extension UISlider: Shakeable { /* ... */ }
func shakeEm(controls: [???]) {
for control in controls where control.state.isEnabled {
}
control.shake()
}
在 Swift 3 中,這里的 ??? 應(yīng)該寫什么呢?如果寫 UIControl,那么 control.shake() 就會報錯;如果寫 Shakeable,那么 control.state.isEnabled 就會報錯。其實我們也可以這樣寫:
func shakeEm(controls: [UIControl]) {
for control in controls where control.isEnabled {
if control is Shakeable {
(control as! Shakeable).shake()
}
}
}
這樣寫雖然可以跑通了,但是很丑陋。
在 Swift 4 中,可以把類型和協(xié)議用 & 組合在一起作為一個類型使用,就可以像下面這樣寫了:
protocol Shakeable {
func shake()
}
extension UIButton: Shakeable { /* ... */ }
extension UISlider: Shakeable { /* ... */ }
func shakeEm(controls: [UIControl & Shakeable]) {
for control in controls where control.state.isEnabled {
control.shake()
}// Objective-C API
@interface NSCandidateListTouchBarItem : NSTouchBarItem
@property (nullable, weak) NSView *client;
@end
}
把它聲明為了 UIControl & Shakeable 類型。OK,圓滿解決。
PS:
這個代碼例子是 WWDC 2017 的 PPT 中的,上面的代碼有點問題,control.state.isEnabled 這句代碼中,state 是沒有 isEnabled 這個屬性的,改為 control.isEnabled 就可以了。看來蘋果的工程師做 PPT 有時候還是不太嚴謹。
另外,iOS SDK 中的 API 也用這個特性做了優(yōu)化,例如:
// Objective-C API
@interface NSCandidateListTouchBarItem : NSTouchBarItem
@property (nullable, weak) NSView *client;
@end
這個 API 的 Objective-C 版本是沒有問題的,可以知道 client 屬性既是一個 NSView,又符合 NSTextInputClient 協(xié)議。然而它對應(yīng)的 Swift 3 版本為:
class NSCandidateListTouchBarItem : NSTouchBarItem {
var client: NSView?
}
僅僅是一個 NSView 類型 /(ㄒoㄒ)/~~
在 Swift 4 中,這類 API 做了優(yōu)化,改成了:
class NSCandidateListTouchBarItem : NSTouchBarItem {
var client: (NSView & NSTextInputClient)?
}
這樣類型的聲明就更加嚴謹了。
Associated Type 可以追加 Where 約束語句
在 Swift 4 中可以在 associatedtype 后面聲明的類型后追加 where 語句
associatedtype Element where
看下面是 Swift 4 標準庫中 Sequence 中 Element 的聲明:
protocol Sequence {
associatedtype Element where Self.Element == Self.Iterator.Element
// ...
}
它限定了 Sequence 中 Element 這個類型必須和 Iterator.Element 的類型一致。
通過 where 語句可以對類型添加更多的約束,使其更嚴謹,避免在使用這個類型時做多余的類型判斷。
新的 Key Paths 語法
先來看看 Swift 3 中 Key Paths 的寫法:
@objcMembers class Kid: NSObject {
dynamic var nickname: String = ""
dynamic var age: Double = 0.0
dynamic var friends: [Kid] = []
}
var ben = Kid(nickname: "Benji", age: 5.5)
let kidsNameKeyPath = #keyPath(Kid.nickname)
let name = ben.valueForKeyPath(kidsNameKeyPath)
ben.setValue("Ben", forKeyPath: kidsNameKeyPath)
Swift 4 中創(chuàng)建一個 KeyPath 用 `` 作為開頭:
\Kid.nickname
當編譯器可以推導出類型時,可以省略基礎(chǔ)類型部分:
\.nickname
上面的代碼在 Swift 4 中就可以這樣寫:
struct Kid {
var nickname: String = ""
var age: Double = 0.0
var friends: [Kid] = []
}
var ben = Kid(nickname: "Benji", age: 8, friends: [])
let name = ben[keyPath: \Kid.nickname]
ben[keyPath: \Kid.nickname] = "BigBen"
相比 Swift 3,Swift 4 的 Key Paths 具有以下優(yōu)勢:
類型可以定義為 class、struct
定義類型時無需加上 @objcMembers、dynamic 等關(guān)鍵字
性能更好
類型安全和類型推斷,例如 ben.valueForKeyPath(kidsNameKeyPath) 返回的類型是 Any,ben[keyPath: \Kid.nickname] 直接返回 String 類型
可以在所有值類型上使用
下標支持泛型
有時候會寫一些數(shù)據(jù)容器,Swift 支持通過下標來讀寫容器中的數(shù)據(jù),但是如果容器類中的數(shù)據(jù)類型定義為泛型,以前的下標語法就只能返回 Any,在取出值后需要用 as? 來轉(zhuǎn)換類型。Swift 4 定義下標也可以使用泛型了。
struct GenericDictionary {
private var data: [Key: Value]
init(data: [Key: Value]) {
self.data = data
}
subscript(key: Key) -> T? {
return data[key] as? T
}
}
let dictionary = GenericDictionary(data: ["Name": "Xiaoming"])
let name: String? = dictionary["Name"] // 不需要再寫 as? String
二、字符串
Unicode 字符串在計算 count 時的正確性改善
在 Unicode 中,有些字符是由幾個其它字符組成的,比如 é 這個字符,它可以用 \u{E9} 來表示,也可以用 e 字符和上面一撇字符組合在一起表示 \u{65}\u{301}。
考慮以下代碼:
var family = "👩"
family += "\u{200D}👩"
family += "\u{200D}👧"
family += "\u{200D}👦"
print(family)
print(family.characters.count)
這個 family 是一個由多個字符組合成的字符,打印出來的結(jié)果為 👩?👩?👧?👦。上面的代碼在 Swift 3 中打印的 count 數(shù)是 4,在 Swift 4 中打印出的 count 是 1。
更快的字符處理速度
Swift 4 的字符串優(yōu)化了底層實現(xiàn),對于英語、法語、德語、西班牙語的處理速度提高了 3.5 倍。
對于簡體中文、日語的處理速度提高了 2.5 倍。
去掉 characters
Swift 3 中的 String 需要通過 characters 去調(diào)用的屬性方法,在 Swift 4 中可以通過 String 對象本身直接調(diào)用,例如:
let values = "one,two,three..."
var i = values.characters.startIndex
while let comma = values.characters[i...
if values.characters[i..
print("found it!")
}
i = values.characters.index(after: comma)
}
Swift 4 可以把上面代碼中的所有的 characters 都去掉,修改如下:
let values = "one,two,three..."
var i = values.startIndex
while let comma = values[i...
if values[i..
print("found it!")
}
i = values.index(after: comma)
}
One-sided Slicing
Swift 4 新增了一個語法糖 ... 可以對字符串進行單側(cè)邊界取子串。
Swift 3:
let values = "abcdefg"
let startSlicingIndex = values.index(values.startIndex, offsetBy: 3)
let subvalues = values[startSlicingIndex..
// defg
Swift 4:
let values = "abcdefg"
let startSlicingIndex = values.index(values.startIndex, offsetBy: 3)
let subvalues = values[startSlicingIndex...] // One-sided Slicing
// defg
String 當做 Collection 來用
Swift 4 中 String 可以當做 Collection 來用,并不是因為 String 實現(xiàn)了 Collection 協(xié)議,而是 String 本身增加了很多 Collection 協(xié)議中的方法,使得 String 在使用時看上去就是個 Collection。例如:
翻轉(zhuǎn)字符串:
let abc: String = "abc"
print(String(abc.reversed()))
// cba
遍歷字符:
let abc: String = "abc"
for c in abc {
print(c)
}
/*
a
b
c
*/
Map、Filter、Reduce:
// map
let abc: String = "abc"
_ = abc.map {
print($0.description)
}
// filter
let filtered = abc.filter { $0 == "b" }
// reduce
let result = abc.reduce("1") { (result, c) -> String in
print(result)
print(c)
return result + String(c)
}
print(result)
Substring
在 Swift 中,String 的背后有個 Owner Object 來跟蹤和管理這個 String,String 對象在內(nèi)存中的存儲由內(nèi)存其實地址、字符數(shù)、指向 Owner Object 指針組成。Owner Object 指針指向 Owner Object 對象,Owner Object 對象持有 String Buffer。當對 String 做取子字符串操作時,子字符串的 Owner Object 指針會和原字符串指向同一個對象,因此子字符串的 Owner Object 會持有原 String 的 Buffer。當原字符串銷毀時,由于原字符串的 Buffer 被子字符串的 Owner Object 持有了,原字符串 Buffer 并不會釋放,造成極大的內(nèi)存浪費。
在 Swift 4 中,做取子串操作的結(jié)果是一個 Substring 類型,它無法直接賦值給需要 String 類型的地方。必須用 String() 包一層,系統(tǒng)會通過復制創(chuàng)建出一個新的字符串對象,這樣原字符串在銷毀時,原字符串的 Buffer 就可以完全釋放了。
let big = downloadHugeString()
let small = extractTinyString(from: big)
mainView.titleLabel.text = small // Swift 4 編譯報錯
mainView.titleLabel.text = String(small) // 編譯通過
多行字符串字面量
Swift 3 中寫很長的字符串只能寫在一行。
func tellJoke(name: String, character: Character) {
let punchline = name.filter { $0 != character }
let n = name.count - punchline.count
let joke = "Q: Why does \(name) have \(n) \(character)'s in their name?\nA: I don't know, why does \(name) have \(n) \(character)'s in their name?\nQ: Because otherwise they'd be called \(punchline)."
print(joke)
}
tellJoke(name: "Edward Woodward", character: "d")
字符串中間有換行只能通過添加 \n 字符來代表換行。
Swift 4 可以把字符串寫在一對 """ 中,這樣字符串就可以寫成多行。
func tellJoke(name: String, character: Character) {
let punchline = name.filter { $0 != character }
let n = name.count - punchline.count
let joke = """
Q: Why does \(name) have \(n) \(character)'s in their name?
A: I don't know, why does \(name) have \(n) \(character)'s in their name?
Q: Because otherwise they'd be called \(punchline).
"""
print(joke)
}
tellJoke(name: "Edward Woodward", character: "d")
三、Swift 標準庫
Encoding and Decoding
當需要將一個對象持久化時,需要把這個對象序列化,往常的做法是實現(xiàn) NSCoding 協(xié)議,寫過的人應(yīng)該都知道實現(xiàn) NSCoding 協(xié)議的代碼寫起來很痛苦,尤其是當屬性非常多的時候。幾年前有一個工具能自動生成 Objective-C 的實現(xiàn) NSCoding 協(xié)議代碼,當時用著還不錯,但后來這個工具已經(jīng)沒有人維護很久了,而且不支持 Swift。
Swift 4 中引入了 Codable 幫我們解決了這個問題。
struct Language: Codable {
var name: String
var version: Int
}
我們想將這個 Language 對象的實例持久化,只需要讓 Language 符合 Codable 協(xié)議即可,Language 中不用寫別的代碼。符合了 Codable 協(xié)議以后,可以選擇把對象 encode 成 JSON 或者 PropertyList。
Encode 操作如下:
let swift = Language(name: "Swift", version: 4)
if let encoded = try? JSONEncoder().encode(swift) {
// 把 encoded 保存起來
}
Decode 操作如下:
if let decoded = try? JSONDecoder().decode(Language.self, from: encoded) {
print(decoded.name)
}
Sequence 改進
Swift 3:
protocol Sequence {
associatedtype Iterator: IteratorProtocol
func makeIterator() -> Iterator
}
Swift 4:
protocol Sequence {
associatedtype Element
associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
func makeIterator() -> Iterator
}
由于 Swift 4 中的 associatedtype 支持追加 where 語句,所以 Sequence 做了這樣的改進。
Swift 4 中獲取 Sequence 的元素類型可以不用 Iterator.Element,而是直接取 Element。
SubSequence 也做了修改:
protocol Sequence {
associatedtype SubSequence: Sequence
where SubSequence.SubSequence == SubSequence,
SubSequence.Element == Element
}
通過 where 語句的限定,保證了類型正確,避免在使用 Sequence 時做一些不必要的類型判斷。
Collection 也有一些類似的修改。
Protocol-oriented integers
整數(shù)類型符合的協(xié)議有修改,新增了 FixedWidthInteger 等協(xié)議,具體的協(xié)議繼承關(guān)系如下:
+-------------+ +-------------+
+------>+ Numeric | | Comparable |
| | (+,-,*) | | (==,,...)|
| +------------++ +---+---------+
| ^ ^
+-------+------------+ | |
| SignedNumeric | +-+-------+-----------+
| (unary -) | | BinaryInteger |
+------+-------------+ |(words,%,bitwise,...)|
^ ++---+-----+----------+
| +-----------^ ^ ^---------------+
| | | |
+------+---------++ +---------+---------------+ +--+----------------+
| SignedInteger | | FixedWidthInteger | | UnsignedInteger |
| | |(endianness,overflow,...)| | |
+---------------+-+ +-+--------------------+--+ +-+-----------------+
^ ^ ^ ^
| | | |
| | | |
++--------+-+ +-+-------+-+
|Int family |-+ |UInt family|-+
+-----------+ | +-----------+ |
+-----------+ +-----------+
Dictionary and Set enhancements
這里簡單列一下 Dictionary 和 Set 增強了哪些功能:
通過 Sequence 來初始化
可以包含重復的 Key
Filter 的結(jié)果的類型和原類型一致
Dictionary 的 mapValues 方法
Dictionary 的默認值
Dictionary 可以分組
Dictionary 可以翻轉(zhuǎn)
NSNumber bridging and Numeric types
let n = NSNumber(value: 999)
let v = n as? UInt8 // Swift 4: nil, Swift 3: 231
在 Swift 4 中,把一個值為 999 的 NSNumber 轉(zhuǎn)換為 UInt8 后,能正確的返回 nil,而在 Swift 3 中會不可預料的返回 231。
MutableCollection.swapAt(::)
MutableCollection 現(xiàn)在有了一個新方法 swapAt(::) 用來交換兩個位置的值,例如:
var mutableArray = [1, 2, 3, 4]
mutableArray.swapAt(1, 2)
print(mutableArray)
// 打印結(jié)果:[1, 3, 2, 4]
四、構(gòu)建過程改進
New Build System
Xcode 9 引入了 New Build System,可在 Xcode 9 的 File -> Project Settings... 中選擇開啟。
預編譯 Bridging Headers 文件
對于 Swift 和 Objective-C 混合的項目,Swift 調(diào)用 Objective-C 時,需要建立一個 Bridging Headers 文件,然后把 Swift 要調(diào)用的 Objective-C 類的頭文件都寫在里面,編譯器會讀取 Bridging Headers 中的頭文件,然后生成一個龐大的 Swift 文件,文件內(nèi)容是這些頭文件內(nèi)的 API 的 Swift 版本。然后編譯器會在編譯每一個 Swift 文件時,都要編譯一遍這個龐大的 Swift 文件的內(nèi)容。
有了預編譯 Bridging Headers 以后,編譯器會在預編譯階段把 Bridging Headers 編譯一次,然后插入到每個 Swift 文件中,這樣就大大提高了編譯速度。
蘋果宣稱 Xcode 9 和 Swift 4 對于 Swift 和 Objective-C 混合編譯的速度提高了 40%。
Indexing 可以在編譯的同時進行
用 Swift 開發(fā)項目時,近幾個版本的 Xcode 進行 Indexing 的速度慢的令人發(fā)指。Xcode 9 和 Swift 4 在這方面做了優(yōu)化,可以在編譯的同時進行 Indexing,一般編譯結(jié)束后 Indexing 也會同時完成。
COW Existential Containers
Swift 中有個東西叫 Existential Containers,它用來保存未知類型的值,它的內(nèi)部是一個 Inline value buffer,如果 Inline value buffer 中的值占用空間很大時,這個值會被分配在堆上,然而在堆上分配內(nèi)存是一個性能比較慢的操作。
Swift 4 中為了優(yōu)化性能引入了 COW Existential Containers,這里的 COW 就代表 "Copy-On-Write",當存在多個相同的值時,他們會共用 buffer 上的空間,直到某個值被修改時,這個被修改的值才會被拷貝一份并分配內(nèi)存空間。
移除未調(diào)用的協(xié)議實現(xiàn)
struct Date {
private let secondsSinceReferenceDate: Double
}
extension Date: Equatable {
static func ==(lhs: Date, rhs: Date) -> Bool {
return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
}
}
extension Date: Comparable {
static func Bool {
return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
}
}
看上面例子,Date 實現(xiàn)了 Equatable 和 Comparable 協(xié)議。編譯時如果編譯器發(fā)現(xiàn)沒有任何地方調(diào)用了對 Date 進行大小比較的方法,編譯器會移除 Comparable 協(xié)議的實現(xiàn),來達到減小包大小的目的。
減少隱式 @objc 自動推斷
在項目中想把 Swift 寫的 API 暴露給 Objective-C 調(diào)用,需要增加 @objc。在 Swift 3 中,編譯器會在很多地方為我們隱式的加上 @objc,例如當一個類繼承于 NSObject,那么這個類的所有方法都會被隱式的加上 @objc。
class MyClass: NSObject {
func print() { ... } // 包含隱式的 @objc
func show() { ... } // 包含隱式的 @objc
}
這樣很多并不需要暴露給 Objective-C 也被加上了 @objc。大量 @objc 會導致二進制文件大小的增加。
在 Swift 4 中,隱式 @objc 自動推斷只會發(fā)生在很少的當必須要使用 @objc 的情況,比如:
復寫父類的 Objective-C 方法
符合一個 Objective-C 的協(xié)議
其它大多數(shù)地方必須手工顯示的加上 @objc。
減少了隱式 @objc 自動推斷后,Apple Music app 的包大小減少了 5.7%。
五、 Exclusive Access to Memory
在遍歷一個 Collection 的時候可以去修改每一個元素的值,但是在遍歷時如果去添加或刪除一個元素就可能會引起 Crash。
例如為 MutableCollection 擴展一個 modifyEach 方法來修改每個元素的值,代碼如下:
extension MutableCollection {
mutating func modifyEach(_ body: (inout Element) -> ()) {
for index in self.indices {
body(&self[index])
}
}
}
假如在調(diào)用 modifyEach 時去刪除元素:
var numbers = [1, 2, 3]
numbers.modifyEach { element in
element *= 2
numbers.removeAll()
}
就會在運行時 Crash。Swift 4 中引入了 Exclusive Access to Memory,使得這個錯誤可以在編譯時被檢查出來。
六、 兼容性
Xcode 9 中同時集成了 Swift 3.2 和 Swift 4。
Swift 3.2 完全兼容 Swift 3.1,并會在過時的語法或函數(shù)上報告警告。
Swift 3.2 具有 Swift 4 的一些寫法,但是性能不如 Swift 4。
Swift 3.2 和 Swift 4 可以混合編譯,可以指定一部分模塊用 Swift 3.2 編譯,一部分用 Swift 4 編譯。
遷移到 Swift 4 后能獲得 Swift 4 所有的新特性,并且性能比 Swift 3.2 好。
總結(jié):當 Xcode 正式版發(fā)布后,現(xiàn)有的 Swift 代碼可以直接升級到 Swift 3.2 而不用做任何改動,后續(xù)可以再遷移到 Swift 4。或者直接遷移到 Swift 4 也可以,Swift 4 相比 Swift 3 的 API 變化還是不大的,很多第三方庫都可以直接用 Swift 4 編譯。Swift 1 到 2 和 Swift 2 到 3 的遷移的痛苦在 3 到 4 的遷移上已經(jīng)大大改善了。
七、參考資料
WWDC 2017 Session 402 《What's New in Swift》
WWDC 2017 Session 212 《What's New in Foundation》
WWDC 2017 Session 102 《Platforms State of the Union》
《Swift Language Programming (Swift 4.0)》
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
總結(jié)
以上是生活随笔為你收集整理的naarray查询 swift_Swift 4最全的新特性详细解析(推荐)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: formdata怎么传数组_如何使用fo
- 下一篇: docker maven 打包jar_m