如何假裝寫過 Swift

Nemocdz發表於2020-01-03

總結了筆者日常使用 Swift 的一些小 Tips。

Safe & Fast

1. 能用 let,儘量不用 var

把程式碼裡的 var 全改成 let,只保留不能編譯通過的。

ObjC 的 Foundation 層幾乎都是繼承 NSObject 實現的,平時都在操作指標,所以要區分 Mutable 和 Imutable 的設計,比如 NSStringNSMutableString

Swift 使用了 let 和 var 關鍵字直接用於區分是否可變。可變會更容易出錯,所以儘量採用不可變設計,等到需要改變才改為 var 吧。

2. 儘量不用 !

!遇到 nil 時會 crash(包括 as! 進行強制轉換)。可以使用 if let/guard let/case let 配合 as? 將可選值消化掉。可能返回 nil 的 API,為什麼要自己騙自己呢?

當遇到 ObjC 程式碼暴露給 Swift 使用時,給介面 .h 檔案加上 NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END 並檢查介面引數是否可以為 nil 吧。

3. 多定義 struct,少定義 class

struct 是值型別,class 是引用型別。類型別分配在堆區,預設淺拷貝,容易被不經意間被改變,而值型別分配在棧區,預設深拷貝。並且 Swift 還有寫時複製(copy on write)。

即使是使用 class 時,也僅在必要時(如橋接到 ObjC,使用 Runtime 一些特性)繼承自 NSObject

4. 能用 Swift 標準庫型別,儘量不用對應的 Foundation 型別

多使用 StringArrayDictionaryIntBool,少使用 Foundation 裡面的 NSStringNSArrayNSDictionaryNSNumber。Cocoa Foundation 裡面的都是類型別,而 Swift 標準庫的是值型別,有很多標準庫的方便方法。

還有用 print 代替 NSLog

5. 優先使用內建高階函式

forEachmapcompactMapflatMapzipreduce 是好幫手,代替一些使用變數並在迴圈中處理的例子吧。用上高階函式,不僅程式碼更清晰,還能將狀態控制在更小的作用域內。

6. 使用 try catch 捕獲錯誤

和 ObjC 基本都在函式的回撥中返回 NSError 不一樣,Swift 函式可以使用 throw 關鍵字丟擲錯誤。

func test() throws {
	 //... 
}

do {
  try test()
} catch {
  print(error)
}

// 如果對錯誤不敏感
try? test()
複製程式碼

7. 對 String 判空時優先採用 isEmpty

Swift 裡面的 String 的 index 和 count 不是一一對應的(相容 Unicode),所以 stirng.count == 0 的效率不如 string.isEmpty

Clean Code

8. 檔名字去掉字首

Swift 有著 framework 級別的名稱空間,所以命名重複時可以通過 framework 名確定,不用擔心重複命名問題。

儘量只有在需要橋接給 ObjC 時,才使用 @objc(字首 + 類名) 進行別名宣告。

9. 省略 self

對應訪問成員變數,方法時,都不用像 ObjC 那些寫 self 了。

只在閉包內、函式實參和成員變數名字相同和方法形參需要自身時使用。閉包內 self 是強制的,並且可以提醒注意迴圈引用問題。函式呼叫時實參名和成員變數相同時,函式作用域內會優先使用函式實參,所以訪問成員變數是需要 self

10. 省略 init()

直接使用 ClassA(),代替 ClassA.init(),程式碼更簡潔。

11. 能推導的型別不用顯式編寫

// no bad
let flag:Bool = false
// better
let flag = false

// not bad
view.contentMode = UIView.ContentMode.center
// better
view.contentMode = .center
複製程式碼

12. 使用預設形參,簡化介面設計

在設計介面時,不再需要為每一個形參是否需要而編寫一個方法了,減少方法數吧。

// not bad
func test() {
    //...
}

func test(param1:String) {
    //...
}

func test(param2:String) {
    //...
}

