Swift優(yōu)雅的進(jìn)行解包

對于Swift學(xué)習(xí)而言,可選類型Optional是永遠(yuǎn)繞不過的坎,特別是從OC剛剛轉(zhuǎn)Swift的時(shí)候,可能就會被代碼行間的?與!,有的時(shí)候甚至是??搞得稀里糊涂的.

這篇文章會給各位帶來我對于可選類型的一些認(rèn)識以及如何進(jìn)行解包,其中會涉及到Swift中if let以及guard let的使用以及思考,還有涉及OC部分的nullablenonnull兩個(gè)關(guān)鍵字,以及一點(diǎn)點(diǎn)對兩種語言的思考.

var num: Int?它是什么類型?

在進(jìn)行解包前,我們先來理解一個(gè)概念,這樣可能更有利于對于解包.

首先我們來看看這樣一段代碼:


var num: Int?

num = 10

if num is Optional<Int> {

 print("它是可選類型")

}else {

 print("它是Int類型")

}

請先暫時(shí)不要把這段代碼復(fù)制到Xcode中,先自問自答,num是什么類型,是Int類型嗎?

好了,你可以將這段代碼復(fù)制到Xcode里去了,然后在IDE中的if上一定會出現(xiàn)這樣一段話:


'is' test is always true

不是Int類,它是Optiona類型

那么Optional類型是啥呢--可選類型,具體Optional是啥,點(diǎn)進(jìn)去看看你就知道了.在這里我就不多做解釋了.

var num: Int?這是Optional的聲明淹禾,意思不是"我聲明了一個(gè)Optional的Int值",而是”我聲明了一個(gè)Optional類型值相满,它可能包含一個(gè)Int值亲轨,也可能什么都不包含”,也就是說實(shí)際上我們聲明的是Optional類型,而不是聲明了一個(gè)Int類型!

以此類推String?是什么類型,T?是什么類型,答案各位心中已經(jīng)明了吧.

正是因?yàn)閚um是一個(gè)可選類型,所以它才能賦值為nil, var num: Int = nil,這樣是不可能賦值成功的,因?yàn)镮nt類型中沒有nil這個(gè)概念!

這就是Swift與OC一個(gè)很大區(qū)別,在OC中我們的對象都可以賦值為nil,而在Swift中,能賦值為nil只有Optional類型!

解包的基本思路,使用if let或者guard let,而非強(qiáng)制解包

我們先來看一個(gè)簡單的需求,雖然這個(gè)需求在實(shí)際開發(fā)中意義不太大:

我們需要從網(wǎng)絡(luò)請求獲取到的一個(gè)人的身高(cm為單位)以除以100倍,以獲取m為單位的結(jié)果然后將其結(jié)果進(jìn)行返回.

設(shè)計(jì)思路:

由于實(shí)際網(wǎng)絡(luò)請求中,后臺可能會返回我們的身高為空(即nil),所以在轉(zhuǎn)模型的時(shí)候我們不能定義Float類型,而是定義Float?便于接受數(shù)據(jù)

如果身高為nil,那么nil除以100是沒有意義的,在編譯器中Float?除以100會直接報(bào)錯(cuò),那么其返回值也應(yīng)該為nil,所以函數(shù)的返回值也是Float?類型

那么函數(shù)應(yīng)該設(shè)計(jì)成為這個(gè)樣子是這樣的:


func getHeight(_ height: Float?) -> Float?

如果一般解包的話,我們的函數(shù)實(shí)現(xiàn)大概會寫成這樣:


func getHeight(_ height: Float?) -> Float? {

 if height != nil {

 return height! / 100

 }

 return nil

}

使用!進(jìn)行強(qiáng)制解包,然后進(jìn)行運(yùn)算

我想說的是使用強(qiáng)制解包固然沒有錯(cuò),不過如果在實(shí)際開發(fā)中這個(gè)height參數(shù)可能還要其他用途,那么是不是每使用一次都要進(jìn)行強(qiáng)制解包?

強(qiáng)制解包是一種很危險(xiǎn)的行為,一旦解包失敗,就有崩潰的可能,也許你會說這不是有if判斷,然而實(shí)際開發(fā)中,情況往往比想的復(fù)雜的多,所以安全的解包行為應(yīng)該是通過if let 或者guard let來進(jìn)行


func getHeight(_ height: Float?) -> Float? {

 if let unwrapedHeight = height {

 return unwrapedHeight / 100

 }

 return nil

}

或者


func getHeight(_ height: Float?) -> Float? {

 guard let unwrapedHeight = height else {

 return nil

 }

 return unwrapedHeight / 100

}

那么if let和guard let 你更傾向使用哪個(gè)呢?

在本例子中,其實(shí)感覺二者的差別不大,不過我個(gè)人更傾向于使用guard let.

原因如下:

在使用if let的時(shí)候其大括號類中的情況才是正常情況,而外部主體是非正常情況的返回的nil;

