雖說從Swift剛出生就一直有關注,但是由于ABI不穩定以及第三方支持還不夠好等等原因,一直沒有用于實際的真實項目中,直到3.0之后感覺時機成熟了,剛好最近有一個比較小的項目,于是有了一次實際項目的初體驗,也順便寫下一些總結。(總體內容會比較淺顯,慚愧了
Swift與Objective-C常見寫法差異
判斷字符串相等、==與===的區別
在oc中,我們已經習慣了使用isEqualToString
方法來判斷兩個字符串是否相等,似乎也不覺得有什么問題,這是因為oc使用==
來統一引用的比較與值類型的比較,但是NSString在內存模型上比較特殊,以至于oc中使用了一個專門的比較方法,而在其他許多語言中只需要用==
對比字符串就好。Swift改進了這一點,順帶一提,Swift中使用==
來比較值類型是否相等,而使用===
來判斷引用類型是否相等。-
數組遍歷
在oc中,我喜歡使用enumerateObjectsUsingBlock方法遍歷數組并進行操作,大概是這個樣子的:NSArray *list = @[@1,@2,@3,@4,@5]; [list enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"obj = %@, idx = %d", obj , (int)idx); }];
但是Swift自然不會還存在如此oc模樣的方法,相替代的,Swift可以用foreach方法:
let list = [1,2,3,4,5] list.forEach { number in print(number) }
但是如果我們想要像oc一樣同時獲取元素和下標的話,就要換一種寫法了:
let list = [1,2,3,4,5] for (idx, number) in list.enumerated() { print("idx is \(idx) and number is \(number)") }
無論如何,至少看起來比oc是要簡潔多了。
另外,Swift的一個令人激動的特色就是內嵌一些函數式編程范式,因此如果我們要對數組逐個進行操作的話用
map
方法是再方便不過。配合上一些語法糖,非常之簡單而且直觀,比如我們把上面的list的每個元素乘以2:print(list.map { $0 * 2 })
還有更多的基本用法如filter和reduce等等這里就不贅述了,很多相關教程。
判斷或獲取類名字符串
在oc中,獲取一個對象的類型是很簡單的,只需要調用class類型方法就好,比如[AClass class]
,如果判斷某個對象是否屬于這個類型,則使用isKindOfClass
方法:[self isKindOfClass:[AClass class]]
,如果需要獲取一個實例類名的字符串,則NSStringFromClass([AClass class])
。而在Swift中,獲取一個類的class很簡單,只需要AClass.self
就可以了,但是,如果你想獲取一個實例對象的類名字符串,則需要這樣寫String(describing: type(of: aInstance))
,額,總覺得有點奇怪。
Swift特性的一些實踐經驗總結
-
要說在Swift中最有特色體驗最明顯的,應該算可選類型了,誠然,可選類型可以幫助我們構建更安全更可靠的app,但是毫無疑問,可選類型中各種轉換和拆包讓人覺得略煩,特別是我這樣的新手,稍不注意就是報錯或者沒有得到符合預期的值。然后在stackoverflow上看相關的討論的時候,發現一個很有趣的小方法:
extension Optional { func valueOrDefault<T>(defaultValue: T) -> T { switch(self) { case .none: return defaultValue case .some(let value): return value as! T } } }
這是一個Optional類的擴展,用了泛型,代碼就不解釋了,很清晰。用上這個類別之后,我們可以方便的、安全的寫這樣的代碼:
var a: Int? //a = 1 let b = a.valueOrDefault(defaultValue: 2) print("b = \(b)")
在不知道a是否有確切值的情況下,就可以將a安全的賦值給b,通過提供默認值的方式,簡直贊。在這個例子中,如果把注釋的那行取消注釋,b則為1,在大量使用可選類型的基礎上,使用這個小技巧可以避免使用一堆堆的諸如
if let b = a as? Int
的代碼。 -
Swift與強大的枚舉
在oc或者說c中,枚舉的用途也許僅僅是一個同數據類型的常量集合,但是在Swift中,枚舉的能力被大大擴充,甚至完全可以替代簡單的class。而且在一些方面的最佳實踐中,枚舉的出場率也相當高,比如notification.name。先不說有沒有必要這樣做,在我的實踐中,發現Swift的枚舉確實是一個很方便而且簡化邏輯的玩意兒,還是舉個栗子吧,比如我們有一個item,包含一個有3種狀態的state變量,我們還需要根據這個state輸出對應的顏色和說明文字等:struct Item { enum ItemState { case success case fail case ready //inner function func showColor()->UIColor { switch self { case .success: return UIColor.green case .fail: return UIColor.red case .ready: return UIColor.gray } } func descStr()->String { return "[output: \(self)]" } } var name = "example" var state = ItemState.ready }
直接在enum中寫function有什么好處呢?可以免去很多的判斷,從而直接綁定輸出,同時也減少了出錯的機會,在這個例子中,我們可以如下調用:
var item = Item() print("now state is = \(item.state.descStr()) , color is \(item.state.showColor())") item.state = .success print("now state is = \(item.state.descStr()) , color is \(item.state.showColor())") item.state = .fail print("now state is = \(item.state.descStr()) , color is \(item.state.showColor())")
我們可以任意改變state,然后輸出當前state對應的各種綁定變量,把邏輯內聚在enum中,外部的調用會非常順暢。
當然,這其實算是最基礎的用法了,關于enum還有更多高級且高端的用法,可以參考Swift 中枚舉高級用法及實踐 -
Swift中的反射
從現狀來看,Swift的動態性似乎是不如Objective-C的,某些諸如方法交換和動態綁定似乎底層用的也還是Objective-C的runtime。但是好在Swift還是有一個反射的替代方案,比較常用的一個地方就是,在需要保存一個NSObject對象到本地時,必須實現coder的相關方法,默認的做法是需要你把class內每個屬性都分別寫一個轉換,非常麻煩而且容易出錯,在Objective-C中可以通過運行時獲取所有成員變量和類型來做這個事情,相對應的,Swift可以用Mirror:convenience required init?(coder aDecoder: NSCoder) { self.init() for child in Mirror(reflecting: self).children { if let key = child.label { setValue(aDecoder.decodeObject(forKey: key), forKey: key) } } } func encode(with aCoder: NSCoder) { for child in Mirror(reflecting: self).children { if let key = child.label { aCoder.encode(value(forKey: key), forKey: key) } } }
還是比較方便快捷的,我們可以再優化一下,把這個放進NSObject的extension里,這樣所有需要序列化的object就可以不用再繁復地一個個寫了 :)
下一篇我會總結一下目前用到的常用第三方框架和組合用法。