總結了筆者日常使用 Swift 的一些小 Tips。
Safe & Fast
1. 能用 let,儘量不用 var
把程式碼裡的 var 全改成 let,只保留不能編譯通過的。
ObjC 的 Foundation 層幾乎都是繼承 NSObject
實現的,平時都在操作指標,所以要區分 Mutable 和 Imutable 的設計,比如 NSString
和 NSMutableString
。
Swift 使用了 let 和 var 關鍵字直接用於區分是否可變。可變會更容易出錯,所以儘量採用不可變設計,等到需要改變才改為 var 吧。
2. 儘量不用 !
!遇到 nil 時會 crash(包括 as!
進行強制轉換)。可以使用 if let
/guard let
/case let
配合 as?
將可選值消化掉。可能返回 nil 的 API,為什麼要自己騙自己呢?
當遇到 ObjC 程式碼暴露給 Swift 使用時,給介面 .h 檔案加上 NS_ASSUME_NONNULL_BEGIN
和 NS_ASSUME_NONNULL_END
並檢查介面引數是否可以為 nil 吧。
3. 多定義 struct,少定義 class
struct 是值型別,class 是引用型別。類型別分配在堆區,預設淺拷貝,容易被不經意間被改變,而值型別分配在棧區,預設深拷貝。並且 Swift 還有寫時複製(copy on write)。
即使是使用 class 時,也僅在必要時(如橋接到 ObjC,使用 Runtime 一些特性)繼承自 NSObject
。
4. 能用 Swift 標準庫型別,儘量不用對應的 Foundation 型別
多使用 String
、Array
、Dictionary
、Int
、Bool
,少使用 Foundation 裡面的 NSString
、NSArray
、NSDictionary
、NSNumber
。Cocoa Foundation 裡面的都是類型別,而 Swift 標準庫的是值型別,有很多標準庫的方便方法。
還有用 print
代替 NSLog
。
5. 優先使用內建高階函式
forEach
,map
,compactMap
,flatMap
,zip
,reduce
是好幫手,代替一些使用變數並在迴圈中處理的例子吧。用上高階函式,不僅程式碼更清晰,還能將狀態控制在更小的作用域內。
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 在閉包中可以使用 weak
和 unowned
指定閉包對值的捕獲,配合 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
guard
是 if
的反義詞,可以提前將異常情況 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
作為列舉的原始值,可以不需要原始值,也可以使用 String
、Float
、Boolean
作為原始值。能在列舉值上關聯值,實現很多有趣的功能(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。
最後
如有錯誤,歡迎交流&指出。