總結了筆者平常使用 Swift 的一些小 Tips。html
把代碼裏的 var 全改爲 let,只保留不能編譯經過的。git
ObjC 的 Foundation 層幾乎都是繼承 NSObject
實現的,平時都在操做指針,因此要區分 Mutable 和 Imutable 的設計,好比 NSString
和 NSMutableString
。github
Swift 使用了 let 和 var 關鍵字直接用於區分是否可變。可變會更容易出錯,因此儘可能採用不可變設計,等到須要改變才改成 var 吧。objective-c
!遇到 nil 時會 crash(包括 as!
進行強制轉換)。可使用 if let
/guard let
/case let
配合 as?
將可選值消化掉。可能返回 nil 的 API,爲何要本身騙本身呢?編程
當遇到 ObjC 代碼暴露給 Swift 使用時,給接口 .h 文件加上 NS_ASSUME_NONNULL_BEGIN
和 NS_ASSUME_NONNULL_END
並檢查接口參數是否能夠爲 nil 吧。swift
struct 是值類型,class 是引用類型。類類型分配在堆區,默認淺拷貝,容易被不經意間被改變,而值類型分配在棧區,默認深拷貝。而且 Swift 還有寫時複製(copy on write)。markdown
即便是使用 class 時,也僅在必要時(如橋接到 ObjC,使用 Runtime 一些特性)繼承自 NSObject
。網絡
多使用 String
、Array
、Dictionary
、Int
、Bool
,少使用 Foundation 裏面的 NSString
、NSArray
、NSDictionary
、NSNumber
。Cocoa Foundation 裏面的都是類類型,而 Swift 標準庫的是值類型,有不少標準庫的方便方法。閉包
還有用 print
代替 NSLog
。app
forEach
,map
,compactMap
,flatMap
,zip
,reduce
是好幫手,代替一些使用變量並在循環中處理的例子吧。用上高階函數,不只代碼更清晰,還能將狀態控制在更小的做用域內。
和 ObjC 基本都在函數的回調中返回 NSError
不同,Swift 函數可使用 throw
關鍵字拋出錯誤。
func test() throws { //... } do { try test() } catch { print(error) } // 若是對錯誤不敏感 try? test() 複製代碼
Swift 裏面的 String
的 index 和 count 不是一一對應的(兼容 Unicode),因此 stirng.count == 0
的效率不如 string.isEmpty
。
Swift 有着 framework 級別的命名空間,因此命名重複時能夠經過 framework 名肯定,不用擔憂重複命名問題。
儘可能只有在須要橋接給 ObjC 時,才使用 @objc(前綴 + 類名)
進行別名聲明。
對應訪問成員變量,方法時,都不用像 ObjC 那些寫 self
了。
只在閉包內、函數實參和成員變量名字相同和方法形參須要自身時使用。閉包內 self
是強制的,而且能夠提醒注意循環引用問題。函數調用時實參名和成員變量相同時,函數做用域內會優先使用函數實參,因此訪問成員變量是須要 self
。
直接使用 ClassA()
,代替 ClassA.init()
,代碼更簡潔。
// no bad let flag:Bool = false // better let flag = false // not bad view.contentMode = UIView.ContentMode.center // better view.contentMode = .center 複製代碼
在設計接口時,再也不須要爲每個形參是否須要而編寫一個方法了,減小方法數吧。
// not bad func test() { //... } func test(param1:String) { //... } func test(param2:String) { //... } // better func test(param1:String = "", param2:String = "") { //... } 複製代碼
var a = [0] let _ = a.removeLast() [0].forEach{ _ in print("hh")} 複製代碼
而對於本身設計的接口,若是返回值可有可無,只是附加功能的話,可使用 @discardableResult
進行標註。
好比系統有個 default
關鍵字,而你也但願使用這個命名時就能派上用場。
let `default` = A() func `default`() {} 複製代碼
比起 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 } 複製代碼
類型嵌套用於在類型裏定義類型,讓類型的命名空間的精細化程度更高。
struct GameService { enum APIError { enum ResultError { case noResult } } // use APIError } // use GameService.APIError 複製代碼
有時候某一塊邏輯只須要在方法內複用或者作邏輯分割,能夠在方法內定義方法,這樣訪問域會更清晰。
func big(){ func small() { //... } small() } 複製代碼
有時候初始化時一個對象時還須要賦值其中的一些屬性,這個時候就可使用閉包代碼塊的整合。
let someView: UIView = { let view = UIView(frame:.zero) view.backgroundColor = .red return view }() 複製代碼
和 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) 複製代碼
定義一些常量時,用命名空間作隔離是最好的,Swift 的 Enum 比較適合用於命名空間的定義,能嵌套,且不存在初始化方法不會被用於其餘做用。
enum Event { enum Name { static let login = "event.name.login" } } // use Event.Name.login // not allow Event() 複製代碼
// not bad var name:String? if let aName = dic["name"] as? String { name = aName } else { name = "" } // better let name = dic["name"] as? String ?? "" 複製代碼
除了常規的字符串插值,Swift5 還增長了更強大可自定義的字符串插值系統,詳情見 文章。
let a = 2 print("\(a) is 2") 複製代碼
Swift 在設計上,爲協議作了不少強大的功能。Swift 標準庫裏大量的方法和類都使用了協議進行抽象。在編寫代碼時優先考慮使用協議進行邏輯的抽象,詳情能夠參考 Apple WWDC 2015 Session 408 - Protocol-Oriented Programming in Swift。
guard
是 if
的反義詞,能夠提早將異常狀況 return。配合 guard let
使用,能夠在正常分支下使用正確的條件。
guard let a = a as? String else { return } // 下面的 a 就是 string 而且 non-nil 的了 複製代碼
元組(Tuple)是個包含多個值的簡單對象,使用元組,能夠簡單的用來函數返回多參數,也能夠在集合類型中存取一對對的值。
typealias Pair<T> = (T, T) let pair = Pair(1, 2) 複製代碼
比起 ObjC 僅支持在集合類型裏使用輕量級範型,Swift 的範型更強大,除了集合,還支持類、枚舉、協議(Associate Type)。
protocol View { associatedtype Model func update(model:Model) } 複製代碼
比起 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 } } 複製代碼
和 ObjC 的 Categories 相似,拓展能夠添加類的方法。Swift 的 Extension 還能拓展值類型,枚舉的方法,且不須要新建文件編寫和支持權限訪問關鍵字。經過 Extension,還能給 Protocol 增長默認實現。也能在 Extension 中遵循協議,讓方法劃分更加清晰。
fileprivate extension Date { var toString: String { //... } } Date().toString 複製代碼
懶加載不須要像 ObjC 同樣重寫 getter 方法,並判空了,在屬性前面加上 lazy
關鍵字就能夠實現了。
lazy var view = UIView(frame:.zero) 複製代碼
where
關鍵字能夠對範圍進行限定,詳情見這篇 文章。
typealias
能夠用來命名閉包類型、協議類型、範型類型,還支持組合。更多用法見 文章。
typealias NewName<D> = ClassA<D>&ProtocolA&ProtocolB 複製代碼
Result
是一個枚舉類型,包含成功或者失敗的枚舉值,並持有相應成功或者失敗的值,經過範型肯定類型信息。由於成功和失敗是互斥的,這樣就能夠避免多個可選參數返回。
好比對網絡請求回調進行改造:
URLSession.shared.dataTask(with: request) { result in switch result { case .success(let (data, _)): handle(data: data) case .failure(let error): handle(error: error) } } 複製代碼
在 ObjC 中,開發者更習慣用相似 FBKVOController 等第三方庫進行 KVO 的監聽,那是由於原生的寫法太難用了。Swift 爲 KVO 增長了閉包的 API,更簡潔好用。
scrollObserver = observe(\.scrollView!.contentOffset, options: [.new], changeHandler: { object, change in //... }) 複製代碼
同理,Swift 的 GCD API 也是專門通過 Swift 化的,也更加簡潔好用。
在 Swift4 加入 Codable 協議後,JSON 等通用結構轉模型,終於有了原生的支持。詳情見 文章。
在 WWDC19 推出的 Swift Only 的庫,SwiftUI 有着相似 React 的聲明式 UI 開發框架,配合實時調試,在 Demo 和簡單頁面,跨 Apple 平臺應用適配時有必定優點。而 Combine 時相似 RxSwift 的響應式編程框架,能使事件流更統一。
關於 SwiftUI 和 Combine 的介紹,能夠參考 WWDC 2019 相關 Session。也能夠參考筆者翻譯的 文章 1,文章 2。
若有錯誤,歡迎交流&指出。