iOS開發(fā)-Swift進(jìn)階之可選類型Optional & Equatable+Comparable協(xié)議瞎领!

swift進(jìn)階總匯

本文主要分析Optional源碼、Equatable+Comparable協(xié)議


Optional分析

swift中的可選類型(Optional),用于處理值缺失的情況怎棱,有以下兩種情況

  • 有值,且等于x

  • 沒有值

這點(diǎn)可以通過swift-source->Optional.swift源碼(CMD+P绷跑,搜索Optional)源碼來印證

@frozen
public enum Optional<Wrapped>: ExpressibleByNilLiteral {
    ......
  //為nil
  case none

    ......
  //有值
  case some(Wrapped)
  
  ......
}
  • 通過源碼可知拳恋,Optional的本質(zhì)是enum,當(dāng)前枚舉接收一個(gè)泛型參數(shù)Wrapped砸捏,當(dāng)前Some的關(guān)聯(lián)值就是當(dāng)前的Wrapper纫普,下面兩種寫法完全等價(jià)
var age: Int? = 10
等價(jià)于
var age1: Optional<Int> = Optional(5)
  • 【Optional使用模式匹配】:既然Optional的本質(zhì)是枚舉玩焰,那么也可以使用模式匹配來匹配對(duì)應(yīng)的值,如下所示
//1、聲明一個(gè)可選類型的變量
var age: Int? = 10
//2拌蜘、通過模式匹配來匹配對(duì)應(yīng)的值
switch age{
    case nil:
        print("age 是個(gè)空值")
    case .some(let val):
        print("age的值是\(val)")
}

<!--或者這樣寫-->
switch age{
    case nil:
        print("age 是個(gè)空值")
    case .some(10):
        print("age的值是10")
    default:
        print("unKnow")
}
  • 【Optional解包】:因?yàn)槭?code>Optional類型恩静,當(dāng)我們需要從其中拿到我們想要的值時(shí)理朋,需要對(duì)其進(jìn)行解包眯娱,因?yàn)楫?dāng)前的可選項(xiàng)是對(duì)值做了一層包裝的,有以下兩種方式:

  • if let:如果有值,則會(huì)進(jìn)入if流程

  • guard let:如果為nil滑废,則會(huì)進(jìn)入else流程

  • 1蝗肪、強(qiáng)制解包:好處是省事,壞處是一旦解包的值是nil蠕趁,那么程序就會(huì)崩潰

    強(qiáng)制解包為nil崩潰

  • 2薛闪、通過可選項(xiàng)綁定:判斷當(dāng)前的可選項(xiàng)是否有值

//3、可選項(xiàng)解包
var age: Int? = nil

//3-1俺陋、強(qiáng)制解包
//如果age為nil豁延,則程序崩潰
print(age!)

//3-2、可選值綁定
<!--方式一-->
if let age = age{
    //如果age不為nil腊状,則打印
    print(age)
}
<!--方式二-->
guard let tmp = age else {
    print("age為nil")
    return
}
print(tmp)

可選項(xiàng)綁定總結(jié)

作為一個(gè)開發(fā)者诱咏,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要,這是一個(gè)我的iOS開發(fā)交流群:130 595 548缴挖,不管你是小白還是大牛都?xì)g迎入駐 袋狞,讓我們一起進(jìn)步,共同發(fā)展S澄荨(群內(nèi)會(huì)免費(fèi)提供一些群主收藏的免費(fèi)學(xué)習(xí)書籍資料以及整理好的幾百道面試題和答案文檔9堆臁)

  • 1、使用if let創(chuàng)建的內(nèi)容當(dāng)中age僅僅只能在當(dāng)前if分支的大括號(hào)內(nèi)訪問

  • 2棚点、使用guard let定義的tmp在當(dāng)前大括號(hào)外部也是能訪問的

Equatable協(xié)議

