Swift -- 5.Enum&Optional&運算符

一.Enum

1.Enum基本信息

Swift中通過enum關(guān)鍵字來聲明一個枚舉

enum LGEnum {
    case one
    case two
    case three
}

C或者OC中默認(rèn)受整數(shù)支持,也就意味著下面的例子中:A,B,C分別默認(rèn)代表0,1,2

typedef NS_ENUM(NSInteger, LGEnum) {
    A,
    B,
    C
};

Swift中的枚舉則更加靈活蒋搜,并且不需要給枚舉中的每一個成員都提供值(所謂“原始”值)篡撵。這個值可以是字符串、字符豆挽、任意的整數(shù)值育谬,或者浮點類型。

enum Color: String {
    case red = "Red"
    case green = "Green"
    case blue = "Blue"
}

enum LGEnum: Double {
    case one = 10.0
    case two = 20.0
    case three = 30.0
    case four = 40.0
}

隱式RawValue分配是建立在Swift類型推斷機(jī)制上的

enum Week: Int {
    case mon, tue, wed, thu, fri = 10, sat, sun
}

print(Week.mon.rawValue) //0
print(Week.tue.rawValue) //1
print(Week.wed.rawValue) //2
print(Week.thu.rawValue) //3
print(Week.fri.rawValue) //10
print(Week.sat.rawValue) //11
print(Week.sun.rawValue) //12
  • 原始值是從0,1,2,3開始的帮哈,和OC一致
  • 當(dāng)指定原始值后膛檀,后面數(shù)據(jù)會從指定原始值做累加操作

RawValue類型Int改為String

enum Week: String {
    case mon, tue, wed, thu, fri = "10", sat, sun
}

print(Week.mon.rawValue) //mon
print(Week.tue.rawValue) //tue
print(Week.wed.rawValue) //wed
print(Week.thu.rawValue) //thu
print(Week.fri.rawValue) //10
print(Week.sat.rawValue) //sat
print(Week.sun.rawValue) //sun
  • 編譯器默認(rèn)給每個枚舉成員分配了一個原始值,也就是枚舉成員字符串

通過SIL分析rawValue原始值默認(rèn)為枚舉成員字符串的原因

Swift代碼

enum Week: String {
    case mon, tue, wed, thu, fri = "10", sat, sun
}

let m = Week.mon.rawValue

SIL代碼

//關(guān)于枚舉的定義
enum Week : String {
  case mon, tue, wed, thu, fri, sat, sun
  init?(rawValue: String) //可失敗的初始化器
  typealias RawValue = String //給String起了個別名RawValue
  var rawValue: String { get } //獲取raValue其實就是獲取rawValue的get方法
}

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main1mSSvp                     // id: %2
  %3 = global_addr @$s4main1mSSvp : $*String      // user: %8
  %4 = metatype $@thin Week.Type

  //%5其實就是聲明了一個Week.mon的參數(shù)
  %5 = enum $Week, #Week.mon!enumelt              // user: %7
  // function_ref Week.rawValue.getter
  %6 = function_ref @$s4main4WeekO8rawValueSSvg : $@convention(method) (Week) -> @owned String // user: %7

  //傳入%5(Week.mon)返回rawValue
  %7 = apply %6(%5) : $@convention(method) (Week) -> @owned String // user: %8
  store %7 to %3 : $*String                       // id: %8
  %9 = integer_literal $Builtin.Int32, 0          // user: %10
  %10 = struct $Int32 (%9 : $Builtin.Int32)       // user: %11
  return %10 : $Int32                             // id: %11
} // end sil function 'main'