而在使用guard let的時(shí)候,guard let else中的大括號是異常情況,而外部主體返回的是正常情況.

對于一個(gè)以返回結(jié)果為目的的函數(shù),函數(shù)主體展示正常返回值,而將異常拋出在判斷中,這樣不僅邏輯更清晰,而且更加易于代碼閱讀

解包深入

有這么一個(gè)需求,從本地路徑獲取一個(gè)json文件,最終將其轉(zhuǎn)為字典,準(zhǔn)備進(jìn)行轉(zhuǎn)模型操作

在這個(gè)過程中我們大概有這么幾個(gè)步驟:

  1. 獲取本地路徑

func path(forResource name: String?, ofType ext: String?) -> String?

2. 將本地路徑讀取轉(zhuǎn)為Data

init(contentsOf url: URL, options: Data.ReadingOptions = default) throws

3. JSON序列化

class func jsonObject(with data: Data, options opt: JSONSerialization.ReadingOptions = []) throws -> Any

4. 是否可以轉(zhuǎn)為字典類型

我們可以看到以上幾個(gè)函數(shù)中,獲取路徑獲取返回的路徑結(jié)果是一個(gè)可選類型,而轉(zhuǎn)Data的方法是拋出異常,JSON序列化也是拋出異常,至于最后一步的類型強(qiáng)轉(zhuǎn)是使用as! [Sting: Any]這樣的操作

這個(gè)函數(shù)我是這來進(jìn)行設(shè)計(jì)與步驟分解的:

函數(shù)的返回類型為可選類型,因?yàn)橄旅娴?步中都有可能失敗進(jìn)而返回nil

雖然有人會說第一步獲取本地路徑,一定是本地有的才會進(jìn)行讀取操作,但是作為一個(gè)嚴(yán)謹(jǐn)操作,凡事和字符串打交道的書寫都是有隱患的,所以我這里還是用了guard let進(jìn)行守護(hù)

這個(gè)函數(shù)看起來很不簡潔,每一個(gè)guard let 后面都跟著一個(gè)異常返回,甚至不如使用if let看著簡潔

但是這么寫的好處是:在調(diào)試過程中你可以明確的知道自己哪一步出錯(cuò)


func getDictFromLocal() -> [String: Any]? {

 //1 獲取路徑

 guard let path = Bundle.main.path(forResource: "test", ofType:"json") else {

 return nil

 }

 //2 獲取json文件里面的內(nèi)容

 guard let jsonData = try? Data.init(contentsOf: URL.init(fileURLWithPath: path)) else {

 return nil

 }

 //3 解析json內(nèi)容

 guard let json = try? JSONSerialization.jsonObject(with: jsonData, options:[]) else {

 return nil

 }

 //4 將Any轉(zhuǎn)為Dict

 guard let dict = json as? [String: Any] else {

 return nil

 }

 return dict

}

當(dāng)然,如果你要追求簡潔,這么寫也未嘗不可,一波流帶走


func getDictFromLocal() -> [String: Any]? {

 guard let path = Bundle.main.path(forResource: "test", ofType:"json"),

 let jsonData = try? Data.init(contentsOf: URL.init(fileURLWithPath: path)),

 let json = try? JSONSerialization.jsonObject(with: jsonData, options:[]),

 let dict = json as? [String: Any] else {

 return nil

 }

 return dict

}

guard let與if let不僅可以判斷一個(gè)值的解包,而是可以進(jìn)行連續(xù)操作

像下面這種寫法,更加最求的是結(jié)果,對于一般的調(diào)試與學(xué)習(xí),多幾個(gè)guard let進(jìn)行拆分,也未嘗不可

至于哪種用法更適合,因人而異

可選鏈的解包

至于可選鏈的解包是完全可以一步到位,假設(shè)我們有以下這個(gè)模型


class Person {

 var phone: Phone?

}

class Phone {

 var number: String?

}

Person類中有一個(gè)手機(jī)對象屬性,手機(jī)類中有個(gè)手機(jī)號屬性,現(xiàn)在我們有位小明同學(xué),我們想知道他的手機(jī)號

小明他不一定有手機(jī),可能有手機(jī)而手機(jī)并沒有上手機(jī)號碼


let xiaoming = Person()

guard let number = xiaoming.phone?.number else {

 return

}

這里只是拋磚引玉,更長的可選鏈也可以一步到位,而不必一層層進(jìn)行判斷,因?yàn)榭蛇x鏈中一旦有某個(gè)鏈為nil,那么就會返回nil

nullable和nonnull

我們先來看這兩個(gè)函數(shù),PHImageManager在OC與Swift中通過PHAsset實(shí)例獲取圖片的例子


[[PHImageManager defaultManager] requestImageForAsset:asset 

targetSize:size 

contentMode:PHImageContentModeDefault 

options:options 

resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {

 // 非空才進(jìn)行操作 注意_Nullable,Swift中即為nil,注意判斷

 if (result) {

 }

}];