在上面的例子中早处,可以通過==判斷兩個(gè)可選項(xiàng)是否相等,原因是因?yàn)镺ptinal在底層遵循了Equatable協(xié)議

var age: Int? = 10
var age1: Optional<Int> = Optional(5)

age == age1
  • 繼續(xù)回到Optional源碼中瘫析,可以看到Optional遵循了Equatable協(xié)議
extension Optional: Equatable where Wrapped: Equatable {
    
    ......
    
    @inlinable
  public static func ==(lhs: Wrapped?, rhs: Wrapped?) -> Bool {
    switch (lhs, rhs) {
    case let (l?, r?):
      return l == r
    case (nil, nil):
      return true
    default:
      return false
    }
  }
}

swift標(biāo)準(zhǔn)庫中的類型

在swift中的類型砌梆,可以通過遵循Equatable協(xié)議來使用相等運(yùn)算符(==)不等運(yùn)算符(!=)比較兩個(gè)值相等還是不相等,Swift標(biāo)準(zhǔn)庫中絕大多數(shù)類型都默認(rèn)實(shí)現(xiàn)了Equatable協(xié)議

例如下面的例子贬循,對(duì)于Int類型來說咸包,系統(tǒng)默認(rèn)實(shí)現(xiàn)了 ==

var age2: Int = 20
var isEqual = age1 == age2
print(isEqual)

<!--打印結(jié)果-->
false

自定義類型

對(duì)于自定義的類型,如果想實(shí)現(xiàn) ==甘有,應(yīng)該怎么辦呢诉儒?

  • 如果像下面這樣寫,是會(huì)直接報(bào)錯(cuò)的

    報(bào)錯(cuò)示意

  • 可以通過遵循Equatable協(xié)議實(shí)現(xiàn)亏掀,如下所示

//2、自定義類型如何實(shí)現(xiàn)Equatable協(xié)議
struct CJLTeacher: Equatable{
    var age: Int
    var name: String
}
var t = CJLTeacher(age: 18, name: "CJL")
var t1 = CJLTeacher(age: 19, name: "CJL")
print(t == t1)

<!--打印結(jié)果-->
false
//如果將t1中的age改成18泛释,打印結(jié)果是什么
true

為什么呢滤愕?其根本原因是因?yàn)樽袷亓?code>Equatable協(xié)議,系統(tǒng)默認(rèn)幫我們實(shí)現(xiàn)了==方法

  • 查看SIL方法怜校,是否如我們猜想的一樣间影?經(jīng)過驗(yàn)證確實(shí)與我們猜測(cè)結(jié)論是一致的

    SIL驗(yàn)證-1

  • 查看__derived_struct_equals方法的實(shí)現(xiàn)

SIL驗(yàn)證-2

疑問:如果是Class類型呢?

如果像Struct那么寫茄茁,會(huì)報(bào)錯(cuò)魂贬,提示需要自己實(shí)現(xiàn)Equatable協(xié)議的方法

class僅遵守Equatable協(xié)議報(bào)錯(cuò)

  • class中手動(dòng)實(shí)現(xiàn)Equatable協(xié)議的方法
//3巩割、如果是class類型呢?需要手動(dòng)實(shí)現(xiàn)Equatable協(xié)議的方法
class CJLTeacher: Equatable{
    
    var age: Int
    var name: String
    
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    
    static func == (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool {
        return lhs.age == rhs.age && lhs.name == rhs.name
    }
    
}
var t = CJLTeacher(age: 18, name: "CJL")
var t1 = CJLTeacher(age: 19, name: "CJL")
print(t == t1)
  • 如果class中的age和name都是Optional呢付燥?
//4宣谈、如果class中的屬性都是可選類型呢?底層是調(diào)用Optional的==來判斷
class CJLTeacher: Equatable{
    