// better
func test(param1:String = "", param2:String = "") {
    //...
}
複製程式碼

13. 使用 _ 表示不使用的返回值

var a = [0]
let _ = a.removeLast()

[0].forEach{ _ in print("hh")}
複製程式碼

而對於自己設計的介面,如果返回值無關緊要,只是附加功能的話,可以使用 @discardableResult 進行標註。

14. 使用 `` 來定義和關鍵字重名的方法和屬性

比如系統有個 default 關鍵字,而你也希望使用這個命名時就能派上用場。

let `default` = A()

func `default`() {}
複製程式碼

15. Strong-Weak Dance 很簡單

比起 ObjC 裡需要每次需要寫

__weak typeof(self) weak_self = self;
__typeof__(self) strong_self = weak_self;
複製程式碼

儘管很多人會採用巨集來簡化,但重複的巨集定義又會衝突,且 ObjC 沒有訪問許可權關鍵字。

Swift 在閉包中可以使用 weakunowned 指定閉包對值的捕獲,配合 guard 就可以實現同樣的功能。

test(){ [weak self] in 
		guard let self = self else { return }
    // self is strong without retain cycle
}
複製程式碼

16. 型別巢狀

型別巢狀用於在型別裡定義型別,讓型別的名稱空間的精細化程度更高。

struct GameService {
    enum APIError {
        enum ResultError {
            case noResult
        }
    }
  	// use APIError
}
// use GameService.APIError
複製程式碼

17. func 巢狀

有時候某一塊邏輯只需要在方法內複用或者做邏輯分割,可以在方法內定義方法,這樣訪問域會更清晰。

func big(){
	func small() {
    //...
  }
  small()
}
複製程式碼

18. 使用閉包做初始化

有時候初始化時一個物件時還需要賦值其中的一些屬性,這個時候就可以使用閉包程式碼塊的整合。

let someView: UIView = {
	let view = UIView(frame:.zero)
  view.backgroundColor = .red
  return view
}()
複製程式碼

19. 使用更簡潔的函式實參和形參

和 ObjC 不同,有形參實參的 Swift,可以在呼叫和編寫的時候都有更合適簡潔的表達。

// not bad
func updateWithView(view:UIView)
updateWithView(view:viewA)
// better
func updateWithView(_ view:UIView)
updateWith(viewA)

// not bad
func didSelectAtIndex(index:Int)
didSelectAtIndex(index:2)
// better
func didSelect(at index:Int)
didSelect(at:2)
複製程式碼

20. Enum 用於名稱空間宣告

定義一些常量時,用名稱空間做隔離是最好的,Swift 的 Enum 比較適合用於名稱空間的定義,能巢狀,且不存在初始化方法不會被用於其他作用。

enum Event {
    enum Name {
        static let login = "event.name.login"
    }
}

// use 
Event.Name.login
// not allow
Event()
複製程式碼

21. 使用 ?? 返回預設值

// not bad
var name:String?
if let aName = dic["name"] as? String {
	name = aName
} else {
  name = ""
}

// better
let name = dic["name"] as? String ?? ""
複製程式碼

22. 使用字串插值

除了常規的字串插值,Swift5 還增加了更強大可自定義的字串插值系統,詳情見 文章

let a = 2
print("\(a) is 2")
複製程式碼

Syntactic sugar

23. 更 POP(Protocol Oriented Programming,面向協議程式設計)

Swift 在設計上,為協議做了很多強大的功能。Swift 標準庫裡大量的方法和類都使用了協議進行抽象。在編寫程式碼時優先考慮使用協議進行邏輯的抽象,詳情可以參考 Apple WWDC 2015 Session 408 - Protocol-Oriented Programming in Swift

24. 優先使用 guard

guardif 的反義詞,可以提前將異常情況 return。配合 guard let 使用,可以在正常分支下使用正確的條件。

guard let a = a as? String else { return }
// 下面的 a 就是 string 並且 non-nil 的了
複製程式碼

