
可選項(xiàng)是Swift的一大特色,那么可選項(xiàng)的本質(zhì)是什么呢?
比如下面代碼:

我們從打印結(jié)果可以看到,age是Optional類型,其實(shí)可選項(xiàng)本質(zhì)就是Optional類型.?只是語(yǔ)法糖的一種寫(xiě)法.我們看看Optional類型的本質(zhì)是什么:

可以看到Optional的本質(zhì)就是一個(gè)枚舉類型,有兩個(gè)成員值和一個(gè)初始化方法.既然知道可選項(xiàng)的本質(zhì)是枚舉類型,那上面的代碼可以這樣寫(xiě):

還可以簡(jiǎn)化:

可選項(xiàng)類型綁定
我么知道可選項(xiàng)和if組合使用可以使用可選項(xiàng)綁定來(lái)判斷可選項(xiàng)中是否有值:

可選項(xiàng)和if組合時(shí),如果可選項(xiàng)包裝有值,那么就進(jìn)行解包,并把值賦值給a.
其實(shí)可選項(xiàng)還可以和switch語(yǔ)句組合,也可以使用可選項(xiàng)綁定:

可選項(xiàng)和switch組合時(shí)有兩個(gè)警告,我們先解決第二個(gè)警告:Case is already handled by previous patterns; consider removing it.這個(gè)警告的意思是說(shuō),上一個(gè)case已經(jīng)包含了所有情況,當(dāng)前的case永遠(yuǎn)也不會(huì)執(zhí)行.其實(shí)上面代碼的可選項(xiàng)綁定時(shí)無(wú)條件綁定.不管可選項(xiàng)有沒(méi)有值都會(huì)賦值給a,所以下面代碼就永遠(yuǎn)也不會(huì)執(zhí)行.那么我們要想實(shí)現(xiàn)和if那種效果,有值的時(shí)候解包再賦值,沒(méi)有值的時(shí)候不解包呢?只需要在a后面添加一個(gè)問(wèn)號(hào) ?就可以了:

這樣一來(lái),第一個(gè)警告也消除了.第一個(gè)警告就是直接打印可選項(xiàng)的時(shí)候的警告.現(xiàn)在使用?如果可選項(xiàng)有值就會(huì)解包,所以警告消除.
雙重可選項(xiàng)
既然知道了可選項(xiàng)的本質(zhì)就是枚舉類型:有值是some,nil是none.那么雙重可選項(xiàng)也是一樣的:

高級(jí)運(yùn)算符
溢出運(yùn)算符 &+,&-,&*
我們知道UInt8的取值范圍是0~255,Int8的取值范圍是-128~127.那么如果經(jīng)過(guò)運(yùn)算后的結(jié)果超出了這個(gè)范圍會(huì)怎么樣呢?

可以看到,如果溢出后會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤.所以Swift提供了溢出運(yùn)算符&+,&-,&*

溢出運(yùn)算符的運(yùn)算是循環(huán)的,比如說(shuō)age的值是255, 加 1后就變成了0.
運(yùn)算符重載
如果我們想為系統(tǒng)原有的運(yùn)算符添加額外的功能,可以對(duì)原有的運(yùn)算符進(jìn)行重載.重載就是函數(shù)名相同,參數(shù)不同.功能不同.
系統(tǒng)的運(yùn)算符的本質(zhì)都是方法:

比如說(shuō)+只能對(duì)基本數(shù)據(jù)類型進(jìn)行加法運(yùn)算,如果我們要對(duì)結(jié)構(gòu)體類型進(jìn)行運(yùn)算加法運(yùn)算呢?

