<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Swift:解包的正確姿勢

          共 6376字,需瀏覽 13分鐘

           ·

          2022-03-01 11:33

          ????關(guān)注后回復(fù)?“進(jìn)群”?,拉你進(jìn)程序員交流群????

          轉(zhuǎn)自:掘金? season_zhu

          https://juejin.cn/post/6931154052776460302

          嗯,先來一段感慨

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

          這篇文章會(huì)給各位帶來我對于可選類型的一些認(rèn)識以及如何進(jìn)行解包,其中會(huì)涉及到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?{

          ???print("它是Optional類型")

          ??}else?{

          ????print("它是Int類型")

          ??}

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

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


          'is'?test?is?always?true

          num不是_Int類,它是Optional類型_。

          那么Optional類型是啥呢--可選類型,具體Optional是啥,Optional類型的本質(zhì)實(shí)際上就是一個(gè)帶有泛型參數(shù)的enum類型,各位去源碼中仔細(xì)看看就能了解到,這個(gè)類型和Swift中的Result類有異曲同工之妙。

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

          至于像Int!或者Int?這種寫法,只是一種Optional類型的糖語法寫法。

          以此類推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ò)請求中,后臺可能會(huì)返回我們的身高為空(即nil),所以在轉(zhuǎn)模型的時(shí)候我們不能定義Float類型,而是定義Float?便于接受數(shù)據(jù)。

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

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


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

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


          ??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)的行為,一旦解包失敗,就有崩潰的可能,也許你會(huì)說這不是有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序列化也是拋出異常,至于最后一步的類型轉(zhuǎn)換是使用as?[Sting: Any]這樣的操作

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

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

          雖然有人會(huì)說第一步獲取本地路徑,一定是本地有的才會(huì)進(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

          ??}
          ??
          ??print(number)

          這里只是拋磚引玉,更長的可選鏈也可以一步到位,而不必一層層進(jìn)行判斷,因?yàn)榭蛇x鏈中一旦有某個(gè)鏈為nil,那么就會(huì)返回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會(huì)崩掉的函數(shù)需要特別提醒一下,應(yīng)該寫成這樣:


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

          ///?或者下面這樣

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

          以便告訴程序員,入?yún)⒉荒転榭眨荒転榭眨荒転榭眨匾氖虑檎f三遍!!!

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

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

          當(dāng)然,Swift的嚴(yán)謹(jǐn)使得它失去部分的靈活性,OC在靈活性上比Swift卓越。

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?面試題?資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點(diǎn)擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點(diǎn)這里好文分享給更多人↓↓

          瀏覽 41
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美大鸡巴视频 | 中文丰满亲子伦 | 成人三级在线观看 | 中文字幕在线观 | 天天操天天摸天天爽 |