
一、Enum
1.1 枚舉的基本用法
Swift語(yǔ)言中使用enum關(guān)鍵字來(lái)進(jìn)行枚舉的創(chuàng)建。
///創(chuàng)建一個(gè)姓氏枚舉類型
enum Surname {
case 趙
case 錢
case 孫
case 李
}
上面的代碼創(chuàng)建了一個(gè)姓氏枚舉類型,這個(gè)枚舉類型中定義了4個(gè)枚舉值,分別是趙、錢、孫、李,上面的寫法將4個(gè)枚舉值分別在4個(gè)case語(yǔ)句中定義,開(kāi)發(fā)者也可以在1個(gè)case子句中完成多個(gè)枚舉值的定義,如
///創(chuàng)建一個(gè)姓氏枚舉類型
enum Surname {
case 趙, 錢,孫,李
}
1.2 枚舉的原始值和相關(guān)值
枚舉的原始值特性可以將枚舉值與另一種數(shù)據(jù)類型進(jìn)行綁定,相關(guān)值則可以為枚舉值關(guān)聯(lián)一些其他數(shù)據(jù)。通過(guò)相關(guān)值,開(kāi)發(fā)者可以實(shí)現(xiàn)復(fù)雜的枚舉類型。
1.2.1 枚舉的原始值
swift語(yǔ)言中的枚舉支持開(kāi)發(fā)者聲明一個(gè)原始值類型,并將某個(gè)已經(jīng)存在的類型的值與枚舉值進(jìn)行綁定,枚舉指定原始值類型的語(yǔ)法與繼承的語(yǔ)法有些類似,示例代碼如下:
///為枚舉類型指定一個(gè)原始值類型
enum CharEnum: Character {
///通過(guò)賦值的方式為枚舉值設(shè)置一個(gè)原始值
case a = "a"
case b = "b"
case c = "c"
case d = "d"
}
如果開(kāi)發(fā)者要指定枚舉的原始值類型為Int類型,那么可以只設(shè)置第一個(gè)枚舉值的原始值,其后的枚舉值的原始值會(huì)在第一個(gè)枚舉值原始值的基礎(chǔ)上依次遞增,示例如下:
enum IntEnum: Int {
///第一個(gè)枚舉值的原始值設(shè)置為1
case a = 1
///默認(rèn)原始值為2
case b
///默認(rèn)原始值為3
case c
///默認(rèn)原始值為4
case d
}
通過(guò)枚舉類型中的rawValue屬性來(lái)獲取枚舉的原始值,示例如下:
enum CharEnum: Character {
///通過(guò)賦值的方式為枚舉值設(shè)置一個(gè)原始值
case a = "a"
case b = "b"
case c = "c"
case d = "d"
}
var char = CharEnum.a
var value = char.rawValue
print(value)
隱式RawValue分配是建立在swift的類型推斷機(jī)制上的。
enum DayOfWeek: String {
case mon, tue, wed, thu, fri = "Hello World", sat, sun
}
print(DayOfWeek.mon.rawValue)
print(DayOfWeek.fri.rawValue)
print(DayOfWeek.sat.rawValue)
lldb打印輸出
mon
Hello World
sat
我們當(dāng)前的系統(tǒng)已經(jīng)默認(rèn)給我們的每一個(gè)枚舉值分配了一個(gè)字符串,而這個(gè)字符串其實(shí)跟我們枚舉成員值的字符串是一致的。那么它到底是怎么做到的哪,我們?cè)?strong>SIL文件看一下。
enum DayOfWeek : String {
case mon, tue, wed, thu, fri, sat, sun
init?(rawValue: String)
typealias RawValue = String
var rawValue: String { get }
}
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main1xSSvp // id: %2
%3 = global_addr @$s4main1xSSvp : $*String // user: %8
%4 = metatype $@thin DayOfWeek.Type
%5 = enum $DayOfWeek, #DayOfWeek.mon!enumelt // user: %7
// function_ref DayOfWeek.rawValue.getter
%6 = function_ref @$s4main9DayOfWeekO8rawValueSSvg : $@convention(method) (DayOfWeek) -> @owned String // user: %7
%7 = apply %6(%5) : $@convention(method) (DayOfWeek) -> @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
}

這里就是在訪問(wèn)我們rawValue的getter方法。
sil hidden @$s4main9DayOfWeekO8rawValueSSvg : $@convention(method) (DayOfWeek) -> @owned String {
// %0 "self" // users: %2, %1
bb0(%0 : $DayOfWeek):
debug_value %0 : $DayOfWeek, let, name "self", argno 1 // id: %1
switch_enum %0 : $DayOfWeek, case #DayOfWeek.mon!enumelt: bb1, case #DayOfWeek.tue!enumelt: bb2, case #DayOfWeek.wed!enumelt: bb3, case #DayOfWeek.thu!enumelt: bb4, case #DayOfWeek.fri!enumelt: bb5, case #DayOfWeek.sat!enumelt: bb6, case #DayOfWeek.sun!enumelt: bb7 // id: %2
switch_enum %0 就是我們傳進(jìn)來(lái)的當(dāng)前枚舉成員值mon,接下來(lái)就是一個(gè)模式匹配,匹配我們當(dāng)前的成員值。匹配上了swift case #DayOfWeek.mon!enumelt: bb1代碼塊。
bb1: // Preds: bb0
%3 = string_literal utf8 "mon" // user: %8
那么這個(gè)字符串 "mon" 是從哪里得到的哪?這樣的字符串其實(shí)就是一個(gè)字符串常量,而字符串常量存儲(chǔ)在哪里哪,我們把Mach-o文件拖到MachoView應(yīng)用來(lái)查看一下。

在枚舉變量初始化時(shí),開(kāi)發(fā)者可以使用枚舉類型加點(diǎn)語(yǔ)法的方式,如果這個(gè)枚舉有指定的原始值,也可以通過(guò)枚舉值的原始值來(lái)完成枚舉實(shí)例的構(gòu)造,示例如下:
enum IntEnum: Int {
///第一個(gè)枚舉值的原始值設(shè)置為1
case a = 1
///默認(rèn)原始值為2
case b
///默認(rèn)原始值為3
case c
///默認(rèn)原始值為4
case d
}
///通過(guò)原始值構(gòu)造枚舉變量a
var intEnum = IntEnum(rawValue: 1)
需要注意,通過(guò)原始值進(jìn)行枚舉實(shí)例的構(gòu)造時(shí),是有可能構(gòu)造失敗的,因?yàn)殚_(kāi)發(fā)者傳入的原始值不一定會(huì)對(duì)應(yīng)某一個(gè)枚舉值。因此,這個(gè)方法實(shí)際上返回的是一個(gè)Optional類型的可選值,如果構(gòu)造失敗,則會(huì)返回nil。
1.2.1 枚舉的相關(guān)值
在定義枚舉值的時(shí)候,開(kāi)發(fā)者可以為其設(shè)置一個(gè)參數(shù)列表,這個(gè)參數(shù)列表被稱為枚舉的相關(guān)值。示例如下:
///定義形狀枚舉
enum Shape {
///圓形,設(shè)置圓心和半徑為相關(guān)值
case circle(center: (Double, Double), radius: Double)
///矩形,設(shè)置中心,寬高為相關(guān)值
case rect(center: (Double, Double), width: Double, height: Double)
///三角形,設(shè)置三個(gè)頂點(diǎn)為相關(guān)值
case triangle(point1: (Double, Double), point2: (Double, Double), point3: (Double, Double))
}
在創(chuàng)建相關(guān)值枚舉的時(shí)候,開(kāi)發(fā)者需要提供參數(shù)列表中所需要的參數(shù),示例如下:
省略......
///創(chuàng)建圓形,圓心為(0, 0),半徑為3
var circle = Shape.circle(center: (0, 0), radius: 3)
///創(chuàng)建矩形,中心點(diǎn)為(1, 1), 寬度為10,高度為15
var rect = Shape.rect(center: (1, 1), width: 10, height: 15)
///創(chuàng)建三角形,頂點(diǎn)為(2, 2), (3, 3), (2, 5)
var triangle = Shape.triangle(point1: (2, 2), point2: (3, 3), point3: (2, 5))
1.3 模式匹配
在switch-case結(jié)構(gòu)語(yǔ)句中,匹配到枚舉后,可以通過(guò)參數(shù)捕獲的方式來(lái)獲取枚舉實(shí)例的相關(guān)值,這里捕獲到的相關(guān)值參數(shù)可以在開(kāi)發(fā)者的代碼中使用,示例如下:
///定義形狀枚舉
enum Shape {
///圓形,設(shè)置圓心和半徑為相關(guān)值
case circle(center: (Double, Double), radius: Double)
///矩形,設(shè)置中心,寬高為相關(guān)值
case rect(center: (Double, Double), width: Double, height: Double)
///三角形,設(shè)置三個(gè)頂點(diǎn)為相關(guān)值
case triangle(point1: (Double, Double), point2: (Double, Double), point3: (Double, Double))
}
///創(chuàng)建圓形,圓心為(0, 0),半徑為3
var circle = Shape.circle(center: (0, 0), radius: 3)
///創(chuàng)建矩形,中心點(diǎn)為(1, 1), 寬度為10,高度為15
var rect = Shape.rect(center: (1, 1), width: 10, height: 15)
///創(chuàng)建三角形,頂點(diǎn)為(2, 2), (3, 3), (2, 5)
var triangle = Shape.triangle(point1: (2, 2), point2: (3, 3), point3: (2, 5))
func ShapeFunc(param: Shape) {
switch param {
case let .circle(center, radius):
print("此圓的圓心為:\(center),半徑為:\(radius)")
case let .rect(center, width, height):
print("此矩形的中心為:\(center),寬為:\(width),高為:\(height)")
case let .triangle(point1, point2, point3):
print("此三角形的3個(gè)頂點(diǎn)為:\(point1),\(point2),\(point3)")
}
}
ShapeFunc(param: circle)
ShapeFunc(param: rect)
ShapeFunc(param: triangle)
1.4 枚舉的大小
接下來(lái)我們來(lái)討論一下枚舉占用的內(nèi)存大小,這里我們區(qū)分幾種不同的情況,首先第一種就是 No-payload enums 。
///UInt8
/// 最多可存儲(chǔ)256個(gè)case
enum Week {
case MONDAY
case TUEDAY
case WEDDAY
case THUDAY
case FRIDAY
case SATDAY
case SUNDAY
}
print(MemoryLayout<Week>.size)
打印結(jié)果是1字節(jié)。
可以看到這里我們測(cè)試出來(lái)的不管是 size 還是 stride 都是 1 ,這個(gè)地方我們也很好理解,當(dāng)前的 enum 有幾個(gè) case ? 是不是 8 個(gè)啊,在 Swift 中進(jìn)行枚舉布局的時(shí)候一直是嘗試使用最少的空間來(lái)存儲(chǔ) enum ,對(duì)于當(dāng)前的 case 數(shù)量來(lái)說(shuō), UInt8能夠表示 256 cases ,也就意味著如果一個(gè)默認(rèn)枚舉類型且沒(méi)有關(guān)聯(lián)值的 case 少于 256 ,當(dāng)前枚舉類型的大小都是 1 字節(jié)。

通過(guò)上面的打印我們可以直觀的看到,當(dāng)前變量
a , b , c這三個(gè)變量存儲(chǔ)的內(nèi)容分別 是 00, 01, 02 這和我們上面說(shuō)的布局理解是一致的。No-payload enums 的布局比較簡(jiǎn)單,我們也比較好理解,接下來(lái)我們來(lái)理解一下 Single- payload enums 的內(nèi)存布局, 字面意思就是只有一個(gè)負(fù)載的 enum, 比如下面這個(gè)例子:
enum ZGEnum {
case test_one(Bool)
case test_two
case test_three
case test_four
}
print(MemoryLayout<ZGEnum>.size)
print(MemoryLayout<ZGEnum>.stride)
大家猜一下當(dāng)前的這個(gè)案例, enum 在內(nèi)存中的大小是多少?
1
1
如果我把當(dāng)前的案例換一下,換成如下的案例,那么當(dāng)前 enum 占用的大小又是多少?
enum ZGEnum {
case test_one(Int)
case test_two
case test_three
case test_four
}
print(MemoryLayout<ZGEnum>.size)
print(MemoryLayout<ZGEnum>.stride)
結(jié)果是
9
16
這里我們就產(chǎn)生了疑問(wèn)了,為什么都是單個(gè)負(fù)載,但是當(dāng)前占用的大小卻不一致?
注意, Swift 中的 enum 中的 Single-payload enums 會(huì)使用負(fù)載類型中的額外空間來(lái)記錄沒(méi)有負(fù)載的 case 值。這句話該怎么理解?首先 Bool 類型是 1字節(jié),也就是 UInt8 ,所以當(dāng)前能表達(dá) 256 個(gè) case的情況,對(duì)于 Bool類型來(lái)說(shuō),只需要使用低位的 0, 1 這兩種情況,其 他剩余的空間就可以用來(lái)表示沒(méi)有負(fù)載的 case 值。
enum ZGEnum {
case test_one(Bool)
case test_two
case test_three
case test_four
}
var a = ZGEnum.test_one(false)
var b = ZGEnum.test_one(true)
var c = ZGEnum.test_two
var d = ZGEnum.test_three
var f = ZGEnum.test_four

可以看到,不同的
case 值確實(shí)是按照我們?cè)陂_(kāi)始得出來(lái)的那個(gè)結(jié)論進(jìn)行布局的。
enum ZGEnum {
case test_one(Int)
case test_two
case test_three
case test_four
}
print(MemoryLayout<ZGEnum>.size)
print(MemoryLayout<ZGEnum>.stride)
var a = ZGEnum.test_one(10)
var b = ZGEnum.test_one(20)
var c = ZGEnum.test_two
var d = ZGEnum.test_three
var f = ZGEnum.test_four

對(duì)于 Int 類型的負(fù)載來(lái)說(shuō),其實(shí)系統(tǒng)是沒(méi)有辦法推算當(dāng)前的負(fù)載所要使用的位數(shù),也就意味著當(dāng)前 Int 類型的負(fù)載是沒(méi)有額外的剩余空間的,這個(gè)時(shí)候我們就需要額外開(kāi)辟內(nèi)存空間來(lái)去存儲(chǔ)我們的 case 值,也就是 8 + 1 = 9 字節(jié)。
上面說(shuō)完了 Single-payload enums , 接下來(lái)我們說(shuō)第三種情況 Mutil-payload enums , 有多個(gè)負(fù)載的情況產(chǎn)生時(shí),當(dāng)前的 enum是如何進(jìn)行布局的哪?
enum ZGEnum {
case test_one(Bool)
case test_two(Bool)
case test_three
case test_four
}
print(MemoryLayout<ZGEnum>.size)
print(MemoryLayout<ZGEnum>.stride)
var a = ZGEnum.test_one(false)
var b = ZGEnum.test_two(true)
var c = ZGEnum.test_three
var d = ZGEnum.test_three
var f = ZGEnum.test_four
1
1
上面這個(gè)例子中,我們有兩個(gè) Bool 類型的負(fù)載,這個(gè)時(shí)候我們打印當(dāng)前的 enum 大小 發(fā)現(xiàn)其大小仍然為 1,這個(gè)時(shí)候我們來(lái)看一下內(nèi)存當(dāng)中的存儲(chǔ)情況。

這里我們可以看到當(dāng)前內(nèi)存存儲(chǔ)的分別是 00 41 80 80 81 00 , 這里在存儲(chǔ)當(dāng)前的 case 時(shí)候會(huì)使用到 common spare bits,什么意思?其實(shí)在上一個(gè)案例我們也講過(guò)了,首先 bool 類型需要 1 字節(jié),也就是 8 位。

接下來(lái)我們來(lái)看一下
00 41 80 80 81 00 分別代表的是什么?首先 0, 4, 8 這里我們叫 做 tag value , 0, 1 這里我們就做tag index。當(dāng)前一般來(lái)說(shuō),我們有多個(gè)負(fù)載的枚舉時(shí),當(dāng)前枚舉類型的大小取決于當(dāng)前最大關(guān)聯(lián)值的大小。
1.5 遞歸枚舉
遞歸枚舉其實(shí)就是使用遞歸的方式來(lái)進(jìn)行數(shù)據(jù)描述。使用indirect關(guān)鍵字修飾的枚舉值表示這個(gè)枚舉值是可遞歸的,即此枚舉值中的相關(guān)值可以使用其枚舉類型本身。
enum Expression {
case num(num: Int)
indirect case add(num1: Expression, num2: Expression)
indirect case sub(num1: Expression, num2: Expression)
indirect case mul(num1: Expression, num2: Expression)
indirect case div(num1: Expression, num2: Expression)
}
使用Expression枚舉來(lái)描述復(fù)合表達(dá)式( (5 + 5) * 2 - 8) / 2的代碼如下:
///單值 5
var num5 = Expression.num(num: 5)
/// 表達(dá)式 5 + 5
var exp1 = Expression.add(num1: num5, num2: num5)
///單值 2
var num2 = Expression.num(num: 2)
///表達(dá)式 (5 + 5) * 2
var exp2 = Expression.mul(num1: exp1, num2: num2)
///單值 8
var num8 = Expression.num(num: 8)
///表達(dá)式 (5 + 5) * 2 - 8
var exp3 = Expression.sub(num1: exp2, num2: num8)
///表達(dá)式 ((5 + 5) * 2 - 8) / 2
var expFinal = Expression.div(num1: exp3, num2: num2)
我們可以為這個(gè)四則表達(dá)式枚舉類型Expression實(shí)現(xiàn)一個(gè)函數(shù)來(lái)進(jìn)行運(yùn)算,在開(kāi)發(fā)中將描述與運(yùn)算結(jié)合,能夠編寫出十分優(yōu)美的代碼。處理遞歸枚舉通常會(huì)采用遞歸函數(shù),函數(shù)方法實(shí)現(xiàn)示例如下:
func expressionFunc(num: Expression) -> Int {
switch num {
case let .num(num):
return num
case let .add(num1, num2):
return expressionFunc(num: num1) + expressionFunc(num: num2)
case let .sub(num1, num2):
return expressionFunc(num: num1) - expressionFunc(num: num2)
case let .mul(num1, num2):
return expressionFunc(num: num1) * expressionFunc(num: num2)
case let .div(num1, num2):
return expressionFunc(num: num1) / expressionFunc(num: num2)
}
}
///((5 + 5) * 2 - 8) / 2 打印結(jié)果為6
expressionFunc(num: expFinal)
關(guān)于遞歸枚舉還有一點(diǎn)需要注意,如果一個(gè)枚舉中所有的枚舉值都是可遞歸的,開(kāi)發(fā)者可以直接將整個(gè)枚舉類型聲明為可遞歸的,示例如下:
indirect enum Expression {
case num(num: Int)
case add(num1: Expression, num2: Expression)
case sub(num1: Expression, num2: Expression)
case mul(num1: Expression, num2: Expression)
case div(num1: Expression, num2: Expression)
}
二、Optional
2.1 認(rèn)識(shí)可選值
之前我們?cè)趯懘a的過(guò)程中早就接觸過(guò)可選值,比如我們?cè)诖a這樣定義:
class ZGTeacher {
var age: Int?
}
當(dāng)前的 age 我們就稱之為可選值,那對(duì)于 Optional 的本質(zhì)是什么?我們直接跳轉(zhuǎn)到源碼,打開(kāi) Optional.swift 文件。
public enum Optional<Wrapped>: ExpressibleByNilLiteral {
case none
case some(Wrapped)
}
從源碼可以得知,Optional 的本質(zhì)是枚舉,那么我們也可以仿照系統(tǒng)的實(shí)現(xiàn)制作一個(gè)自己的 Optional。
比如給定任意一個(gè)自然數(shù),如果當(dāng)前自然數(shù)是偶數(shù)返回,否則為 nil,我們應(yīng)該怎么表達(dá)這個(gè)案例。
enum MyOptional<Value> {
case none
case some(Value)
}
func getOddValue(_ value: Int) -> MyOptional<Int> {
if value % 2 == 0 {
return .some(value)
} else {
return .none
}
}
這個(gè)時(shí)候給定一個(gè)數(shù)組,我們想刪除數(shù)組中所有的偶數(shù)

這個(gè)時(shí)候編譯器就會(huì)檢查我們當(dāng)前的 value 會(huì)發(fā)現(xiàn)他的類型和系統(tǒng)編譯器期望的類型不符,這 個(gè)時(shí)候我們就能使用
MyOptional來(lái)限制語(yǔ)法的安全性。于此同時(shí)我們通過(guò)
enum 的模式匹配來(lái)取出對(duì)應(yīng)的值。
for index in array {
let value = getOddValue(index)
switch value {
case .some(let v):
array.remove(at: array.firstIndex(of: v)!)
case .none :
print("value not exit")
}
}
如果我們把上述的返回值更換一下,其實(shí)就和系統(tǒng)的 Optional使用無(wú)疑。
func getOddValue(_ value: Int) -> Int {
if value % 2 == 0 {
return .some(value)
} else {
return .none
}
}
這樣我們其實(shí)是利用當(dāng)前編譯器的類型檢查來(lái)達(dá)到語(yǔ)法書寫層面的安全性。
當(dāng)然如果每一個(gè)可選值都用模式匹配的方式來(lái)獲取值在代碼書寫上就比較繁瑣,我們還可以使 用 if let 的方式來(lái)進(jìn)行可選值綁定。
case .some(let v):
array.remove(at: array.firstIndex(of: v)!)
除了使用 if let 來(lái)處理可選值之外,我們還可以使用 gurad let 來(lái)簡(jiǎn)化我們的代碼。
gurad let 和 if let 剛好相反,gurad let 守護(hù)一定有值。如果沒(méi)有,直接返回。 通常判斷是否有值之后,會(huì)做具體的邏輯實(shí)現(xiàn),通常代碼多 如果用 if let t 憑空多了一層分支, gurad let 是降低分支層次的辦法。
2.2 可選鏈
我們都知道在OC 中我們給一個(gè) nil 對(duì)象發(fā)送消息什么也不會(huì)發(fā)生, Swift 中我們是沒(méi)有辦法向一個(gè) nil 對(duì)象直接發(fā)送消息的,但是借助可選鏈可以達(dá)到類似的效果。我們看下面兩段代碼:
let str: String? = "abc"
let upperStr = str?.uppercased() // Optional<"ABC">
var str1: String?
let upperStr1 = str1?.uppercased() // nil
我們?cè)賮?lái)看下面這段代碼輸出什么?
let str: String? = "zhang"
let upperStr = str?.uppercased().lowercased()
同樣的可選鏈對(duì)于下標(biāo)和函數(shù)調(diào)用也適用。
var closure: ((Int) -> ())?
/// closure 為 nil 不執(zhí)行
closure?(1)
let dict = ["one": 1, "two": 2]
///Optional(1)
var one = dict["one"]
///nil
var three = dict["three"]
print(one, three)
2.3 ?? 運(yùn)算符 (空合并運(yùn)算符)
( a ?? b ) 將對(duì)可選類型 a 進(jìn)行空判斷,如果 a 包含一個(gè)值就進(jìn)行解包,否則就返回 一個(gè)默認(rèn)值 b 。
- 表達(dá)式
a必須是 Optional 類型 - 默認(rèn)值
b的類型必須要和a存儲(chǔ)值的類型保持一致。
var q: Int? = 8
var value: Int
value = q ?? 0
print(value)
2.4 運(yùn)算符重載與自定義
2.4.1 運(yùn)算符重載
讀者在認(rèn)識(shí)重載運(yùn)算符之前,首先應(yīng)該清楚重載的概念。重載的概念最初是針對(duì)函數(shù)的,對(duì)同一個(gè)函數(shù)名設(shè)置不同的參數(shù)類型以實(shí)現(xiàn)不同的功能被稱為函數(shù)的重載。
下面我們自定義一個(gè)圓形的類,通過(guò)重載加號(hào)運(yùn)算符+來(lái)實(shí)現(xiàn)對(duì)圓形類實(shí)例的相加操作。
設(shè)計(jì)圓形類如下,其中有兩個(gè)屬性,分別表示圓形半徑與圓心:
class Circle {
var center: (Double, Double)
var radius: Double
init(center: (Double, Double), radius: Double) {
self.center = center
self.radius = radius
}
}
定義兩個(gè)Circle實(shí)例進(jìn)行相加操作時(shí)應(yīng)執(zhí)行這樣的運(yùn)算:兩個(gè)Circle實(shí)例相加返回一個(gè)新的Circle實(shí)例,并且這個(gè)新的Circle實(shí)例的圓心為兩個(gè)操作數(shù)Circle實(shí)例半徑的和,重載加法運(yùn)算符如下:
func +(param1: Circle, param2: Circle) -> Circle {
return Circle(center: param1.center, radius: param1.radius + param2.radius)
}
比如在開(kāi)發(fā)中我們定義了一個(gè)二維向量,這個(gè)時(shí)候我們想對(duì)兩個(gè)向量進(jìn)行基本的操作,那么我們就可以通過(guò)重載運(yùn)算符來(lái)達(dá)到我們的目的。
struct Vector {
let x: Int
let y: Int
}
extension Vector {
static func + (fistVector: Vector, secondVector: Vector) -> Vector {
return Vector(x: fistVector.x + secondVector.x, y: fistVector.y + secondVector.y)
}
static prefix func - (vector: Vector) -> Vector {
return Vector(x: -vector.x, y: -vector.y)
}
static func - (fistVector: Vector, secondVector: Vector) -> Vector {
return fistVector + -secondVector
}
}
var x = Vector(x: 10, y: 20)
var y = Vector(x: 20, y: 30)
var z = x + y
print(z)
var u = -z
print(u)
輸出打印
Vector(x: 30, y: 50)
Vector(x: -30, y: -50)
2.4.2 自定義運(yùn)算符
開(kāi)發(fā)者可以自定義不存在的運(yùn)算符來(lái)實(shí)現(xiàn)特殊的需求。
自定義運(yùn)算符分為兩個(gè)步驟,首先開(kāi)發(fā)者需要對(duì)要定義的運(yùn)算符進(jìn)行聲明。在聲明運(yùn)算符的結(jié)構(gòu)中,prefix的作用是聲明運(yùn)算符的類型,可以使用prefix關(guān)鍵字將其聲明為前綴運(yùn)算符,使用infix關(guān)鍵字將其聲明為中綴運(yùn)算符,使用postfix關(guān)鍵字將其聲明為后運(yùn)算符。
在進(jìn)行運(yùn)算符的實(shí)現(xiàn)時(shí),后綴和前綴運(yùn)算符只能有一個(gè)參數(shù),參數(shù)在func關(guān)鍵字前需要表明要實(shí)現(xiàn)的運(yùn)算符類型,而中綴運(yùn)算符需要有兩個(gè)參數(shù)且func關(guān)鍵字前不需要額外標(biāo)明,示例如下:
///自定義前綴運(yùn)算符
prefix operator ++
///進(jìn)行自定義運(yùn)算符實(shí)現(xiàn)
prefix func ++(num: Int) -> Int {
return num + 1
}
var a = ++5
///將返回6
print(a)
///自定義中綴運(yùn)算符
infix operator ++
func ++(num1: Int, num2: Int) -> Int {
return num1 * num1 + num2 * num2
}
var b = 5 ++ 4
///將返回41
print(b)
///自定義后綴運(yùn)算符
postfix operator ++
postfix func ++(num: Int) -> Int {
return num + num
}
var c = 5++
///將返回10
print(c)
提示
前綴運(yùn)算符是指只有一個(gè)操作數(shù)且在使用運(yùn)算符進(jìn)行運(yùn)算時(shí),運(yùn)算符需要出現(xiàn)在操作數(shù)的前面;
中綴運(yùn)算符需要有兩個(gè)操作數(shù),且在進(jìn)行運(yùn)算時(shí)運(yùn)算符需要出現(xiàn)在兩個(gè)操作數(shù)的中間;
后綴運(yùn)算符只能有一個(gè)操作數(shù),在運(yùn)算時(shí)后綴運(yùn)算符需要出現(xiàn)在操作數(shù)的后面。
2.4.3 運(yùn)算符的優(yōu)先級(jí)與結(jié)合性
任何運(yùn)算符都有默認(rèn)的優(yōu)先級(jí),開(kāi)發(fā)者自定義的運(yùn)算符也是如此,優(yōu)先級(jí)越高的運(yùn)算符越優(yōu)先執(zhí)行。對(duì)于結(jié)合性而言,由于前綴運(yùn)算符與后綴運(yùn)算符都只有一個(gè)操作數(shù),因此它只對(duì)中綴運(yùn)算符有意義。
在重載運(yùn)算符操作中,并不會(huì)改變?cè)\(yùn)算符的結(jié)合性和優(yōu)先級(jí),但對(duì)于自定義運(yùn)算符,開(kāi)發(fā)者可以設(shè)置其結(jié)合性與優(yōu)先級(jí),示例如下:
precedencegroup customGroup {
higherThan: AdditionPrecedence///優(yōu)先級(jí)比加法運(yùn)算符的優(yōu)先級(jí)高
lowerThan: MultiplicationPrecedence///優(yōu)先級(jí)比乘法運(yùn)算符的優(yōu)先級(jí)低
assignment: true///設(shè)置執(zhí)行可選鏈操作時(shí)的優(yōu)先級(jí)
associativity: left///定義結(jié)合性
}
///定義中綴運(yùn)算符,其屬性由customGroup描述
infix operator +++: customGroup
當(dāng)系統(tǒng)內(nèi)置的優(yōu)先級(jí)組不能滿足我們的要求時(shí),即可使用precedencegroup關(guān)鍵字來(lái)自定義優(yōu)先級(jí)組。
2.5 隱式解析可選類型
隱式解析可選類型是可選類型的一種,使用的過(guò)程中和非可選類型無(wú)異。它們之間唯一的區(qū)別是,隱式解析可選類型是你告訴Swift 編譯器,我在運(yùn)行時(shí)訪問(wèn)時(shí),值不會(huì)為 nil。
var age: Int?
var age1: Int!
age = nil
age1 = nil

其實(shí)日常開(kāi)發(fā)中我們比較常見(jiàn)這種隱士解析可選類型。

IBOutlet類型是Xcode強(qiáng)制為可選類型的,因?yàn)樗皇窃诔跏蓟瘯r(shí)賦值的,而是在加載視圖的時(shí)候。你可以把它設(shè)置為普通可選類型,但是如果這個(gè)視圖加載正確,它是不會(huì)為空的。
2.6 與可選值有關(guān)的高階函數(shù)
- map : 這個(gè)方法接受一個(gè)閉包,如果可選值有內(nèi)容則調(diào)用這個(gè)閉包進(jìn)行轉(zhuǎn)換。
var dict = ["one": "1", "two": "2"]
let result = dict["one"].map{ Int($0) }/// Optional(Optional(1))
上面的代碼中我們從字典中取出字符串”1”,并將其轉(zhuǎn)換為Int類型,但因?yàn)?strong>String轉(zhuǎn)換成 Int不一定能成功,所以返回的是Int?類型,而且字典通過(guò)鍵不一定能取得到值,所以map 返回的也是一個(gè)Optional,所以最后上述代碼result的類型為Int??類型。
那么如果要把我們的雙重可選展平開(kāi)來(lái),這個(gè)時(shí)候我們就需要使用到
- flatMap: 可以把結(jié)果展平成為單個(gè)可選值
var dict = ["one": "1", "two": "2"]
let result = dict["one"].flatMap{ Int($0) } /// Optional(1)
- 注意,這個(gè)方法是作用在
Optioanl的方法,而不是作用在Sequence上的。 - 作用在Sequence上的
flatMap方法在Swift4.1中被更名為compactMap,該方法可以將序列中的nil過(guò)濾出去。
let array = ["1", "2", "3", nil]
let result = array.compactMap{ $0 } // ["1", "2", "3"]
let array1 = ["1", "2", "3", "four"]
let result1 = array1.compactMap{ Int($0) } // [1, 2, 3]
2.7 元類型、AnyClass、Self (self)
- AnyObject: 代表任意類的 instance,類的類型,僅遵守類的協(xié)議。
class ZGTeacher {
var age = 18
}
var t = ZGTeacher()
var t1: AnyObject = t
var t2: AnyObject = ZGTeacher.self
- T.self: T是實(shí)例對(duì)象,當(dāng)前T.self返回的就是他本身;如果 T 是類,當(dāng)前 T.self 返回的就是元類型。
class ZGTeacher {
var age = 18
}
var t = ZGTeacher()
var t1 = t.self
var t2 = t.self.self
var t3 = ZGTeacher.self
lldb打印一下
(lldb) po t1
<ZGTeacher: 0x101c28fc0>
(lldb) po t2
<ZGTeacher: 0x101c28fc0>
(lldb) po t3
ZGTest.ZGTeacher
class ZGTeacher {
var age = 18
}
var t = ZGTeacher()
var t1 = t.self
var t2 = t.self.self
var t3 = ZGTeacher.self