可以看到結(jié)構(gòu)體是不支持 + 運(yùn)算的.
所以我們只能對(duì)+進(jìn)行重載,達(dá)到我們的目的:
struct Point{
var x: Int
var y: Int
}
func + (lhs: Point , rhs: Point) -> Point{
Point(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
var p1 = Point(x: 10, y: 20)
var p2 = Point(x: 11, y: 21)
var p3 = p1 + p2
print(p3)
因?yàn)槲覀兪墙o Point 提供的額外的運(yùn)算功能,所以我們應(yīng)該把重載的方法放到 Point 結(jié)構(gòu)體里面:
struct Point{
var x: Int
var y: Int
static func + (lhs: Point , rhs: Point) -> Point{
Point(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
}
重載+ , - ,+= , -= , 取反, ++ , --
// +
static func + (lhs: Point , rhs: Point) -> Point{
Point(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
// -
static func - (lhs: Point , rhs: Point) -> Point{
Point(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
}
//取反
static prefix func - (lhs: inout Point){
lhs = Point(x: -lhs.x, y: -lhs.y)
}
// +=
static func += (lhs: inout Point , rhs: Point){
lhs = lhs + rhs
}
// -=
static func -= (lhs: inout Point , rhs: Point){
lhs = lhs - rhs
}
// ++ 在前
static prefix func ++ (lhs: inout Point){
//先運(yùn)算
lhs += Point(x: 1, y: 1)
}
// ++ 在后
static postfix func ++ (lhs: inout Point) -> Point{
//先保存原來(lái)的
let p = lhs
lhs += Point(x: 1, y: 1)
return p
}
==運(yùn)算符
如果想要比較兩個(gè)Point對(duì)象是否相等可以使用==運(yùn)算符,但是Swift的==運(yùn)算符默認(rèn)不支持結(jié)構(gòu)體類型,所以需要我們重載==運(yùn)算符.

重載==運(yùn)算符有兩步:
- 實(shí)現(xiàn)
Equatable協(xié)議 - 重載
==運(yùn)算符

上圖可以看到,我們把==運(yùn)算符的重載方法注釋了,也就是說(shuō)并沒(méi)有重載==運(yùn)算符.為什么也可以使用==運(yùn)算符呢?
Swift會(huì)為一下三種情況提供默認(rèn)的Equatable實(shí)現(xiàn):
1. 只擁有遵守了 Equatable 協(xié)議的存儲(chǔ)屬性的結(jié)構(gòu)體
2. 只關(guān)聯(lián)了遵守 Equatable 協(xié)議類型的枚舉
3. 沒(méi)有關(guān)聯(lián)值的枚舉
以上三種情況,Swift會(huì)默認(rèn)提供Equatable協(xié)議的實(shí)現(xiàn).Point的兩個(gè)存儲(chǔ)屬性都是Int類型,而Int類型顯然是遵守了Equatable協(xié)議的,所以不用重載==.
沒(méi)有關(guān)聯(lián)類型的枚舉:
enum Season{
case spring
case summer
case autumn
case winter
}
var season1 = Season.spring
var season2 = Season.winter
print(season1 == season2)
關(guān)聯(lián)類型實(shí)現(xiàn)了Equatable協(xié)議:
enum Season: Equatable{
case spring(Int,Int)
case summer
case autumn
case winter
}
var season1 = Season.spring(10, 20)
var season2 = Season.winter
print(season1 == season2)
如果是class類型,必須實(shí)現(xiàn)Equatable協(xié)議,并且重載==方法:
class Person: Equatable{
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
static func == (p1: Person, p2: Person) -> Bool{
p1.age == p2.age
}
}
var person1 = Person(age: 16, name: "Jack")
var person2 = Person(age: 18, name: "Jorn")
print(person1 == person2)
上面Person類可以不實(shí)現(xiàn)Equatable協(xié)議,==方法同樣可以正常使用,但是還是建議實(shí)現(xiàn)Equatable協(xié)議,因?yàn)檫@樣做有兩個(gè)好處:
1. 一眼就可以看出 Person 類是可比較的
2. 適用于泛型限定
比如說(shuō)我們寫(xiě)一個(gè)比較傳進(jìn)來(lái)的泛型參數(shù)是否相等的方法:

可以看到編譯報(bào)錯(cuò),因?yàn)閰?shù)是泛型,傳入的參數(shù)不一定是可比較的.所以必須對(duì)泛型添加限定條件,要求必須是實(shí)現(xiàn)了Equatale協(xié)議的類型.如果我們想讓Person實(shí)例能適用這個(gè)方法,就必須讓Person實(shí)現(xiàn)Equatale協(xié)議:

如果我們實(shí)現(xiàn)了Equatale協(xié)議,重載了==方法,等價(jià)于重載了!=運(yùn)算符,我們可以直接使用!=運(yùn)算符.
如果要比較兩個(gè)引用類型引用的是不是同一個(gè)對(duì)象,要使用===運(yùn)算符:
var person1 = Person(age: 16, name: "Jack")
var person2 = Person(age: 18, name: "Jorn")
print(person1 === person2) // false
==是判斷兩個(gè)對(duì)象是否相等,如果我們相比較大小呢?
如果要比較兩個(gè)對(duì)象大小,要遵守Comparable協(xié)議,然后重載> , >= , < , <=運(yùn)算符.
Comparable里面就聲明了這四個(gè)方法:

自定義運(yùn)算符
如果現(xiàn)有的運(yùn)算符還是不能滿足我們的需求,我們可以根據(jù)自己的需求,自定義運(yùn)算符.
自定義運(yùn)算符時(shí)需要注意兩點(diǎn):
1. 要指明是前綴運(yùn)算符,后綴運(yùn)算符,還是中綴運(yùn)算符
2. 如果是中綴運(yùn)算符,要設(shè)定優(yōu)先級(jí)
下面我們分別自定義前綴運(yùn)算符,后綴運(yùn)算符,后綴運(yùn)算符:
//優(yōu)先級(jí)組
precedencegroup plus3Prece{
//結(jié)合性
associativity: none //left / right / none
//比誰(shuí)的優(yōu)先級(jí)高
higherThan: AdditionPrecedence
//比誰(shuí)的優(yōu)先級(jí)低
lowerThan: MultiplicationPrecedence
//在可選鏈操作中,和賦值運(yùn)算符一樣的優(yōu)先級(jí)
assignment: true
}
//聲明
prefix operator +++
postfix operator +++
infix operator +++ : plus3Prece
//前綴運(yùn)算符
struct Point{
var x: Int
var y: Int
//前綴+++
static prefix func +++ (p: inout Point) -> Point{
p = Point(x: p.x + 2, y: p.y + 2)
return p
}
//后綴+++
static postfix func +++ (p: inout Point) -> Point{
let tempPoint = p
p = Point(x: p.x + 2, y: p.y + 2)
return tempPoint
}
//中綴
static func +++ (p1: Point, p2: Point) -> Point{
let p = Point(x: (p1.x + p2.x) * 2, y: (p1.y + p2.y) * 2)
return p
}
}
?
優(yōu)先級(jí)組precedencegroup需要介紹一下:
associativity:組合型,left表示從左到右計(jì)算.right表示從右到左計(jì)算.none表示沒(méi)有組合型,也就是說(shuō)不能1 + 2 + 3這樣運(yùn)算.
higherThan:表示比哪個(gè)優(yōu)先級(jí)高.
lowerThan:表示比哪個(gè)優(yōu)先級(jí)低.
assignment:表示在可選鏈操作中和賦值運(yùn)算符有著同樣的優(yōu)先級(jí).
assignment:可能有些抽象,我們回憶一下可選鏈中的賦值運(yùn)算符.

assignment和可選鏈中的賦值運(yùn)算符一樣:

另外higherThan , lowerThan后面跟著運(yùn)算符的名稱,不是瞎寫(xiě)的.運(yùn)算符的名稱可以去先面這個(gè)網(wǎng)址去找:
https://developer.apple.com/documentation/swift/swift_standard_library/operator_declarations?changes=latest_minor
擴(kuò)展 Extension
擴(kuò)展類似于 OC 中的分類.擴(kuò)展可以為類,結(jié)構(gòu)體,枚舉,協(xié)議添加新的功能.
使用擴(kuò)展的注意點(diǎn):
1. 不能覆蓋原有的功能,OC的分類可以,swift擴(kuò)展不可以
2. 不能添加存儲(chǔ)屬性,因?yàn)榇鎯?chǔ)屬性保存在實(shí)例內(nèi)存中.擴(kuò)展不能夠改變能存結(jié)構(gòu)
3. 不能通過(guò)擴(kuò)展來(lái)添加父類,因?yàn)樘砑痈割愐灿锌赡芨淖儍?nèi)存結(jié)構(gòu)
4. 不能添加指定初始化器,重要的初始化器不能通過(guò)擴(kuò)展添加.
Swift 的擴(kuò)展功能很強(qiáng)大,它可以添加方法,計(jì)算屬性,下標(biāo),便捷初始化器,嵌套類型,協(xié)議等等.
擴(kuò)展計(jì)算屬性:
//擴(kuò)展計(jì)算屬性
extension Double{
var km: Double{ self / 1_000.0 }
var m: Double{ self }
}
print(128.6.km)
用擴(kuò)展下標(biāo):
//使用擴(kuò)展添加下標(biāo)
extension Array{
subscript (nulable index: Int) -> Element?{
if (startIndex ..< endIndex).contains(index){
return self[index]
}
return nil
}
}
擴(kuò)展嵌套類型:
//擴(kuò)展嵌套類型
extension Int{
//循環(huán)n次
func circles(toDo: () -> Void){
for _ in 0..<self{
toDo()
}
}
//嵌套類型
enum Kind{
case positive,zero,negative
}
var kind: Kind{
switch self{
case 0:
return .zero
case let x where x > 0:
return .positive
default:
return .negative
}
}
}
3.circles {
print(2)
}
print((-2).kind)
擴(kuò)展協(xié)議
如果一個(gè)類之前沒(méi)有遵守某個(gè)協(xié)議,我們可以通過(guò)擴(kuò)展,讓他遵守協(xié)議:
class Person{}
protocol Runable {
func run()
}
extension Person: Runable{
func run() {
print("run")
}
}
var person = Person()
person.run()
如果協(xié)議中有初始化器方法,那么遵守了此協(xié)議的類必須把協(xié)議中的初始化器聲明為required必要初始化器,這樣做的目的是為了讓子類都有這個(gè)初始化器:
protocol Runable {
init(speed: Int)
}
class Person: Runable{
required init(speed: Int) {
print("run")
}
}
注意: 協(xié)議中不能聲明 required 初始化器
我們知道,如果我們自定義了初始化器,那么系統(tǒng)就不會(huì)再幫我們自動(dòng)生成初始化器.有一種辦法可以既保留系統(tǒng)生成的初始化器,也可以自定義初始化器.
擴(kuò)展可以保留系統(tǒng)生成的初始化器:
struct Point {
var x: Int = 0
var y: Int = 0
}
extension Point{
init(p: Point) {
self.x = p.x
self.y = p.y
}
}
//系統(tǒng)生成的初始化器
var p1 = Point()
var p2 = Point(x: 10)
var p3 = Point(y: 10)
var p4 = Point(x: 10, y: 10)
//通過(guò)擴(kuò)展自定義的初始化器
var p5 = Point(p: p4)
比如說(shuō)現(xiàn)在有這樣一個(gè)需求,給整數(shù)擴(kuò)展一個(gè)方法,判斷整數(shù)是不是偶數(shù).
我們很快會(huì)想到可以這樣做:
extension Int{
func isEven () -> Bool{
self % 2 == 0
}
}
print(4.isEven())
給Int類型擴(kuò)展一個(gè)方法.但是這樣做不完善,因?yàn)檎麛?shù)包括有符號(hào)整數(shù)和無(wú)符號(hào)整數(shù).那我們?cè)鯓幽依ㄋ姓麛?shù)呢?我們只要找到整數(shù)的共同點(diǎn)就好了.
在swift中所有整數(shù)都遵守了BinaryInteger協(xié)議,所以我么可以直接給BinaryInteger協(xié)議擴(kuò)展一個(gè)方法就好了.
給一個(gè)協(xié)議擴(kuò)展方法,凡是遵守了這個(gè)協(xié)議的類型都有這個(gè)方法:
extension BinaryInteger{
func isEven () -> Bool{
self % 2 == 0
}
}
print(4.isEven())
var uNum: UInt = 10
uNum.isEven()
我們知道協(xié)議中所有的東西都必須實(shí)現(xiàn),如果實(shí)現(xiàn)的不完整就會(huì)編譯不通過(guò):