// rawValue的getter方法
// Week.rawValue.getter
sil hidden @$s4main4WeekO8rawValueSSvg : $@convention(method) (Week) -> @owned String {
// %0 "self"                                      // users: %2, %1
bb0(%0 : $Week):
  debug_value %0 : $Week, let, name "self", argno 1 // id: %1

  //switch_enum但汞,模式匹配宿刮。這里邏輯其實很簡單就是,匹配傳入的枚舉值來執(zhí)行不同的代碼塊
  switch_enum %0 : $Week, case #Week.mon!enumelt: bb1, case #Week.tue!enumelt: bb2, case #Week.wed!enumelt: bb3, case #Week.thu!enumelt: bb4, case #Week.fri!enumelt: bb5, case #Week.sat!enumelt: bb6, case #Week.sun!enumelt: bb7 // id: %2

//bb1代碼塊的邏輯非常簡單私蕾,就是創(chuàng)建了一個字符串常量"mon"僵缺,返回回去。
//其它的代碼塊邏輯與bb1一致
bb1:                                              // Preds: bb0
  %3 = string_literal utf8 "mon"                  // user: %8
  %4 = integer_literal $Builtin.Word, 3           // user: %8
  %5 = integer_literal $Builtin.Int1, -1          // user: %8
  %6 = metatype $@thin String.Type                // user: %8
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %7 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %8
  %8 = apply %7(%3, %4, %5, %6) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %9
  br bb8(%8 : $String)                            // id: %9

bb2:                                              // Preds: bb0
  %10 = string_literal utf8 "tue"                 // user: %15
  %11 = integer_literal $Builtin.Word, 3          // user: %15
  %12 = integer_literal $Builtin.Int1, -1         // user: %15
  %13 = metatype $@thin String.Type               // user: %15
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %14 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %15
  %15 = apply %14(%10, %11, %12, %13) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %16
  br bb8(%15 : $String)                           // id: %16

}

關(guān)于SIL中的常量mon踩叭,也就是case值對應(yīng)的常量存放在Mach-o的哪個位置

  • 其實這個問題很簡單磕潮,常量字符串肯定存放在__TEXT.__cstring里,也就是硬編碼中
__TEXT__cstring

Arm64匯編驗證mon的存放位置

基地址為0x0000000100498000

mon在內(nèi)存中的地址為0x0000000100498000 + 0x7D48 = 0x10049FD48

進(jìn)入?yún)R編調(diào)試

  • 此時生成字符串傳入的參數(shù)就是x0容贝,也就是mon在內(nèi)存中的地址0x000000010049fd48

2.Enum原始值&枚舉值

//枚舉值(Week類型)
print(Week.mon)
//原始值(String類型)
print(Week.mon.rawValue)
//通過原始值創(chuàng)建枚舉值
print(Week(rawValue: "mon")!)

SIL分析Week.init


  // function_ref _allocateUninitializedArray<A>(_:)
  // _allocateUninitializedArray自脯,分配一個連續(xù)的內(nèi)存空間
  %5 = function_ref @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %6

  //StaticString,創(chuàng)建連續(xù)的內(nèi)存空間來存儲case與之匹配的字符串
  %6 = apply %5<StaticString>(%4) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %8, %7

  //匹配傳進(jìn)來的rawValue斤富,返回枚舉值
  • 原理就是從一個連續(xù)的內(nèi)存空間存儲case與之匹配的字符串

3.Enum關(guān)聯(lián)值

enum Shape {
    case circle(radious: Double)
    case rectangle(width: Double, height: Double)
}

let shape = Shape.circle(radious: 10)

switch shape {
    case .circle(let radious):
        print("Circle radious: \(radious)") // Circle radious: 10.0
    case .rectangle(let width, var height): //這里也使用使用var
        height += 10.0
        print("Rectangle width: \(width) height:\(height)")
    default:
        print("other shape")
}

4.Enum的其它用法

//3.可以遵循協(xié)議
protocol ShapeProtocol {
    func getPerimeter() -> Double
}

extension Shape: ShapeProtocol {
    func getPerimeter() -> Double {
        switch cirCleshape {
            case .circle(let radious):
                return 2 * Double.pi * radious
            case let .rectangle(width, height):
                return 2 * (width + height)
        }
    }
}

//4.可以使用extension擴(kuò)展方法
extension Shape {
    func printInfo() {
        switch cirCleshape {
            case .circle(let radious):
                print("Circle radious: \(radious)") // Circle radious: 10.0
            case .rectangle(let width, var height): //這里也使用使用var
                height += 10.0
                print("Rectangle width: \(width) height:\(height)")
        }
    }
}

enum Shape {
    
    case circle(radious: Double)
    case rectangle(width: Double, height: Double)
    
    //2.計算屬性膏潮,但是枚舉不能添加屬性
    var area: Double {
        get {
            switch self {
            case .circle(let radious):
                return Double.pi * radious * radious
            case .rectangle(let width, let height):
                return width * height
            }
        }
        set {
            switch self {
            case .circle:
                self = Shape.circle(radious: sqrt(newValue/Double.pi))
            case .rectangle:
                self = Shape.circle(radious: sqrt(newValue/Double.pi))
            }
        }
    }
    
    //1.異變方法,修改自身
    mutating func changeShape(shape: Shape) {
        self = shape
    }
}

5.枚舉的大小

1.No-Payload enums也就是沒有關(guān)聯(lián)值的枚舉

enum Week {
    case Mon
    case Tue
    case Wed
    case Thu
    case Fri
    case Sat
    case Sun
}

print(MemoryLayout<Week>.size) //1
print(MemoryLayout<Week>.stride) //1
print(MemoryLayout<Week>.alignment) //1
  • 默認(rèn)以UInt8去存枚舉值满力,因此枚舉值大小/步長/對齊方式都是1字節(jié)
  • 最多能存256個case焕参,如果枚舉值超了,此時的UInt8會升為UInt16油额,當(dāng)然在實際開發(fā)中也不可能會有那么多枚舉值

通過LLDB讀取枚舉在內(nèi)存中的值

(lldb) frame variable -L a
0x00000001000080d9: (swiftTest.Week) a = Mon
(lldb) x/b 0x00000001000080d9
0x1000080d9: 0x00
(lldb) frame variable -L b
0x00000001000080da: (swiftTest.Week) b = Tue
(lldb) x/b 0x00000001000080da
0x1000080da: 0x01
(lldb) frame variable -L c
0x00000001000080db: (swiftTest.Week) c = Wed
(lldb) x/b 0x00000001000080db
0x1000080db: 0x02
(lldb) 

2.Single-Playload enums只有一個成員負(fù)載

enum LGEnum {
    case one(Bool)
    case two
    case three
    case four
}

/*
 分析為什么掛載了一個負(fù)載Bool叠纷,還是一字節(jié),和未掛載一樣
 我們知道1字節(jié)為8位0b00000000潦嘶,最大可存256個值(Uint8)涩嚣,也就是0~255
 掛載值Bool在內(nèi)存中只會占取1位,因此還有剩下的7位去存放我們的枚舉值掂僵。(128個)
 因此當(dāng)枚舉的case小于128時航厚,會用Int8去存。當(dāng)然如果大于等于128锰蓬,此時的Int8就會升級成Int16
 */
print(MemoryLayout<LGEnum>.size) //1
print(MemoryLayout<LGEnum>.stride) //1
print(MemoryLayout<LGEnum>.alignment) //1

enum LGEnum_Int {
    case one(Int)
    case two
    case three
    case four
}

/*
 分析為什么掛載了Int阶淘,占用了9字節(jié)
 因為Int占用8字節(jié),不能與case值占用的1字節(jié)共用互妓。因此需要額外的8字節(jié)來存儲Int
 所以就是8+1=9字節(jié)
 stride步長,也就是對齊后的大小,這里是8字節(jié)對齊冯勉,因此內(nèi)存對齊后就是16
 aligment澈蚌,對齊大小,當(dāng)前這里就是Int的大小8字節(jié)
 */
print(MemoryLayout<LGEnum_Int>.size) //9
print(MemoryLayout<LGEnum_Int>.stride) //16
print(MemoryLayout<LGEnum_Int>.alignment) //8

3.關(guān)于Single-Playload enums多個關(guān)聯(lián)值內(nèi)存對齊問題

enum Enum_Aligment {
    case one(Int,Int,Bool,Int)
}

print(MemoryLayout<Enum_Aligment>.size) //32
print(MemoryLayout<Enum_Aligment>.stride) //32

enum Enum_AligmentTwo {
    case one(Int,Int,Int,Bool)
}

print(MemoryLayout<Enum_AligmentTwo>.size) //25
print(MemoryLayout<Enum_AligmentTwo>.stride) //32

/*
 這里的步長其實很好理解灼狰,也就是基于8字節(jié)對齊后的大小
 
 這里的Bool位置不同宛瞄,影響這size的大小,那么這里其實也好理解交胚,原理和結(jié)構(gòu)體內(nèi)存對齊是一回事份汗。
 
 對于Enum_Aligment來說,前2個Int開辟了16字節(jié)內(nèi)存大小蝴簇,來到第三個Bool值杯活,開辟了1字節(jié)的內(nèi)存大小存放Bool。
 當(dāng)來到第4個Int時熬词,此時的index不能滿足被8整除旁钧,因此會向后偏移到能被8整除的下標(biāo)才能存放Int。
 所以Bool和最后一個Int中間還有7字節(jié)的剩余空間來存放case值互拾。所以size為32
 
 對于Enum_AligmentTwo來說歪今,前3個Int都好理解,開辟24字節(jié)內(nèi)存空間存放颜矿。然后開辟1字節(jié)存放Bool也是沒問題寄猩,所以size為25
 */

4.Mutil-Playload enums

enum LGEnum_Multi {
    case one(Bool)
    case two(Bool)
    case three
    case four
    case five
    case six
}

//探究關(guān)于內(nèi)存
//tag Index(低4位)
//tag Value(高4位)

//暫時還沒有在源碼找到關(guān)于枚舉內(nèi)存的規(guī)律
let x1 = LGEnum_Multi.one(false) //00
let x2 = LGEnum_Multi.one(true) //01
let x3 = LGEnum_Multi.two(false) //40
let x4 = LGEnum_Multi.two(true) //41
let x5 = LGEnum_Multi.three //80
let x6 = LGEnum_Multi.four //81
let x7 = LGEnum_Multi.five //c0
let x8 = LGEnum_Multi.six //c1

/*
 其實看到x1~x8,大概可以總結(jié)一個規(guī)律骑疆。當(dāng)然沒有在源碼中找到驗證
 如果掛載了Bool值來說
 那么對于同一個tagValue來說田篇,低4位的0/1來判斷是否有關(guān)聯(lián)值
 如果沒有掛載的話,就是80到81封断,也就是上面的x5和x6
 有掛載值的tagValue斯辰,相鄰類型是差距了4倍。也就是x1(00)-x3(40)
 沒有掛載值的tagValue坡疼,如果前一個內(nèi)存低4位是0彬呻,將低4位加1存入下一個枚舉值。也就是x5(80)-x6(81)
 如果前一個類型內(nèi)存低4位是1柄瑰,相鄰類型是差了2倍闸氮。也就是x6(81)-x7(c0)
 */

enum LGEnum_Multi_Int {
    case one(Int)
    case two(Int)
    case three
    case four
}


let i1 = LGEnum_Multi_Int.one(10) // 0x10000c118: 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
let i2 = LGEnum_Multi_Int.one(30) // 0x10000c128: 1e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
let i3 = LGEnum_Multi_Int.three // 0x10000c138: 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00

/*
 這里的前8字節(jié)都是存放的Int,也就是掛載值
 偏移8字節(jié)開始存放的是case值教沾,也就是枚舉值蒲跨。這里可以看到i1和i2都是0,而i3的枚舉值卻是0x2
 在源碼中也沒有找到關(guān)于枚舉在內(nèi)存中是怎么存儲中
 */

