原文鏈接:http://alisoftware.github.io/swift/2015/09/06/thinking-in-swift-1/
我經(jīng)乘泵看到一些剛接觸Swift的小伙伴們嘗試著把他們的ObjC代碼翻譯成Swift颅停。但開始在Swift代碼最困難的事情不是語法,而是改變你的思維方式,使用新的Swift概念是在ObjC中是并沒有的。
假設(shè)你想創(chuàng)建一個條目列表(比如過會兒要顯示在一個tableView里)纸肉,每個條目都有一個圖標(biāo)烧董,標(biāo)題和網(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!),強(qiáng)制轉(zhuǎn)型(value as! String)和強(qiáng)制使用try(try!)返吻。
可選類型是你的朋友:它們很棒乎婿,因為它們能迫使你去思考你的值什么時候是nil,以及在這種情形下你該做什么谢翎。比如”如果沒有圖標(biāo)的話我該顯示什么呢?在我的 TableViewCell 里我該用一個占位符(placeholder)么榨婆?或者用另外一個完全不同的 cell 模板吊宋?”。
這些就是我們在 ObjC 中經(jīng)常忘了考慮進(jìn)去的用例拖吼,但是 Swift 幫助我們?nèi)ビ涀∷鼈冋馕牵援?dāng)值是nil的時候把它們強(qiáng)制拆包導(dǎo)致程序崩潰,把可選類型這個高級特性扔在一邊不用唾糯,是很可惜的移怯。
! ! ! 你絕不應(yīng)該對一個值進(jìn)行強(qiá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;你當(dāng)然可以把它和if let結(jié)合使用
- 你也可以用try?替換掉try!士修,前者在表達(dá)式失敗時返回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中有些機(jī)制能幫我們做簡化:
將多個if let語句合并為一個:if let x = opt1, y = opt2
-
使用guard語句蕊爵,在某個條件不滿足的情況下能讓我們盡早的從一個函數(shù)中跳出來桦山,避免了再去運(yùn)行函數(shù)體剩下的部分。
當(dāng)類型能被推斷出來的時候恒水,我們再用此代碼把這些變量類型去掉來消除冗余 - 比如簡單的用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è)計。不要把所有東西都強(qiáng)制拆包而忽視了可選類型:當(dāng)你在你的 Swift 代碼中看見了一個!交惯,你就應(yīng)該總是要把它看做是一處代碼異味穿仪,某些事情是要出錯的。
注意這個try?默默的將error丟棄了:用它的時候你不會知道為什么代碼出錯的原因只锻。所以通常來說如果可能的話用do { try ... } catch { }替換掉try?會更好紫谷。但是在我們的例子中,因為我們希望在 JSON 因某種原因序列化失敗時返回一個空數(shù)組笤昨,這里用try?是 OK 的。?
如你所見羹膳,我在代碼的最后保留了一個as!(items.copy() as! NSArray)根竿。有時殺死小馬強(qiáng)制轉(zhuǎn)型是 OK 的,如果你真的醒颖,真的知道返回的類型不是其他任何東西壳炎,就像這里的mutableArray.copy()逼侦⊙遥可是這種例外十分罕見,只有在你一開始的時候就認(rèn)真思考過這個用例的情況下才可以接受(當(dāng)心晰赞,如果那匹小馬??死了选侨,你將會受到良心的譴責(zé))。
本文由 SwiftGG 翻譯組翻譯援制,已經(jīng)獲得作者翻譯授權(quán)