Swift編程思想 Part 1: 拯救小馬

原文鏈接: Thinking in Swift, Part 1: Saving ponies
原博客地址:Crunchy Development
原文日期: 2015-09-06
譯者:ray16897188


我惩鞯看見Swift的新手試著將它們的ObjC代碼翻譯成Swift蹋艺。但是開始用Swift寫代碼的時候最難的事情并不是語法,而是思維方式的轉(zhuǎn)變钦铺,去用那些ObjC里并沒有的Swift新概念小泉。

在這一系列的文章中芦疏,我們會拿一個ObjC代碼做例子冕杠,然后在把它轉(zhuǎn)成Swift代碼的全程中引入越來越多的對新概念的講解。

本文的第一部分內(nèi)容:可選類型(optionals)酸茴,對可選類型的強制拆包分预,小馬,if let薪捍,guard和??笼痹。

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

想象一下有多少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)ビ涀∷鼈兓莼伲援斨凳?code>nil的時候把它們強制拆包導(dǎo)致程序崩潰犹芹,把可選類型這個高級特性扔在一邊不用,是很可惜的鞠绰。

絕不應(yīng)該對一個值進行強制拆包腰埂,除非你真的知道你在干什么。記住蜈膨,每次你加一個!去安撫編譯器的時候屿笼,你就屠殺了一匹小馬??。

很可悲翁巍,Xcode是鼓勵犯這種錯誤的驴一,因為error提示到:"value of optional type ‘NSArray?’ not unwrapped. Did you mean to use ! or ?",修改提示建議...你在后面加一個!??灶壶。噢肝断,Xcode,你是有多菜。

我們來拯救這些小馬吧

那么我們該怎樣去避開這些無處不在的糟糕的!呢胸懈?這兒有一些技巧:

  • 使用可選綁定(optional binding)if let x = optional { /* 使用 x */ }
  • as?替換掉as!,前者在轉(zhuǎn)型失敗的時候返回nil担扑;你當然可以把它和if let結(jié)合使用
  • 你也可以用try?替換掉try!,前者在表達式失敗時返回nil1趣钱。

好了涌献,來看看用了這些規(guī)則之后我們的代碼2

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ù)的開始部分就把代碼集中在了對輸入的有效性檢查上军掂,然后在代碼剩下的部分中你就不用再為這些檢查操心了。如果輸入并非所想昨悼,我們就盡早跳出蝗锥,幫助我們專注在事情都如所期的正軌上。

Swift難道不應(yīng)該比ObjC更簡潔么率触?

誘人的蛋糕子虛烏有终议!
誘人的蛋糕子虛烏有!

嗯好吧葱蝗,這代碼好像是比它的ObjC版本更復(fù)雜穴张。但是別愁,在即將到來的本文第二部分中我們會把它大幅度簡化两曼。

但更重要的是皂甘,這段代碼比它ObjC的版本更加安全。實際上ObjC的代碼更短只是因為我們忘了去執(zhí)行一大堆的安全測試悼凑。即使我們的ObjC代碼看起來蠻正常偿枕,它還是會在一些情況中立即崩潰,比如我們給它一個無效的JSON户辫,或者一個并不是由string類型的dictionary的array構(gòu)造出來的東西(比如創(chuàng)建JSON的那個人覺得"icon"這個key值對應(yīng)的就是一個用來提示該條目是否有圖標的Boolean渐夸,而不是一個String...)。在ObjC中我們僅僅是忘了去處理這些用例寸莫,因為ObjC沒有引導(dǎo)我們?nèi)タ紤]這些情況捺萌,而Swift迫使我們?nèi)タ紤]。

所以O(shè)bjC代碼當然更短:因為我們就是忘了去處理所有這些事情。如果你去不防止自己程序崩潰的話桃纯,把代碼寫的更短是很輕松的酷誓。開車的時候不留意路上的障礙當然輕松,但你就是這樣把小馬給撞死的态坦。

結(jié)論