25. 嘗試元組

元組(Tuple)是個包含多個值的簡單物件,使用元組,可以簡單的用來函式返回多引數,也可以在集合型別中存取一對對的值。

typealias Pair<T> = (T, T)

let pair = Pair(1, 2)
複製程式碼

26. 嘗試範型

比起 ObjC 僅支援在集合型別裡使用輕量級範型,Swift 的範型更強大,除了集合,還支援類、列舉、協議(Associate Type)。

protocol View {
    associatedtype Model
    
    func update(model:Model)
}
複製程式碼

27. 嘗試列舉

比起 ObjC 那和 C 語言差不多的列舉,Swift 的列舉更強大。Swift 的列舉不一定需要 Int 作為列舉的原始值,可以不需要原始值,也可以使用 StringFloatBoolean 作為原始值。能在列舉值上關聯值,實現很多有趣的功能(Rx,Promise 的狀態機)。能給列舉編寫函式,能給列舉增加 Extension。

enum State<Value> {
    case pending
    case fulfill(value:Value)
    case reject(reason:Error)
    mutating func update(to state:State){
        guard case .pending = self else {
            return
        }
        self = state
    }
}
複製程式碼

28. 嘗試 Extension

和 ObjC 的 Categories 類似,擴充可以新增類的方法。Swift 的 Extension 還能擴充值型別,列舉的方法,且不需要新建檔案編寫和支援許可權訪問關鍵字。通過 Extension,還能給 Protocol 增加預設實現。也能在 Extension 中遵循協議,讓方法劃分更加清晰。

fileprivate extension Date {
    var toString: String {
        //...
    }
}

Date().toString
複製程式碼

29. lazy 關鍵字

懶載入不需要像 ObjC 一樣重寫 getter 方法,並判空了,在屬性前面加上 lazy 關鍵字就可以實現了。

lazy var view = UIView(frame:.zero)
複製程式碼

30. where 關鍵字

where 關鍵字可以對範圍進行限定,詳情見這篇 文章

31. typealias 關鍵字

typealias 可以用來命名閉包型別、協議型別、範型型別,還支援組合。更多用法見 文章

typealias NewName<D> = ClassA<D>&ProtocolA&ProtocolB
複製程式碼

32. Result 型別

Result 是一個列舉型別,包含成功或者失敗的列舉值,並持有相應成功或者失敗的值,通過範型確定型別資訊。因為成功和失敗是互斥的,這樣就可以避免多個可選引數返回。

比如對網路請求回撥進行改造:

URLSession.shared.dataTask(with: request) { result in
    switch result {
    case .success(let (data, _)):
        handle(data: data)
    case .failure(let error):
        handle(error: error)
    }
}
複製程式碼

33. KVO

在 ObjC 中,開發者更習慣用類似 FBKVOController 等第三方庫進行 KVO 的監聽,那是因為原生的寫法太難用了。Swift 為 KVO 增加了閉包的 API,更簡潔好用。

scrollObserver = observe(\.scrollView!.contentOffset, options: [.new], changeHandler: { object, change in
     //...
})
複製程式碼

同理,Swift 的 GCD API 也是專門經過 Swift 化的,也更加簡潔好用。

34. Codable

在 Swift4 加入 Codable 協議後,JSON 等通用結構轉模型,終於有了原生的支援。詳情見 文章

35. SwiftUI&Combine

在 WWDC19 推出的 Swift Only 的庫,SwiftUI 有著類似 React 的宣告式 UI 開發框架,配合實時除錯,在 Demo 和簡單頁面,跨 Apple 平臺應用適配時有一定優勢。而 Combine 時類似 RxSwift 的響應式程式設計框架,能使事件流更統一。

關於 SwiftUI 和 Combine 的介紹,可以參考 WWDC 2019 相關 Session。也可以參考筆者翻譯的 文章 1文章 2

最後

如有錯誤,歡迎交流&指出。

參考連結

相關文章