原文鏈接:http://alisoftware.github.io/swift/2015/09/06/thinking-in-swift-1/
我經(jīng)常看到一些剛接觸Swift的小伙伴們嘗試著把他們的ObjC代碼翻譯成Swift。但開始在Swift代碼最困難的事情不是語法,而是改變你的思維方式,使用新的Swift概念是在ObjC中是并沒有的。
假設(shè)你想創(chuàng)建一個條目列表(比如過會兒要顯示在一個tableView里),每個條目都有一個圖標,標題和網(wǎng)址。這些條目都通過一個json初始化。我們來看一下下面的ObjC代碼:
@interface ListItem : NSObject
@property(strong) UIImage* icon;
@property(strong) NSString* title;
@property(strong) NSURL* url;
@end
@implementation ListItem
+(NSArray*)listItemsFromJSONData:(NSData*)jsonData {
NSArray* itemsDescriptors = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
NSMutableArray* items = [NSMutableArray new];
for (NSDictionary* itemDesc in itemsDescriptors) {
ListItem* item = [ListItem new];
item.icon = [UIImage imageNamed:itemDesc[@"icon"]];
item.title = itemDesc[@"title"];
item.url = [NSURL URLWithString:itemDesc[@"title"]];
[items addObject:item];
}
return [items copy];
}
@end
我們來看一下直譯成Swift是什么樣的:
class ListItem {
var icon: UIImage?
var title: String = ""
var url: NSURL!
static func listItemsFromJSONData(jsonData: NSData?) -> NSArray {
let jsonItems: NSArray = try! NSJSONSerialization.JSONObjectWithData(jsonData!, options: []) as! NSArray
let items: NSMutableArray = NSMutableArray()
for itemDesc in jsonItems {
let item: ListItem = ListItem()
item.icon = UIImage(named: itemDesc["icon"] as! String)
item.title = itemDesc["title"] as! String
item.url = NSURL(string: itemDesc["url"] as! String)!
items.addObject(item)
}
return items.copy() as! NSArray
}
}
對 Swift 稍有經(jīng)驗的人應(yīng)該會看出來這里面有很多代碼異味。Swift 的專家讀到這段代碼之后就很可能心臟病突發(fā)而全部掛掉了。
哪里做錯了?
上面例子中第一個看起來像代碼異味的地方就是一個 Swift 新手經(jīng)常犯的壞毛病:到處使用隱式解析可選類型(value!),強制轉(zhuǎn)型(value as! String)和強制使用try(try!)。
可選類型是你的朋友:它們很棒,因為它們能迫使你去思考你的值什么時候是nil,以及在這種情形下你該做什么。比如”如果沒有圖標的話我該顯示什么呢?在我的 TableViewCell 里我該用一個占位符(placeholder)么?或者用另外一個完全不同的 cell 模板?”。
這些就是我們在 ObjC 中經(jīng)常忘了考慮進去的用例,但是 Swift 幫助我們?nèi)ビ涀∷鼈儯援斨凳莕il的時候把它們強制拆包導(dǎo)致程序崩潰,把可選類型這個高級特性扔在一邊不用,是很可惜的。
! ! ! 你絕不應(yīng)該對一個值進行強制拆包,除非你真的知道你在干什么。記住,每次你加一個!去取悅編譯器的時候,你就屠殺了一匹小馬??。
可悲的是,Xcode是鼓勵犯這種錯誤的,因為error提示到:”value of optional type ‘NSArray?’ not unwrapped. Did you mean to use!or??” ,修改提示建議…你在最后面加一個!?。
Let’s save those ponies
那么我們該怎樣去避開使用這些無處不在的糟糕的!呢?這兒有一些技巧:
- 使用可選綁定(optional binding)if let x = optional { /* 使用 x */ }
- 用as?替換掉as!,前者在轉(zhuǎn)型失敗的時候返回nil;你當然可以把它和if let結(jié)合使用
- 你也可以用try?替換掉try!,前者在表達式失敗時返回nil1。
好了,來看看用了這些規(guī)則之后我們的代碼:
class ListItem {
var icon: UIImage?
var title: String = ""
var url: NSURL!
static func listItemsFromJSONData(jsonData: NSData?) -> NSArray {
if let nonNilJsonData = jsonData {
if let jsonItems: NSArray = (try? NSJSONSerialization.JSONObjectWithData(nonNilJsonData, options: [])) as? NSArray {
let items: NSMutableArray = NSMutableArray()
for itemDesc in jsonItems {
let item: ListItem = ListItem()
if let icon = itemDesc["icon"] as? String {
item.icon = UIImage(named: icon)
}
if let title = itemDesc["title"] as? String {
item.title = title
}
if let urlString = itemDesc["url"] as? String {
if let url = NSURL(string: urlString) {
item.url = url
}
}
items.addObject(item)
}
return items.copy() as! NSArray
}
}
return [] // In case something failed above
}
}
判決的金字塔
可悲的是,滿世界的添加這些if let讓我們的代碼往右挪了好多,形成了臭名昭著的判決金字塔)(此處插段悲情音樂)。
Swift中有些機制能幫我們做簡化:
將多個if let語句合并為一個:if let x = opt1, y = opt2
-
使用guard語句,在某個條件不滿足的情況下能讓我們盡早的從一個函數(shù)中跳出來,避免了再去運行函數(shù)體剩下的部分。
當類型能被推斷出來的時候,我們再用此代碼把這些變量類型去掉來消除冗余 - 比如簡單的用let items = NSMutableArray() - 并利用guard語句再確保我們的json確實是一個NSDictionary對象的數(shù)組。最后,我們用一個更”Swift化”的返回類型[ListItem]替換掉ObjC的NSArray:class ListItem { var icon: UIImage? var title: String = "" var url: NSURL! static func listItemsFromJSONData(jsonData: NSData?) -> [ListItem] { guard let nonNilJsonData = jsonData, let json = try? NSJSONSerialization.JSONObjectWithData(nonNilJsonData, options: []), let jsonItems = json as? Array<NSDictionary> else { // If we failed to unserialize the JSON // or that JSON wasn't an Array of NSDictionaries, // then bail early with an empty array return [] } var items = [ListItem]() for itemDesc in jsonItems { let item = ListItem() if let icon = itemDesc["icon"] as? String { item.icon = UIImage(named: icon) } if let title = itemDesc["title"] as? String { item.title = title } if let urlString = itemDesc["url"] as? String, let url = NSURL(string: urlString) { item.url = url } items.append(item) } return items } }
guard語句真心很贊,因為它在函數(shù)的開始部分就把代碼專注于檢查輸入是否有效,然后在代碼剩下的部分中你就不用再為這些檢查操心了。如果輸入并非所想,我們就盡早跳出,幫助我們專注在那些我們期望的事情上。
結(jié)論
Swift 是為了更高的安全性而設(shè)計。不要把所有東西都強制拆包而忽視了可選類型:當你在你的 Swift 代碼中看見了一個!,你就應(yīng)該總是要把它看做是一處代碼異味,某些事情是要出錯的。
注意這個try?默默的將error丟棄了:用它的時候你不會知道為什么代碼出錯的原因。所以通常來說如果可能的話用do { try ... } catch { }替換掉try?會更好。但是在我們的例子中,因為我們希望在 JSON 因某種原因序列化失敗時返回一個空數(shù)組,這里用try?是 OK 的。?
如你所見,我在代碼的最后保留了一個as!(items.copy() as! NSArray)。有時殺死小馬強制轉(zhuǎn)型是 OK 的,如果你真的,真的知道返回的類型不是其他任何東西,就像這里的mutableArray.copy()。可是這種例外十分罕見,只有在你一開始的時候就認真思考過這個用例的情況下才可以接受(當心,如果那匹小馬??死了,你將會受到良心的譴責(zé))。
本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán)