Swift-進(jìn)階:可選類(lèi)型Optional & Equatable+Comparable協(xié)議

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

Optional分析

swift中的可選類(lèi)型(Optional)光稼,用于處理值缺失的情況芯咧,有以下兩種情況

  • 有值,且等于x

  • 沒(méi)有值

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

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

    ......
  //有值
  case some(Wrapped)

  ......
}

  • 通過(guò)源碼可知水孩,Optional的本質(zhì)是enum,當(dāng)前枚舉接收一個(gè)泛型參數(shù)Wrapped琐驴,當(dāng)前Some的關(guān)聯(lián)值就是當(dāng)前的Wrapper俘种,下面兩種寫(xiě)法完全等價(jià)
var age: Int? = 10
等價(jià)于
var age1: Optional<Int> = Optional(5)

  • 【Optional使用模式匹配】:既然Optional的本質(zhì)是枚舉,那么也可以使用模式匹配來(lái)匹配對(duì)應(yīng)的值绝淡,如下所示
//1宙刘、聲明一個(gè)可選類(lèi)型的變量
var age: Int? = 10
//2、通過(guò)模式匹配來(lái)匹配對(duì)應(yīng)的值
switch age{
    case nil:
        print("age 是個(gè)空值")
    case .some(let val):
        print("age的值是\(val)")
}

<!--或者這樣寫(xiě)-->
switch age{
    case nil:
        print("age 是個(gè)空值")
    case .some(10):
        print("age的值是10")
    default:
        print("unKnow")
}

  • 【Optional解包】:因?yàn)槭?code>Optional類(lèi)型牢酵,當(dāng)我們需要從其中拿到我們想要的值時(shí)悬包,需要對(duì)其進(jìn)行解包,因?yàn)楫?dāng)前的可選項(xiàng)是對(duì)值做了一層包裝的馍乙,有以下兩種方式:
    • 1布近、強(qiáng)制解包:好處是省事垫释,壞處是一旦解包的值是nil,那么程序就會(huì)崩潰

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

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

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

//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é)

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

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

Equatable協(xié)議

在上面的例子中,可以通過(guò)==判斷兩個(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)庫(kù)中的類(lèi)型

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

例如下面的例子践宴,對(duì)于Int類(lèi)型來(lái)說(shuō),系統(tǒng)默認(rèn)實(shí)現(xiàn)了 ==

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

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

自定義類(lèi)型

對(duì)于自定義的類(lèi)型爷怀,如果想實(shí)現(xiàn) ==阻肩,應(yīng)該怎么辦呢?

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

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

//2、自定義類(lèi)型如何實(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)過(guò)驗(yàn)證確實(shí)與我們猜測(cè)結(jié)論是一致的

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

疑問(wèn):如果是Class類(lèi)型呢?

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

  • class中手動(dòng)實(shí)現(xiàn)Equatable協(xié)議的方法

//3、如果是class類(lèi)型呢刻诊?需要手動(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中的屬性都是可選類(lèi)型呢则涯?底層是調(diào)用Optional的==來(lá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
    }
}
var t = CJLTeacher(age: 18, name: "CJL")
var t1 = CJLTeacher(age: 19, name: "CJL")
print(t == t1)

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

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

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

  • === 是用來(lái)判斷 兩個(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重寫(xiě) < 運(yùn)算符

  • 以struct為例递礼,遵循Comparable協(xié)議,重寫(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()
  }
}

從源碼中分析辫愉,??只有兩種類(lèi)型,一種是T将硝,一種是恭朗,主要是與 ?? 后面的返回值有關(guān)(即簡(jiǎn)單來(lái)說(shuō),就是??后是什么類(lèi)型依疼,??返回的就是什么類(lèi)型),如下所示

  • ??后面是age1痰腮,而age1的類(lèi)型是Int?,所以t的類(lèi)型是 Int?

    ??是Int?

如果??是30呢? -- 類(lèi)型是Int

  • ??是30
  • 如果??是String呢? -- 會(huì)報(bào)錯(cuò)律罢,??要求類(lèi)型一致(跟是否是可選類(lèi)型無(wú)關(guān))

    ??是String

可選鏈

可選鏈 則意味著 允許在一個(gè)鏈上來(lái)訪問(wèn)當(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()

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

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

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ì)崩潰

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

  • 這里的-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),沒(méi)有崩潰辟癌,與官方所說(shuō)是一致的

區(qū)分as寒屯、 as? 和 as!

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

var age1 = age as Any
print(age1)

var age2 = age as AnyObject
print(age2)

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

  • as? 將類(lèi)型轉(zhuǎn)換為 其他可選類(lèi)型
var age: Int = 10
//as?
//as? 不確定類(lèi)型是Double,試著轉(zhuǎn)換下,如果轉(zhuǎn)換失敗寡夹,則返回nil
var age3 = age as? Double
print(age3)

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

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

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

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

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

SIL分析

查看以下代碼的SIL文件

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

  • 常規(guī)使用:如果可以明確類(lèi)型,則可以直接使用as!

//常規(guī)使用
var age: Any = 10
func test(_ age: Any) -> Int{
    return (age as! Int) + 1
}
print(test(age))

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

使用建議

  • 如果能確定的類(lèi)型菩掏,使用 as! 即可

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

總結(jié)

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

  • Optional的解包方式有兩種:

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

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

  • Equatable協(xié)議:

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

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

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

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

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

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

  • Comparable協(xié)議:

    • 對(duì)于自定義類(lèi)型,需要遵循Comparable協(xié)議系草,并重寫(xiě)運(yùn)算符

    • ??空運(yùn)算符:??只有兩種類(lèi)型,一種是T唆涝,一種是T?,主要是與 ?? 后面的返回值有關(guān)(即簡(jiǎn)單來(lái)說(shuō),就是??后是什么類(lèi)型驯妄,??返回的就是什么類(lèi)型

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

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

  • 區(qū)分 as凡辱、as?戒职、 as!

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

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

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末透乾,一起剝皮案震驚了整個(gè)濱河市洪燥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌乳乌,老刑警劉巖捧韵,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異汉操,居然都是意外死亡再来,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)磷瘤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)芒篷,“玉大人搜变,你說(shuō)我怎么就攤上這事∷蠓ィ” “怎么了痹雅?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)糊识。 經(jīng)常有香客問(wèn)我绩社,道長(zhǎng),這世上最難降的妖魔是什么赂苗? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任愉耙,我火速辦了婚禮,結(jié)果婚禮上拌滋,老公的妹妹穿的比我還像新娘朴沿。我一直安慰自己,他們只是感情好败砂,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布赌渣。 她就那樣靜靜地躺著,像睡著了一般昌犹。 火紅的嫁衣襯著肌膚如雪坚芜。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天斜姥,我揣著相機(jī)與錄音鸿竖,去河邊找鬼。 笑死铸敏,一個(gè)胖子當(dāng)著我的面吹牛缚忧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播杈笔,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼闪水,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蒙具?” 一聲冷哼從身側(cè)響起敦第,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎店量,沒(méi)想到半個(gè)月后芜果,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡融师,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年右钾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡舀射,死狀恐怖窘茁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脆烟,我是刑警寧澤山林,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站邢羔,受9級(jí)特大地震影響驼抹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拜鹤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一框冀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧敏簿,春花似錦明也、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蜻势,卻和暖如春撑刺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咙边。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留次员,地道東北人败许。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像淑蔚,于是被迫代替她去往敵國(guó)和親市殷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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