一.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
里,也就是硬編碼中
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 enums
和Multi-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)幫我們隱式解包了弓熏,也就是告訴編譯器它是肯定有值的恋谭。這種隱式解包
操作,可以大大減少代碼中的解包操作最常見的用法就是從
Storyboard
或Xib
生成的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.聲明自定義prefix
或postfix
運算符
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)