    var age: Int?
    var name: String?
    
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    
    static func == (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool {
        return lhs.age == rhs.age && lhs.name == rhs.name
    }
}
var t = CJLTeacher(age: 18, name: "CJL")
var t1 = CJLTeacher(age: 19, name: "CJL")
print(t == t1)

查看其SIL文件可以驗(yàn)證這一點(diǎn):底層是通過調(diào)用Optional的==來判斷

class的SIL驗(yàn)證

區(qū)分 == vs ===

  • == 相當(dāng)于 equal to键科,用于判斷兩個(gè)值是否相等

  • === 是用來判斷 兩個(gè)對(duì)象是否是同一個(gè)實(shí)例對(duì)象(即內(nèi)存地址指向是否一致)

class CJLTeacher: Equatable{
    
    var age: Int?
    var name: String?
    
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    
    static func == (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool {
        return lhs.age == rhs.age && lhs.name == rhs.name
    }
}
//===:判斷兩個(gè)對(duì)象是否是同一個(gè)
var t = CJLTeacher(age: 18, name: "CJL")
var t1 = t
t1.age = 20
print(t == t1)

<!--打印結(jié)果-->
true

除了==闻丑,還有!=以及其他的運(yùn)算符

Comparable協(xié)議

除了Equatable,還有Comparable協(xié)議,其中的運(yùn)算符有:< 勋颖、<=嗦嗡、>=、> 饭玲、...侥祭、..<、

public protocol Comparable : Equatable {
    static func < (lhs: Self, rhs: Self) -> Bool
    
    static func <= (lhs: Self, rhs: Self) -> Bool
    
    static func >= (lhs: Self, rhs: Self) -> Bool
    
    static func > (lhs: Self, rhs: Self) -> Bool
}
extension Comparable {
    public static func ... (minimum: Self, maximum: Self) -> ClosedRange<Self>
    ......
}

Struct重寫 < 運(yùn)算符

  • 以struct為例茄厘,遵循Comparable協(xié)議,重寫 < 運(yùn)算符
//1卑硫、struct遵守Comparable協(xié)議
struct CJLTeacher: Comparable{
    
    var age: Int
    var name: String
    
    //重載 < 符號(hào)
    static func < (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool {
        return lhs.age < rhs.age
    }
}
var t = CJLTeacher(age: 18, name: "CJL")
var t1 = CJLTeacher(age: 19, name: "CJL")
print(t < t1)

<!--打印結(jié)果-->
true

?? 空運(yùn)算符

如果當(dāng)前的變量為nil,可以在??返回一個(gè)nil時(shí)的默認(rèn)值

  • 下面例子的打印結(jié)果是什么蚕断?
//?? 空運(yùn)算符
var age: Int? = nil
//?? 等價(jià)于 if le / guard let
print(age ?? 20)


<!--打印結(jié)果-->
20
  • 進(jìn)入Optional源碼,查看??實(shí)現(xiàn)
<!--返回T-->
@_transparent//空運(yùn)算符
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T)
    rethrows -> T {
  switch optional {
  case .some(let value):
    return value
  case .none:
    return try defaultValue()
  }
}

<!--返回T?-->
@_transparent
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?)
    rethrows -> T? {
  switch optional {
  case .some(let value):
    return value
  case .none:
    return try defaultValue()
  }
}

從源碼中分析欢伏,??只有兩種類型,一種是T亿乳,一種是硝拧,主要是與 ?? 后面的返回值有關(guān)(即簡(jiǎn)單來說,就是??后是什么類型葛假,??返回的就是什么類型),如下所示

  • ??后面是age1障陶,而age1的類型是Int?,所以t的類型是 Int?

    ??是Int?

  • 如果??是30呢? -- 類型是Int

    ??是30