5.關(guān)于Single-Playload enumsMulti-Playload enums的源碼

Enum.cpp

void
swift::swift_initEnumMetadataSinglePayload(EnumMetadata *self,
                                           EnumLayoutFlags layoutFlags,
                                           const TypeLayout *payloadLayout,
                                           unsigned emptyCases) {
  size_t payloadSize = payloadLayout->size;
  //獲取掛載額外的bits空間
  unsigned payloadNumExtraInhabitants
    = payloadLayout->getNumExtraInhabitants();
  
  //沒有使用的額外存儲空間
  unsigned unusedExtraInhabitants = 0;
  
  // If there are enough extra inhabitants for all of the cases, then the size
  // of the enum is the same as its payload.
  size_t size;
  //如果額外的存儲空間>=case占用的空間
  if (payloadNumExtraInhabitants >= emptyCases) {
    size = payloadSize;
    //沒有使用的額外存儲空間 = 額外的剩余空間 - case占用的空間
    unusedExtraInhabitants = payloadNumExtraInhabitants - emptyCases;
  } else {
    //這里相當(dāng)于是掛載了一個Int授翻, 8 + 1 = 9
    size = payloadSize + getEnumTagCounts(payloadSize,
                                      emptyCases - payloadNumExtraInhabitants,
                                        1 /*payload case*/).numTagBytes; ;
  }

  auto vwtable = getMutableVWTableForInit(self, layoutFlags);
  
  //關(guān)于內(nèi)存對齊
  size_t align = payloadLayout->flags.getAlignment();
  ...

}

void
swift::swift_initEnumMetadataMultiPayload(EnumMetadata *enumType,
                                     EnumLayoutFlags layoutFlags,
                                     unsigned numPayloads,
                                     const TypeLayout * const *payloadLayouts) {
  // Accumulate the layout requirements of the payloads.
  size_t payloadSize = 0, alignMask = 0;
  bool isPOD = true, isBT = true;
  for (unsigned i = 0; i < numPayloads; ++i) {
    const TypeLayout *payloadLayout = payloadLayouts[i];
    payloadSize
      = std::max(payloadSize, (size_t)payloadLayout->size);
    alignMask |= payloadLayout->flags.getAlignmentMask();
    isPOD &= payloadLayout->flags.isPOD();
    isBT &= payloadLayout->flags.isBitwiseTakable();
  }

6.關(guān)于一些常用的LLDB指令

 po                 打印信息
 p                  打印詳細(xì)的信息
 bt                 打出堆
 register read      讀取寄存器
 x或memory read     讀取內(nèi)存段
 x/4g               讀取4段8字節(jié)內(nèi)存段
 x/4w               讀取4段4字節(jié)內(nèi)存段
 p/x                以16進(jìn)制打印
 p/t                以二進(jìn)制打印
 p *$0              打印變量($0)的值
 
 字節(jié)大小
 b
 byte 1字節(jié)
 h
 half word 2字節(jié)
 w
 word 4字節(jié)
 g
 giant word 8字節(jié) 

6.Indirect關(guān)鍵字

1.關(guān)于indirect

/*
 枚舉是值類型或悲,在編譯時期大小就能確定
 但是下列寫法確定不了枚舉的大小
 
 indirect表達(dá)遞歸的枚舉類型孙咪,編譯器會在堆上分配內(nèi)存空間
 */

indirect enum BanaryTree<T> {
    case empty
    case node(left: BanaryTree, right: BanaryTree, value: T)
}


var code = BanaryTree<Int>.node(left: BanaryTree<Int>.empty, right: BanaryTree<Int>.empty, value: 10)

LLDB查看

(lldb) frame variable -L code
0x000000010000c178: (swiftTest.BanaryTree<Int>) code = node {
0x00000001012111a0:   node = {
0x00000001012111a0:     left = empty
0x00000001012111a8:     right = empty
0x00000001012111b0:     value = 10
  }
}
(lldb) x/8g 0x000000010000c178
0x10000c178: 0x0000000101211190 0x0000000100008150
0x10000c188: 0x0000000000000000 0x0000000000000000
0x10000c198: 0x0000000000000000 0x0000000000000000
0x10000c1a8: 0x0000000000000000 0x0000000000000000
(lldb) x/8g 0x0000000101211190
0x101211190: 0x00000001000080a0 0x0000000000000003
0x1012111a0: 0x0000000000000000 0x0000000000000000
0x1012111b0: 0x000000000000000a 0x00037ff843559c20
0x1012111c0: 0x0000000000000007 0x00007ff841d98240
(lldb) 
  • 當(dāng)我們讀取0x0000000101211190時,可以看到讀取的值就是一個HeapObject
  • 也就意味著巡语,我們在枚舉上使用遞歸時翎蹈,加上indirect關(guān)鍵字后,編譯器會在堆區(qū)申請內(nèi)存地址來進(jìn)行存放

SIL分析

SIL中有一個可以看出男公,調(diào)用了alloc_box荤堪,也就是執(zhí)行swift_allocObject

匯編查看是否執(zhí)行了swift_allocObject

2.關(guān)于indirect放到case前面

enum BanaryTree<T> {
    case empty
    indirect case node(left: BanaryTree, right: BanaryTree, value: T)
}
  • 當(dāng)我們把indirect放到case前面時,只有這個case值使用引用類型枢赔,也就是會放到堆空間上存儲

  • 當(dāng)枚舉使用了indirect關(guān)鍵字后澄阳,整個枚舉會變?yōu)橐妙愋停簿褪窃诙芽臻g存儲的

二.Optional

1.認(rèn)識可選值

class LGTeacher {
    var age: Int?
}

當(dāng)前的age就稱為可選值

var age: Int? 等同于 var age: Optional<Int>

2.Optional的本質(zhì)

Optional.swift

@frozen
public enum Optional<Wrapped>: ExpressibleByNilLiteral {
  case none
  case some(Wrapped)
}
  • 實際上就是一個枚舉中踏拜,關(guān)聯(lián)一個值

根據(jù)源碼使用自己的MyOptional

enum MyOptional<Value> {
    case none
    case some(Value)
}

func getOddValue(value:Int) -> MyOptional<Int> {
    if value % 2 == 0 {
        return .some(value)
    }else {
        return .none
    }
}

var arr = [0, 1, 2, 3, 4, 5]

for element in arr {
    let value = getOddValue(value: element)
    switch value {
    case .some(let value):
        print("Odd: \(value)")
    case .none:
        break
    }
}
  • 通過模式匹配拿出當(dāng)前的值

  • 如果將MyOptional<Int>改為Int?碎赢,其它代碼完全不用改變,也能正常使用执隧。通過代碼也印證了Optional的本質(zhì)

3.可選值的綁定(解包)

如果每一個可選值都用模式匹配的方式來獲取值在書寫代碼上就比較繁瑣揩抡,我們還可以使用if let的方式來進(jìn)行可選值的綁定

if let value = value {
        
 }
  • 這里value類型只有是Int?時才能使用if let解包,如果是MyOptional<Int>镀琉,編譯器是不允許這樣做的

當(dāng)然這里還可以使用guard let來解包峦嗤。注意:只能在func中使用

guard let value = value else {
  return
}

4.可選鏈

可選鏈其實就是類似于OC向一個nil對象發(fā)送消息什么都不會發(fā)生。在Swift中借助可選鏈可以達(dá)到類似的效果

let str: String? = "abc"
let upperStr = str?.uppercased()
print(upperStr) //Optional("ABC")

var str1: String?
let upperStr1 = str1?.uppercased()
print(upperStr1) //nil

可選鏈的調(diào)用

let str: String? = "abc"
let upperStr = str?.uppercased().lowercased()
print(upperStr) //Optional("abc")

對于閉包也適用

var closure: (() -> Void)?
closure?() //closure為nil不執(zhí)行

5.??運算符(空合并運算符)

a ?? b對可選類型a進(jìn)行空判斷屋摔,如果a包含一個值就進(jìn)行解包烁设,否則就返回一個默認(rèn)值b

  • 表達(dá)式a必須是Optional類型
  • 默認(rèn)值b的類型必須與a存儲值的類型保持一致
var age: Int?

print(age ?? 0) //0

age = 10

print(age ?? 0) //10

關(guān)于optional源碼中的運算符??

@_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()
  }
}

6.隱式解包

一般在開發(fā)的時候,如果確定某個變量的值在使用的時候是一直存在的钓试,可以使用隱式解包!


int age: Int!

  • 后續(xù)在使用到age時装黑,雖然它是可選值,但是編譯器已經(jīng)幫我們隱式解包了弓熏,也就是告訴編譯器它是肯定有值的恋谭。這種隱式解包操作,可以大大減少代碼中的解包操作

  • 最常見的用法就是從StoryboardXib生成的IBOutlet屬性

三.運算符

1.運算符重載

比如實現(xiàn)2個向量的加挽鞠、減疚颊、負(fù)運算符

  • 運算符重載必須使用static關(guān)鍵字
struct Vector {
    let x: Double
    let y: Double
}

extension Vector {
    //運算符重載必須使用static
    //prefix聲明前綴運算符
    static prefix func - (vector: Vector) -> Vector {
        return Vector(x: -vector.x, y: -vector.y)
    }
    
    static func + (vector0: Vector, vector1: Vector) -> Vector {
        return Vector(x: vector0.x + vector1.x, y: vector0.y + vector1.y)
    }
    
    static func - (vector0: Vector, vector1: Vector) -> Vector {
        return vector0 + -vector1
    }
    
}

let vector0 = Vector(x: 10, y: 10)
let vector1 = Vector(x: 5, y: 5)

print(-vector0) //Vector(x: -10.0, y: -10.0)
print(vector0+vector1) //Vector(x: 15.0, y: 15.0)
print(vector0-vector1) //Vector(x: 5.0, y: 5.0)

2.自定義運算符

官方文檔

  • prefix前綴運算符
  • infix中綴運算符
  • postfix后綴運算符

1.聲明自定義prefixpostfix運算符

struct Vector {
    let x: Double
    let y: Double
}

//1.聲明prefix或postfix自定義運算符

//pow
prefix operator *==
//sqar
postfix operator /==


//2.prefix或postfix實現(xiàn)運算符功能
extension Vector {
    static prefix func *==(vector: Vector) -> Vector {
        return Vector(x: pow(vector.x, 2), y: pow(vector.y, 2))
    }
    
    static postfix func /==(vector: Vector) -> Vector {
        return Vector(x: sqrt(vector.x), y: sqrt(vector.y))
    }
}

print(*==Vector(x: 10, y: 10)) //Vector(x: 100.0, y: 100.0)
print(Vector(x: 100, y: 100)/==) //Vector(x: 10.0, y: 10.0)

2.聲明infix運算符

struct Vector {
    let x: Double
    let y: Double
}

//1.聲明一個`infix`運算符

//實現(xiàn)2次+=
infix operator +=+=

//2.實現(xiàn)運算符功能
extension Vector {
    static func +=+=(vector0: Vector, vector1: Vector) -> Vector {
        return Vector(x: vector0.x + vector1.x + vector1.x, y: vector0.y + vector1.y + vector1.y)
    }
    
}

print(Vector(x: 10, y: 10) +=+= Vector(x: 5, y: 5)) //Vector(x: 20.0, y: 20.0)

如果當(dāng)前自定義運算符類型為infix,還可以指定優(yōu)先級組信认,也就是結(jié)合原則材义。