通過(guò)打印我們可以得知,當(dāng)前t3存儲(chǔ)的是我們的
metadata,也就是元類型。
- self:
class ZGTeacher {
var age = 18
func test() {
///當(dāng)前實(shí)例對(duì)象
print(self)
}
static func test1() {
///self 是 ZGTeacher 這個(gè)類型本身
print(self)
}
}
- Self: Self 類型不是特定類型,?是讓您?便地引?當(dāng)前類型,??需重復(fù)或知道該類型的名稱。 在協(xié)議聲明或協(xié)議成員聲明中,Self 類型是指最終符合協(xié)議的類型。可作為?法的返回類型, 作為只讀下標(biāo)的返回類型,作為只讀計(jì)算屬性的類型。
class ZGTeacher {
static let age = 18
func test() -> Self {
///當(dāng)前實(shí)例對(duì)象
return self
}
}
class ZGPerson {
static let age = 0
let age1 = age
var age2 = age
lazy var age3 = Self.age
}
protocol MyProtocol {
func get() -> Self
}
-
Any: 代表任意類的實(shí)例,包括 funcation 類型或者 Optional 類型。
截屏2022-01-14 17.27.57.png AnyClass: 代表任意實(shí)例的類型。
class ZGTeacher {
var age = 18
}
var t: AnyClass = ZGTeacher.self