PHImageManager.default().requestImage(for: asset, 

targetSize: size, 

contentMode: .default, 

options: options, 

resultHandler: { (result: UIImage?, info: [AnyHashable : Any]?) in

 guard let image = result else { return }

})

在Swift中閉包返回的是兩個(gè)可選類型result: UIImage?與info: [AnyHashable : Any]?

而在OC中返回的類型是 UIImage * _Nullable result, NSDictionary * _Nullable info

注意觀察OC中返回的類型UIImage * 后面使用了_Nullable來修飾,至于Nullable這個(gè)單詞是什么意思,我想稍微有點(diǎn)英文基礎(chǔ)的應(yīng)該一看就懂--"可能為空",這不恰恰和Swift的可選類型呼應(yīng)嗎?

另外還有PHFetchResult遍歷這個(gè)函數(shù),我們再來看看在OC與Swift中的表達(dá)


PHFetchResult *fetchResult;

[fetchResult enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

}];


let fetchResult: PHFetchResult

fetchResult.enumerateObjects({ (obj, index, stop) in

})

看見OC中Block中的回調(diào)使用了Nonnull來修飾,即不可能為空,不能為nil,一定有值,對于使用這樣的字符修飾的對象,我們就不必為其做健壯性判斷了.

這也就是nullable與nonnull兩個(gè)關(guān)鍵字出現(xiàn)的原因吧--與Swift做橋接使用以及顯式的提醒對象的狀態(tài)

一點(diǎn)點(diǎn)Swift與OC的語言思考

我之前寫過一篇文章,是說有關(guān)于一個(gè)字符串拼接函數(shù)的

從Swift來反思OC的語法

OC函數(shù)是這樣的


- (NSString *)stringByAppendingString:(NSString *)aString;

Swift中函數(shù)是這樣的


public mutating func append(_ other: String)

僅從API來看,OC的入?yún)⑹呛芪kU(xiǎn)的,因?yàn)轭愋褪荖SString *

那么nil也可以傳入其中,而傳入nil的后果就是崩掉,我覺得對于這種傳入?yún)?shù)為nil會崩掉的函數(shù)需要特別提醒一下,應(yīng)該寫成這樣:


- (NSString *)stringByAppendingString:(NSString * _Nonnull)aString;

或者這樣


- (NSString *)stringByAppendingString:(nonnull NSString *)aString;

以便告訴程序員,入?yún)⒉荒転榭?不能為空,不能為空.重要的事情說三遍!!!

反觀Swift就不會出現(xiàn)這種情況,other后面的類型為String,而不是String?,說明入?yún)⑹且粋€(gè)非可選類型.

基于以上對于代碼的嚴(yán)謹(jǐn)性,所以我才更喜歡使用Swift進(jìn)行編程.

當(dāng)然,Swift的嚴(yán)謹(jǐn)使得它失去部分的靈活性,OC在靈活性上比Swift卓越,但是從安全角度和編碼的長遠(yuǎn)意義看Swift才是現(xiàn)在與未來.

最后想說的是Swift在國內(nèi)的使用并不是很受擁戴,這點(diǎn)很無奈,因?yàn)楹驼麄€(gè)的大環(huán)境有關(guān).

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锥债,一起剝皮案震驚了整個(gè)濱河市深碱,隨后出現(xiàn)的幾起案子锁荔,更是在濱河造成了極大的恐慌瞎领,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件勇劣,死亡現(xiàn)場離奇詭異靖避,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)比默,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門幻捏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人命咐,你說我怎么就攤上這事篡九。” “怎么了醋奠?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵榛臼,是天一觀的道長伊佃。 經(jīng)常有香客問我,道長沛善,這世上最難降的妖魔是什么航揉? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮金刁,結(jié)果婚禮上帅涂,老公的妹妹穿的比我還像新娘。我一直安慰自己尤蛮,他們只是感情好媳友,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著抵屿,像睡著了一般庆锦。 火紅的嫁衣襯著肌膚如雪捅位。 梳的紋絲不亂的頭發(fā)上轧葛,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音艇搀,去河邊找鬼尿扯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛焰雕,可吹牛的內(nèi)容都是我干的衷笋。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼矩屁,長吁一口氣:“原來是場噩夢啊……” “哼辟宗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起吝秕,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤泊脐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后烁峭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體容客,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年约郁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缩挑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鬓梅,死狀恐怖供置,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绽快,我是刑警寧澤芥丧,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布悲关,位于F島的核電站,受9級特大地震影響娄柳,放射性物質(zhì)發(fā)生泄漏寓辱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一赤拒、第九天 我趴在偏房一處隱蔽的房頂上張望秫筏。 院中可真熱鬧,春花似錦挎挖、人聲如沸这敬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崔涂。三九已至,卻和暖如春始衅,著一層夾襖步出監(jiān)牢的瞬間冷蚂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工汛闸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蝙茶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓诸老,卻偏偏與公主長得像隆夯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子别伏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

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