Swift如何優(yōu)雅的進(jìn)行解包
前言
對于Swift學(xué)習(xí)而言,可選類型Optional是永遠(yuǎn)繞不過的坎,特別是從OC剛剛轉(zhuǎn)Swift的時候,可能就會被代碼行間的?與!,有的時候甚至是??搞得稀里糊涂的.
這篇文章會給各位帶來我對于可選類型的一些認(rèn)識以及如何進(jìn)行解包,其中會涉及到Swift中if let以及guard let的使用以及思考,還有涉及OC部分的nullable和nonnull兩個關(guān)鍵字,以及一點點對兩種語言的思考.
var num: Int?它是什么類型?
在進(jìn)行解包前,我們先來理解一個概念,這樣可能更有利于對于解包.
首先我們來看看這樣一段代碼:
var num: Int? num = 10 if num is Optional<Int> { print("它是可選類型") }else { print("它是Int類型") }
請先暫時不要把這段代碼復(fù)制到Xcode中,先自問自答,num是什么類型,是Int類型嗎?
好了,你可以將這段代碼復(fù)制到Xcode里去了,然后在IDE中的if上一定會出現(xiàn)這樣一段話:'is' test is always true
不是Int類,它是Optiona類型
那么Optional類型是啥呢--可選類型,具體Optional是啥,點進(jìn)去看看你就知道了.在這里我就不多做解釋了.
var num: Int?這是Optional的聲明,意思不是"我聲明了一個Optional的Int值",而是”我聲明了一個Optional類型值,它可能包含一個Int值,也可能什么都不包含”,也就是說實際上我們聲明的是Optional類型,而不是聲明了一個Int類型!
以此類推String?是什么類型,T?是什么類型,答案各位心中已經(jīng)明了吧.
正是因為num是一個可選類型,所以它才能賦值為nil, var num: Int = nil,這樣是不可能賦值成功的,因為Int類型中沒有nil這個概念!
這就是Swift與OC一個很大區(qū)別,在OC中我們的對象都可以賦值為nil,而在Swift中,能賦值為nil只有Optional類型!
解包的基本思路
使用if let或者guard let,而非強(qiáng)制解包
我們先來看一個簡單的需求,雖然這個需求在實際開發(fā)中意義不太大:
我們需要從網(wǎng)絡(luò)請求獲取到的一個人的身高(cm為單位)以除以100倍,以獲取m為單位的結(jié)果然后將其結(jié)果進(jìn)行返回.
設(shè)計思路:
由于實際網(wǎng)絡(luò)請求中,后臺可能會返回我們的身高為空(即nil),所以在轉(zhuǎn)模型的時候我們不能定義Float類型,而是定義Float?便于接受數(shù)據(jù)
如果身高為nil,那么nil除以100是沒有意義的,在編譯器中Float?除以100會直接報錯,那么其返回值也應(yīng)該為nil,所以函數(shù)的返回值也是Float?類型
那么函數(shù)應(yīng)該設(shè)計成為這個樣子是這樣的:
func getHeight(_ height: Float?) -> Float?
如果一般解包的話,我們的函數(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)制解包固然沒有錯,不過如果在實際開發(fā)中這個height參數(shù)可能還要其他用途,那么是不是每使用一次都要進(jìn)行強(qiáng)制解包?
強(qiáng)制解包是一種很危險的行為,一旦解包失敗,就有崩潰的可能,也許你會說這不是有if判斷,然而實際開發(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 你更傾向使用哪個呢?
在本例子中,其實感覺二者的差別不大,不過我個人更傾向于使用guard let.
原因如下:
在使用if let的時候其大括號類中的情況才是正常情況,而外部主體是非正常情況的返回的nil;
而在使用guard let的時候,guard let else中的大括號是異常情況,而外部主體返回的是正常情況.
對于一個以返回結(jié)果為目的的函數(shù),函數(shù)主體展示正常返回值,而將異常拋出在判斷中,這樣不僅邏輯更清晰,而且更加易于代碼閱讀
解包深入
有這么一個需求,從本地路徑獲取一個json文件,最終將其轉(zhuǎn)為字典,準(zhǔn)備進(jìn)行轉(zhuǎn)模型操作
在這個過程中我們大概有這么幾個步驟:
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)為字典類型
我們可以看到以上幾個函數(shù)中,獲取路徑獲取返回的路徑結(jié)果是一個可選類型,而轉(zhuǎn)Data的方法是拋出異常,JSON序列化也是拋出異常,至于最后一步的類型強(qiáng)轉(zhuǎn)是使用as! [Sting: Any]這樣的操作
這個函數(shù)我是這來進(jìn)行設(shè)計與步驟分解的:
函數(shù)的返回類型為可選類型,因為下面的4步中都有可能失敗進(jìn)而返回nil
雖然有人會說第一步獲取本地路徑,一定是本地有的才會進(jìn)行讀取操作,但是作為一個嚴(yán)謹(jǐn)操作,凡事和字符串打交道的書寫都是有隱患的,所以我這里還是用了guard let進(jìn)行守護(hù)
這個函數(shù)看起來很不簡潔,每一個guard let 后面都跟著一個異常返回,甚至不如使用if let看著簡潔
但是這么寫的好處是:在調(diào)試過程中你可以明確的知道自己哪一步出錯
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不僅可以判斷一個值的解包,而是可以進(jìn)行連續(xù)操作
像下面這種寫法,更加最求的是結(jié)果,對于一般的調(diào)試與學(xué)習(xí),多幾個guard let進(jìn)行拆分,也未嘗不可
至于哪種用法更適合,因人而異
可選鏈的解包
至于可選鏈的解包是完全可以一步到位,假設(shè)我們有以下這個模型
class Person { var phone: Phone? } class Phone { var number: String? }
Person類中有一個手機(jī)對象屬性,手機(jī)類中有個手機(jī)號屬性,現(xiàn)在我們有位小明同學(xué),我們想知道他的手機(jī)號
小明他不一定有手機(jī),可能有手機(jī)而手機(jī)并沒有上手機(jī)號碼
let xiaoming = Person() guard let number = xiaoming.phone?.number else { return }
這里只是拋磚引玉,更長的可選鏈也可以一步到位,而不必一層層進(jìn)行判斷,因為可選鏈中一旦有某個鏈為nil,那么就會返回nil
nullable和nonnull
我們先來看這兩個函數(shù),PHImageManager在OC與Swift中通過PHAsset實例獲取圖片的例子
[[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中閉包返回的是兩個可選類型result: UIImage?與info: [AnyHashable : Any]?
而在OC中返回的類型是 UIImage * _Nullable result, NSDictionary * _Nullable info
注意觀察OC中返回的類型UIImage * 后面使用了_Nullable來修飾,至于Nullable這個單詞是什么意思,我想稍微有點英文基礎(chǔ)的應(yīng)該一看就懂--"可能為空",這不恰恰和Swift的可選類型呼應(yīng)嗎?
另外還有PHFetchResult遍歷這個函數(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兩個關(guān)鍵字出現(xiàn)的原因吧--與Swift做橋接使用以及顯式的提醒對象的狀態(tài)
一點點Swift與OC的語言思考
OC函數(shù)是這樣的
- (NSString *)stringByAppendingString:(NSString *)aString;
Swift中函數(shù)是這樣的
public mutating func append(_ other: String)
僅從API來看,OC的入?yún)⑹呛芪kU的,因為類型是NSString *
那么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)⑹且粋€非可選類型.
基于以上對于代碼的嚴(yán)謹(jǐn)性,所以我才更喜歡使用Swift進(jìn)行編程.
當(dāng)然,Swift的嚴(yán)謹(jǐn)使得它失去部分的靈活性,OC在靈活性上比Swift卓越,但是從安全角度和編碼的長遠(yuǎn)意義看Swift才是現(xiàn)在與未來.
最后想說的是Swift在國內(nèi)的使用并不是很受擁戴,這點很無奈,因為和整個的大環(huán)境有關(guān).
以上就是Swift如何優(yōu)雅的進(jìn)行解包的詳細(xì)內(nèi)容,更多關(guān)于Swift解包的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
swift 3.0中實現(xiàn)字符串截取、比較的方法示例
時,為了使用現(xiàn)有的字符串生成一個新的字符串,我們可以使用截取字符串的方法實現(xiàn)。下面這篇文章主要給大家介紹了關(guān)于swift 3.0中實現(xiàn)字符串截取的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒。2017-10-10Swift map和filter函數(shù)原型基礎(chǔ)示例
這篇文章主要為大家介紹了Swift map和filter函數(shù)原型基礎(chǔ)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07Swift利用Decodable解析JSON的一個小問題詳解
這篇文章主要給大家介紹了關(guān)于Swift利用Decodable解析JSON的一個小問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-04-04Swift在什么情況會發(fā)生內(nèi)存訪問沖突詳解
這篇文章主要給大家介紹了關(guān)于Swift在什么情況會發(fā)生內(nèi)存訪問沖突的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01