  • 如果??是String呢? -- 會(huì)報(bào)錯(cuò)聊训,??要求類型一致(跟是否是可選類型無關(guān))

??是String

可選鏈

可選鏈 則意味著 允許在一個(gè)鏈上來訪問當(dāng)前的屬性/方法,如下所示

//***************6抱究、可選鏈***************
class CJLTeacher{
    var name: String?
    var subject: CJLSubject?
    
}

class CJLSubject {
    var subjectName: String?
    func test(){print("test")}
}

var s = CJLSubject()
var t = CJLTeacher()

//可選鏈訪問屬性
if let tmp = t.subject?.subjectName{
    print("tmp不為nil")
}else{
    print("tmp為nil")
}
//可選鏈訪問方法
t.subject?.test()

運(yùn)行結(jié)果如下,因?yàn)閟為nil带斑,所以屬性和方法都不會(huì)往下執(zhí)行

可選鏈為nil的運(yùn)行結(jié)果

unsafelyUnwrapped(Optional.swift中的)

這個(gè)和強(qiáng)制解包的內(nèi)容是一致的鼓寺,如下所示

//***************7、unsafelyUnwrapped 和強(qiáng)制解包內(nèi)容是一致的
var age: Int? = 30
print(age!)
print(age.unsafelyUnwrapped)

<!--打印結(jié)果-->
30
30


//***************如果age是nil
var age: Int? = 30
print(age!)
print(age.unsafelyUnwrapped)

age是nil的結(jié)果和強(qiáng)制解包一致勋磕,程序會(huì)崩潰

unsafelyUnwrapped為nil的崩潰示意

  • 官方對(duì)其的描述如下

    unsafelyUnwrapped官方說明

    這里的-O妈候,是指target -> Build Setting -> Optimization Level設(shè)置成-O時(shí),如果使用的是age.unsafelyUnwrapped挂滓,則不檢查這個(gè)變量是否為nil苦银,

  • 1、設(shè)置Optimization LevelFastest, Smallest[-Os]

  • 2、edit Scheme -> Run -> Info -> Build Configuration改為release模式幔虏,然后再次運(yùn)行發(fā)現(xiàn)纺念,沒有崩潰,與官方所說是一致的

    根據(jù)官方描述調(diào)測(cè)

區(qū)分as想括、 as? 和 as!

  • as 將類型轉(zhuǎn)換為其他類型
var age: Int = 10

var age1 = age as Any
print(age1)

var age2 = age as AnyObject
print(age2)

<!--打印結(jié)果-->
10
10
  • as? 將類型轉(zhuǎn)換為 其他可選類型
var age: Int = 10
//as?
//as? 不確定類型是Double陷谱,試著轉(zhuǎn)換下,如果轉(zhuǎn)換失敗主胧,則返回nil
var age3 = age as? Double
print(age3)

<!--打印結(jié)果-->
nil

此時(shí)的age3的類型是Double?

as?運(yùn)行結(jié)果

  • as! :強(qiáng)制轉(zhuǎn)換為其他類型
var age: Int = 10
//as! 強(qiáng)制轉(zhuǎn)換為其他類型
var age4 = age as! Double
print(age4)

運(yùn)行結(jié)果如下叭首,會(huì)崩潰

as!崩潰運(yùn)行結(jié)果

SIL分析

查看以下代碼的SIL文件

var age: Int = 10
var age3 = age as? Double
var age4 = age as! Double

as的SIL分析

  • 常規(guī)使用:如果可以明確類型,則可以直接使用as!
//常規(guī)使用
var age: Any = 10
func test(_ age: Any) -> Int{
    return (age as! Int) + 1
}
print(test(age))

<!--打印結(jié)果-->
11

使用建議

  • 如果能確定的類型踪栋,使用 as! 即可

