Swift中你應該知道的一些有用的tips

bestswifter發表於2018-01-03

本文會詳細介紹一些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中檢視更多細節:

Swift中你應該知道的一些有用的tips

或者您也可以點選這篇文章檢視更多細節——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__,它可以很容易讓程式設計師明白自己呼叫的方法的方法名。

迴圈標籤

通常意義上的迴圈標籤主要是continuebreak,不過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)
  }
}
複製程式碼

以上兩段程式碼等價,可以看到使用了迴圈標籤後,程式碼明顯簡潔了很多。

相關文章