有時(shí)候我們可能只想實(shí)現(xiàn)協(xié)議中的某些方法,可不可以做到呢?
可以使用擴(kuò)展給協(xié)議中的方法提供默認(rèn)實(shí)現(xiàn)來(lái)間接實(shí)現(xiàn)協(xié)議的可選效果:

我們還可以通過(guò)擴(kuò)展給協(xié)議添加協(xié)議中沒(méi)有的方法:

注意一個(gè)小細(xì)節(jié),如果我們創(chuàng)建Person實(shí)例的時(shí)候,指明是遵守了Runable協(xié)議的類型,會(huì)怎么樣呢?

為什么會(huì)這樣呢?
因?yàn)槲覀冎该髁祟愋褪?code>遵守了 Runable 協(xié)議.而Runable協(xié)議中并沒(méi)有test2方法的聲明.所以Xcode會(huì)認(rèn)為遵守了此協(xié)議的類中可能沒(méi)有test2方法.所以他就會(huì)優(yōu)先從協(xié)議中找這個(gè)方法.
擴(kuò)展中的泛型:
擴(kuò)展中依然可以使用原類型中的泛型:


符合條件后才擴(kuò)展:
protocol Runable {
func test1()
}
class Person<C>{
var pet: C
init(p: C) {
self.pet = p
}
}
//只有當(dāng) Person 中的泛型 C 遵守了 Runable 協(xié)議后
//才會(huì)給 Person 擴(kuò)展協(xié)議
extension Person: Runable where C: Runable{
func test1() {
}
}