//也就是將當(dāng)前聲明的運算符遵循一個優(yōu)先級原則,當(dāng)然這個優(yōu)先級組可以是系統(tǒng)的的也可以是自定義的
infix operator +=+=: MyCustomOperator

//也可以遵循加法優(yōu)先級原則
//infix operator +=+=: AdditionPrecedence

//3.自定義優(yōu)先級組
precedencegroup MyCustomOperator {
    //優(yōu)先級高于
    higherThan: AdditionPrecedence
    //優(yōu)先級低于
    lowerThan: MultiplicationPrecedence
    //左結(jié)合
    associativity: left
}

驗證自定義的優(yōu)先級組

struct Vector {
    let x: Double
    let y: Double
}

extension Vector {
    //運算符重載必須使用static
    //prefix聲明前綴運算符
    static prefix func - (vector: Vector) -> Vector {
        return Vector(x: -vector.x, y: -vector.y)
    }
    
    static func + (vector0: Vector, vector1: Vector) -> Vector {
        return Vector(x: vector0.x + vector1.x, y: vector0.y + vector1.y)
    }
    
    static func - (vector0: Vector, vector1: Vector) -> Vector {
        return vector0 + -vector1
    }
}

//1.聲明一個`infix`運算符

//實現(xiàn)2次+=
//也就是將當(dāng)前聲明的運算符遵循一個優(yōu)先級原則嫁赏,當(dāng)然這個優(yōu)先級組可以是系統(tǒng)的的也可以是自定義的
infix operator +=+=: MyCustomOperator

//2.實現(xiàn)運算符功能
extension Vector {
    static func +=+=(vector0: Vector, vector1: Vector) -> Vector {
        return Vector(x: vector0.x + vector1.x + vector1.x, y: vector0.y + vector1.y + vector1.y)
    }
    
}

//3.自定義優(yōu)先級組
precedencegroup MyCustomOperator {
    //優(yōu)先級高于
    higherThan: AdditionPrecedence
    //優(yōu)先級低于
    lowerThan: MultiplicationPrecedence
    //左結(jié)合
    associativity: left
}

let x = Vector(x: 5, y: 5)
let y = Vector(x: 10, y: 10)
//此時的+=+=優(yōu)先級比+搞其掂,因此會先執(zhí)行+=+=
let z = x + y +=+= x

print(z) //Vector(x: 25.0, y: 25.0)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市潦蝇,隨后出現(xiàn)的幾起案子款熬,更是在濱河造成了極大的恐慌深寥,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贤牛,死亡現(xiàn)場離奇詭異翩迈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)盔夜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來堤魁,“玉大人喂链,你說我怎么就攤上這事⊥兹” “怎么了椭微?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盲链。 經(jīng)常有香客問我蝇率,道長,這世上最難降的妖魔是什么刽沾? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任本慕,我火速辦了婚禮,結(jié)果婚禮上侧漓,老公的妹妹穿的比我還像新娘锅尘。我一直安慰自己,他們只是感情好布蔗,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布藤违。 她就那樣靜靜地躺著,像睡著了一般纵揍。 火紅的嫁衣襯著肌膚如雪顿乒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天泽谨,我揣著相機(jī)與錄音璧榄,去河邊找鬼。 笑死隔盛,一個胖子當(dāng)著我的面吹牛犹菱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吮炕,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼腊脱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了龙亲?” 一聲冷哼從身側(cè)響起陕凹,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤悍抑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后杜耙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搜骡,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年佑女,在試婚紗的時候發(fā)現(xiàn)自己被綠了记靡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡团驱,死狀恐怖摸吠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嚎花,我是刑警寧澤寸痢,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站紊选,受9級特大地震影響啼止,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜兵罢,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一献烦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧趣些,春花似錦仿荆、人聲如沸屈扎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茶凳。三九已至舶替,卻和暖如春令境,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背顾瞪。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工舔庶, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人陈醒。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓惕橙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钉跷。 傳聞我的和親對象是個殘疾皇子弥鹦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

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