  • 如果是不能確定的焙格,使用 as? 即可

總結(jié)

作為一個(gè)開發(fā)者,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要夷都,這是一個(gè)我的iOS開發(fā)交流群:130 595 548眷唉,不管你是小白還是大牛都?xì)g迎入駐 ,讓我們一起進(jìn)步囤官,共同發(fā)展6簟(群內(nèi)會(huì)免費(fèi)提供一些群主收藏的免費(fèi)學(xué)習(xí)書籍資料以及整理好的幾百道面試題和答案文檔!)

  • Optional的本質(zhì)是enum党饮,所以可以使用模式匹配來匹配Optional的值

  • Optional的解包方式有兩種:

  • 1肝陪、強(qiáng)制解包:一旦為nil,程序會(huì)崩潰

  • 2刑顺、可選值綁定if let (只能在if流程的作用域內(nèi)訪問)氯窍、guard let

  • Equatable協(xié)議:

  • 對(duì)于swift標(biāo)準(zhǔn)庫中的絕大部分類型都默認(rèn)實(shí)現(xiàn)了Equatable協(xié)議

  • 對(duì)于自定義Struct類型,僅需要遵守Equatable協(xié)議

  • 對(duì)于自定義class類型蹲堂,除了需要遵守Equatable協(xié)議狼讨,還需要自己實(shí)現(xiàn)Equatable協(xié)議的方法

  • 區(qū)分 == vs ===

  • == 相當(dāng)于 equal to,用于判斷兩個(gè)值是否相等

  • === 是用來判斷 兩個(gè)對(duì)象是否是同一個(gè)實(shí)例對(duì)象(即內(nèi)存地址指向是否一致)

  • Comparable協(xié)議:

  • 對(duì)于自定義類型柒竞,需要遵循Comparable協(xié)議政供,并重寫運(yùn)算符

  • ??空運(yùn)算符:??只有兩種類型,一種是T朽基,一種是T?布隔,主要是與 ?? 后面的返回值有關(guān)(即簡(jiǎn)單來說,就是??后是什么類型踩晶,??返回的就是什么類型

  • 可選鏈:允許在一個(gè)鏈上來訪問當(dāng)前的屬性/方法执泰,如果為nil,則不會(huì)執(zhí)行?后的屬性/方法

  • unsafelyUnwrapped:與強(qiáng)制解包類似渡蜻,但是如果項(xiàng)目中設(shè)置target -> Build Setting -> Optimization Level設(shè)置成-O時(shí),如果使用的是age.unsafelyUnwrapped,則不檢查這個(gè)變量是否為nil

  • 區(qū)分 as茸苇、as?排苍、 as!

  • as 將類型轉(zhuǎn)換為其他類型

  • as? 將類型轉(zhuǎn)換為 其他可選類型

  • as! 強(qiáng)制轉(zhuǎn)換為其他類型

  • 使用建議:能確定使用as!,不能確定使用as?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末学密,一起剝皮案震驚了整個(gè)濱河市淘衙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌腻暮,老刑警劉巖彤守,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異哭靖,居然都是意外死亡具垫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門试幽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筝蚕,“玉大人,你說我怎么就攤上這事铺坞∑鹂恚” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵济榨,是天一觀的道長(zhǎng)坯沪。 經(jīng)常有香客問我,道長(zhǎng)擒滑,這世上最難降的妖魔是什么腐晾? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮橘忱,結(jié)果婚禮上赴魁,老公的妹妹穿的比我還像新娘。我一直安慰自己钝诚,他們只是感情好颖御,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著凝颇,像睡著了一般潘拱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拧略,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天芦岂,我揣著相機(jī)與錄音,去河邊找鬼垫蛆。 笑死禽最,一個(gè)胖子當(dāng)著我的面吹牛腺怯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播川无,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼呛占,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了懦趋?” 一聲冷哼從身側(cè)響起晾虑,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仅叫,沒想到半個(gè)月后帜篇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诫咱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年笙隙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遂跟。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逃沿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出幻锁,到底是詐尸還是另有隱情凯亮,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布哄尔,位于F島的核電站假消,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏岭接。R本人自食惡果不足惜富拗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鸣戴。 院中可真熱鬧啃沪,春花似錦、人聲如沸窄锅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽入偷。三九已至追驴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間疏之,已是汗流浹背殿雪。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锋爪,地道東北人丙曙。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓爸业,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親河泳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子沃呢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345