Swift是為了更高的安全性而設(shè)計盐数。不要把所有東西都強制拆包而忽視了可選類型:當你在你的Swift代碼中看見了一個!,你就總是要把它看做是一處代碼異味伞梯,某些事情是要出錯的玫氢。

在即將到來的本文第二部分中,我們會看到怎么讓這個Swift代碼更加簡潔谜诫,并延續(xù)Swift的編程思想:將for循環(huán)和if-let搬走漾峡,替換成mapflatmap

與此同時喻旷,安全駕駛生逸,還有,沒錯且预,拯救小馬槽袄!??


  1. 注意這個try?默默的將error丟棄了:用它的時候你不會知道更多關(guān)于為什么代碼出錯的原因。所以通常來說如果可能的話用do { try ... } catch { }替換掉try?會更好锋谐。但是在我們的例子中遍尺,因為我們希望在JSON因某種原因序列化失敗時返回一個空數(shù)組,這里用try?是OK的涮拗。
  2. 如你所見乾戏,我在代碼的最后保留了一個as!(items.copy() as! NSArray)。有時殺死小馬強制轉(zhuǎn)型是OK的多搀,如果你真的歧蕉,真的知道返回的類型不是其他任何東西,就像這里的mutableArray.copy()康铭」咄耍可是這種例外十分罕見,只有在你一開始的時候就認真思考過這個用例的情況下才可以接受(當心从藤,如果那匹??死了催跪,你將會受到良心的譴責)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末夷野,一起剝皮案震驚了整個濱河市懊蒸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悯搔,老刑警劉巖骑丸,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡通危,警方通過查閱死者的電腦和手機铸豁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來菊碟,“玉大人节芥,你說我怎么就攤上這事∧婧Γ” “怎么了头镊?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長魄幕。 經(jīng)常有香客問我相艇,道長,這世上最難降的妖魔是什么梅垄? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任厂捞,我火速辦了婚禮,結(jié)果婚禮上队丝,老公的妹妹穿的比我還像新娘。我一直安慰自己欲鹏,他們只是感情好机久,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著赔嚎,像睡著了一般膘盖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上尤误,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天侠畔,我揣著相機與錄音,去河邊找鬼损晤。 笑死软棺,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的尤勋。 我是一名探鬼主播喘落,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼最冰!你這毒婦竟也來了瘦棋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤暖哨,失蹤者是張志新(化名)和其女友劉穎赌朋,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡沛慢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年服球,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颠焦。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡斩熊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伐庭,到底是詐尸還是另有隱情粉渠,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布圾另,位于F島的核電站霸株,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏集乔。R本人自食惡果不足惜去件,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扰路。 院中可真熱鬧尤溜,春花似錦、人聲如沸汗唱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哩罪。三九已至授霸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間际插,已是汗流浹背碘耳。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留框弛,地道東北人辛辨。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像功咒,于是被迫代替她去往敵國和親愉阎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 關(guān)于 Swift 重要這個文檔所包含的準備信息, 是關(guān)于開發(fā)的 API 和技術(shù)的力奋。這個信息可能會改變, 根據(jù)這個文...
    無灃閱讀 4,275評論 1 27
  • 132.轉(zhuǎn)換錯誤成可選值 通過轉(zhuǎn)換錯誤成一個可選值,你可以使用 try? 來處理錯誤榜旦。當執(zhí)行try?表達式時,如果...
    無灃閱讀 1,237評論 0 3
  • 1、隨機數(shù) 不需要隨機數(shù)種子 arc4random()%N + begin:產(chǎn)生begin~begin+N的隨機數(shù)...
    我是小胡胡分胡閱讀 4,136評論 0 2
  • 基礎(chǔ)部分(The Basics) 當推斷浮點數(shù)的類型時景殷,Swift 總是會選擇Double而不是Float溅呢。 結(jié)合...
    gamper閱讀 1,263評論 0 7
  • 原文鏈接:http://alisoftware.github.io/swift/2015/09/06/thinki...
    大臉貓121閱讀 1,517評論 0 5