Swifter 100 tips 讀后總結
-
將protocol的方法2為mutating,原因如下:
- protocol也適用于struct和enum中,如果不添加mutating,那么在struct和enum中將會提示沒有實現協議方法,無法通過編譯.
- 如果去掉protocol中的mutating字段,那么struct和中將會報錯說不能改變成員變量.
- 在class 中實現protocol 方法的時候,不需要添加mutating字段,因為class可以隨意更改自己的成員變量,所以在protocol里邊用mutating修飾方法,對于class的實現是完全透明的,可以當做不存在.
-
多元組 (Tuple)
這是一個oc中沒有使用過的結合類型,使用它可以幫助我們簡化很多問題,比如:
- 在oc甚至java中,交換2個變量的值,我們通常會這么寫:
func swapMel<T>(a: inout T, b: inOut T) { let temp = a a = b b = temp }
這樣做很容易理解,但是產生了一個中間變量,這是我們不想看到的結果,所以有人會這么寫:
func swapMel<T>(a: inOut T, b: inOut T) { a = a ^ b b = b ^ a a = a ^ b }
這樣,我們就沒有新增一個中間變量,就進行了2個變量的值的交換,但是這樣做的代價就是不容易理解.當我們用swift中的Tuple來實現就簡單多了,并且很容易理解:
func swapMel<T>(a: inOut T, b: inOut T) { (a, b) = (b, a) }
這樣我們就完成了2個變量的交換過程,很簡單.
- oc中的返回值只能有一個,所以在解決某些問題的時候會顯得很麻煩,比如:
CGRect有一個輔助方法CGRectDivide,它是將一個CGRect在一定位置切分成2個區域,具體使用方法如下:
CGRect rect = CGRectMake(0, 0, 100, 100); CGRect small; CGRect large; CGRectDivide(rect, &small, &large, 20, CGRectMinXEdge);
它將{0, 0, 100, 100}分割成了small:{0, 0, 20, 100} 和 large: {20, 0, 80, 100} 2個區域,
下邊我們可以用swift的Tuple來實現,并對比一下:
extension CGRect { func divided(atDistance: CGFload, from fromEdge: CGRectEdge) -> (slice: CGRect, remainder: CGRect) { //... } }
使用的時候,做法如下:
let rect = CGRect(x: 0, y: 0, width: 100, height: 100) let (small, large) = rect.divided(atDistance: 20, from: .minXEdge)
這樣看起來就很簡單明了
-
@autoclosure 和 ??
- @autoclosure做的事情就是吧一句表達式自動封裝成一個閉包(closure).
比如我們有一個方法接受一個閉包參數,當閉包執行結果為true的時候進行打印:
func logIfTrue(_ predicate: () -> Bool) { if predicate() { print("True") } }
在調用的時候,我們會這樣寫:
logIfTrue({return 2 > 1})
swift中對閉包的寫法進行了一些簡化,當只有一條return語句的時候,我們可以這樣寫:
logIfTrue({2 > 1})
因為這個閉包是logIfTrue函數的最后一個參數,也就是尾隨閉包,所以我們還可以簡寫如下:
logIfTrue{2 > 1}
猶豫簡寫的過多,這樣看起來其實已經不那么好理解了,這時候我們就可以使用@autoclosure:
func logIfTrue(_ predicate: @autoclosure () -> Bool) { if predicate() { print("True") } }
這時候我們就可以直接寫:
logIfTrue(2 > 1)
他會自動把 2 > 1這個表達式轉換成 () -> Bool,這樣我么就得到一個寫法簡單,表意清除的式子.
- ??
?? 使用來快速判斷nil的.語意是:如果??操作符左邊的值是非nil的Optional值,就返回他的value,如果是nil,就有??操作符右邊的值代替,比如:
var level: Int? var startLevel = 1 var currentLevel = level ?? startLevel
注意:@autoclosure并不支持帶有輸入參數的方法,也就是形如() -> T的參數才能使用這個特性進行簡化.
// ?? 的2中形式和底層實現 func ??<T>(optional: T?, defaultValue: @autoclosure () -> T?) -> T? func ??<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T // 我們之前用到的是第二種 func ??<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T { switch optinal { case .Some(let value): return value case .None: return defaultValue() } }
有人可以會對為什么使用@autoclosure有疑問,說不使用閉包,直接賦值不好嗎? 原因是這樣的,這個默認值可能是經過特別復雜的計算獲得的,但是它只有在??左側為nil的時候才用的到,當??左側非nil的時候我們根本用不到,所以就不用計算這個默認值,我們通過這個閉包,就可以把默認值的計算推遲到optional判定為nil之后,這就是巧妙之處.
另外,swift中的&& 和||操作符其實也用到了@autoclosure
-
Optional Chaining
使用Optional Chaining可以讓我們拜托很多不必要的判斷和取值,但是使用的時候要小心,還是有坑的
因為Optional Chaining是隨時都可能提前返回nil的,所以使用Optional Chaining所得到的東西其實都是Optional的,比如下邊一段代碼:
class Toy { let name: String init(name: String) { self.name = name } } class Pet { var toy: Toy? } class Child { var pet: Pet? }
在實際使用中,當我們想要知道小明的寵物的玩具的名字的時候,可以通過Optional Chaining來拿到:
let toyName = xiaoming.pet?.toy?.name
雖然我們訪問的是name,并且Toy中name被定義為一個確定的String,而不是String?, 但我們拿到的toyName其實還是String?類型.因為在Optional Chaining中隨時可能會遇到nil而提前返回,這個時候我們拿到的就是nil了.
然而,實際使用中,我們通常會通過Optional Binding(可選綁定)來取值這樣的代碼:
if let toyName = xiaoming.pet?.toy?.name { // 這時候拿到的toyName必然不是nil }
單獨這樣看還是很清楚的,如果和其他的特性結合在一起,可能就會很麻煩了:
// 給Toy添加一個extension // 當 child 的pet 有toy的時候就玩,沒有就不能玩 extension Toy { func play() { // ... } }
拿小明舉例子, 如果小明的pet有toy的話,就玩之:
xiaoming.pet?toy?.play()
如果現在除了小明還有小李小張等,我們需要把這一串調通抽象出來,做一個閉包方便使用.傳入一個child對象,于是我們就可能寫成下邊這個樣子:
這是錯誤的代碼:
let playClosure = {(child: Child) -> () in child.pet?.toy?play() }
這樣的代碼是沒有意義的!可能有人會問,問題處在那里呢?
問題就在play()的調用上.定義的時候沒有寫play()的返回值,就是Void(同()是等價的),但是經過前面的Optional Chaining以后,我們拿到的是一個Optional的結果,也就是我們最后得到的應該是一個closure:
let playClosure = {(child: Child) -> ()? in child.pet?.toy?play() }
這樣調用的返回將是一個()?(等同于Void?),雖然看起來很奇怪,但這是事實.使用的時候,我們通過Optional Binding來判定方法是否調用成功:
if let result: () = playClosure(xiaoming) { print("happy") } else { print("no toy") }