Swift-09:Optional

通過源碼我們來分析一下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ì)值做了一層包裝的疟呐,有以下兩種方式:
    • 1、強(qiáng)制解包:好處是省事东且,壞處是一旦解包的值是nil启具,那么程序就會(huì)崩潰

      image
    • 2、通過可選項(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)訪問

  • 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ò)的

    image
  • 可以通過遵循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í)與我們猜測結(jié)論是一致的

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

      image

疑問:如果是Class類型呢?

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

image
  • 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的==來判斷

image

區(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)(即簡單來說,就是??后是什么類型饿敲,??返回的就是什么類型),如下所示

  • ??后面是age1妻导,而age1的類型是Int?,所以t的類型是 Int?

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

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

2251862-2dd9e7790b48ce26.jpeg

可選鏈

可選鏈 則意味著 允許在一個(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í)行

image

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

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

    image

    這里的-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)普监,沒有崩潰贵试,與官方所說是一致的

    [圖片上傳失敗...(image-3041bb-1617697894901)]

區(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?

image.png
  • 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ì)崩潰

image

SIL分析

查看以下代碼的SIL文件

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

image
  • 常規(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é)

  • 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)(即簡單來說,就是??后是什么類型酸员,??返回的就是什么類型

  • 可選鏈:允許在一個(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閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異财岔,居然都是意外死亡风皿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門匠璧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桐款,“玉大人,你說我怎么就攤上這事夷恍∧д#” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵裁厅,是天一觀的道長冰沙。 經(jīng)常有香客問我,道長执虹,這世上最難降的妖魔是什么拓挥? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮袋励,結(jié)果婚禮上侥啤,老公的妹妹穿的比我還像新娘当叭。我一直安慰自己,他們只是感情好盖灸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布蚁鳖。 她就那樣靜靜地躺著,像睡著了一般赁炎。 火紅的嫁衣襯著肌膚如雪醉箕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天徙垫,我揣著相機(jī)與錄音讥裤,去河邊找鬼。 笑死姻报,一個(gè)胖子當(dāng)著我的面吹牛己英,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吴旋,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼损肛,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了荣瑟?” 一聲冷哼從身側(cè)響起治拿,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎笆焰,沒想到半個(gè)月后忍啤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仙辟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鳄梅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叠国。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖戴尸,靈堂內(nèi)的尸體忽然破棺而出粟焊,到底是詐尸還是另有隱情,我是刑警寧澤孙蒙,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布项棠,位于F島的核電站,受9級(jí)特大地震影響挎峦,放射性物質(zhì)發(fā)生泄漏香追。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一坦胶、第九天 我趴在偏房一處隱蔽的房頂上張望透典。 院中可真熱鬧晴楔,春花似錦、人聲如沸峭咒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凑队。三九已至则果,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間漩氨,已是汗流浹背西壮。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留才菠,地道東北人茸时。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像赋访,于是被迫代替她去往敵國和親可都。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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