本文會詳細介紹一些Swift中不為大多數人知,又很有用的知識點。您不必一次性看完,不過或許哪一天這些知識就能派上用場,專案Demo在我的github,您可以下載下來親自實驗一番,如果覺得有用還望點個star以示支援。
本文主要的知識點有:
- @noescape和@autoclosure
- 內聯lazy屬性
- 函式柯里化
- 可變引數
- dynamic關鍵字
- 一些特殊的字面量
- 迴圈標籤
@noescape和@autoclosure
關於這兩個關鍵字的含義,在我此前的文章——第六章——函式(自動閉包和記憶體)中已經有詳細的解釋,這裡就簡單總結概括一下:
- @noescape:這個關鍵字告訴編譯器,引數閉包只能在函式內部使用。它不能被賦值給臨時變數,不能非同步呼叫,也不能作為未標記為@noescape的引數傳遞給其他函式。總之您可以放心,它無法在這個函式作用域之外使用。
除了安全性上的保證,swift還會為標記為@noescape的引數做一些優化,閉包內訪問類的成員時您還可以省去self.
的語法。
- @autoclosure:這個關鍵字將表示式封裝成閉包,優點在於延遲了表示式的執行,缺點是如果濫用會導致程式碼可讀性降低。
內聯lazy屬性
標記為lazy的屬性在物件初始化時不會被建立,它直到第一次被訪問時才會建立,通常情況下它是這樣實現的:
class PersonOld {
lazy var expensiveObject: ExpensiveObject = {
return self.createExpensiveObject() // 傳統實現方式
}()
private func createExpensiveObject() -> ExpensiveObject {
return ExpensiveObject()
}
}
複製程式碼
lazy屬性本質上是一個閉包,閉包中的表示式只會呼叫一次。需要強調的是,雖然這個閉包中捕獲了self
,但是這樣做並不會導致迴圈引用,猜測是Swift自動把self
標記為unowned了。
這樣的寫法其實可以進行簡化,簡化後的實現如下:
class Person {
lazy var expensiveObject: ExpensiveObject = self.createExpensiveObject()
private func createExpensiveObject() -> ExpensiveObject {
return ExpensiveObject()
}
}
複製程式碼
函式柯里化
函式柯里化也是一個老生常談的問題了,我的這篇文章——第六章——函式(函式的便捷性)對其有比較詳細的解釋。
簡單來說,柯里化函式處理一個引數,然後返回一個函式處理剩下來的所有引數。直觀上來看,它避免了很多括號的巢狀,提高了程式碼的簡潔性和可讀性,比如這個函式:
func fourChainedFunctions(a: Int) -> (Int -> (Int -> (Int -> Int))) {
return { b in
return { c in
return { d in
return a + b + c + d
}
}
}
}
fourChainedFunctions(1)(2)(3)(4)
複製程式碼
對比一下它的柯里化版本:
func fourChainedFunctions(a: Int)(b: Int)(c: Int)(d: Int) -> Int {
return a + b + c + d
}
複製程式碼
不過在 Swift 3.0 中,這種柯里化語法會被移除,你需要使用之前完整的函式宣告。感謝 @沒故事的卓同學 指出。
您可以在Swift Programming Language Evolution中檢視更多細節:
或者您也可以點選這篇文章檢視更多細節——Removing currying func declaration syntax
可變引數
如果在引數型別後面加上三個".",表示引數的數量是可變的,如果您有過Java程式設計的經驗,對此應該會比較熟悉:
func printEverythingWithAKrakenEmojiInBetween(objectsToPrint: Any...) {
for object in objectsToPrint {
print("\(object)?")
}
}
printEverythingWithAKrakenEmojiInBetween("Hey", "Look", "At", "Me", "!")
複製程式碼
此時,引數可以當做SequenceType
型別來使用,也就是說可以使用for in
語法遍歷其中的每一個引數。
可變引數並不是什麼罕見的語法,比如print
函式就是用了可變引數,更多詳細的分析請移步:你其實真的不懂print("Hello,world")
dynamic關鍵字
如果您有過OC的開發經驗,那一定會對OC中@dynamic關鍵字比較熟悉,它告訴編譯器不要為屬性合成getter和setter方法。
Swift中也有dynamic關鍵字,它可以用於修飾變數或函式,它的意思也與OC完全不同。它告訴編譯器使用動態分發而不是靜態分發。OC區別於其他語言的一個特點在於它的動態性,任何方法呼叫實際上都是訊息分發,而Swift則儘可能做到靜態分發。
因此,標記為dynamic的變數/函式會隱式的加上@objc關鍵字,它會使用OC的runtime機制。
雖然靜態分發在效率上可能更好,不過一些app分析統計的庫需要依賴動態分發的特性,動態的新增一些統計程式碼,這一點在Swift的靜態分發機制下很難完成。這種情況下,雖然使用dynamic關鍵字會犧牲因為使用靜態分發而獲得的一些效能優化,但也依然是值得的。
class Kraken {
dynamic var imADynamicallyDispatchedString: String
dynamic func imADynamicallyDispatchedFunction() {
//Hooray for dynamic dispatch!
}
}
複製程式碼
使用動態分發,您可以更好的與OC中runtime的一些特性(如CoreData,KVC/KVO)進行互動,不過如果您不能確定變數或函式會被動態的修改、新增或使用了Method-Swizzle,那麼就不應該使用dynamic關鍵字,否則有可能程式崩潰。
特殊的字面量
在開發或除錯過程中如果能用好下面這四個字面量,將會起到事半功倍的效果:
- __FILE__:當前程式碼在那個檔案中
- __FUNCTION__:當前程式碼在該檔案的那個函式中
- __LINE__:當前程式碼在該檔案的第多少行
- __COLUMN__:當前程式碼在改行的多少列
舉個實際例子,您可以在demo中執行體驗一番:
func specialLitertalExpression() {
print(__FILE__)
print(__FUNCTION__)
print(__LINE__)
print(__COLUMN__) // 輸出結果為11,因為有4個空格,print是五個字元,還有一個左括號。
}
複製程式碼
一般情況下最常用的字面量是__FUNCTION__
,它可以很容易讓程式設計師明白自己呼叫的方法的方法名。
迴圈標籤
通常意義上的迴圈標籤主要是continue
和break
,不過swift在此基礎上做了一些擴充,比如下面這段程式碼:
let firstNames = ["Neil","Kt","Bob"]
let lastNames = ["Zhou","Zhang","Wang","Li"]
for firstName in firstNames {
var isFound = false
for lastName in lastNames {
if firstName == "Kt" && lastName == "Zhang" {
isFound = true
break
}
print(firstName + " " + lastName)
}
if isFound {
break
}
}
複製程式碼
目的是希望找到分別在兩個陣列中找到字串"Kt"和"Zhang",在此之前會列印所有遍歷到的字元。
在結束內層迴圈後,我希望外層迴圈也隨之立刻停止,為了實現這個功能,我不得不引入了isFound
引數。然而實際上我需要的只是可以指定停止哪個迴圈而已:
outsideloop: for firstName in firstNames {
innerloop: for lastName in lastNames {
if firstName == "Kt" && lastName == "Zhang" {
break outsideloop //人為指定break外層迴圈
}
print(firstName + " " + lastName)
}
}
複製程式碼
以上兩段程式碼等價,可以看到使用了迴圈標籤後,程式碼明顯簡潔了很多。