從零學習Swift 12:可選項本質(zhì),高級運算符, Extension 擴展

總結(jié)

可選項是Swift的一大特色,那么可選項的本質(zhì)是什么呢?

比如下面代碼:

可選項

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

Optional 本質(zhì)

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

還可以簡化:

簡化寫法
可選項類型綁定

我么知道可選項和if組合使用可以使用可選項綁定來判斷可選項中是否有值:

可選項綁定

可選項和if組合時,如果可選項包裝有值,那么就進行解包,并把值賦值給a.

其實可選項還可以和switch語句組合,也可以使用可選項綁定:

可選項和 switch 組合

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

這樣一來,第一個警告也消除了.第一個警告就是直接打印可選項的時候的警告.現(xiàn)在使用?如果可選項有值就會解包,所以警告消除.

雙重可選項

既然知道了可選項的本質(zhì)就是枚舉類型:有值是some,nilnone.那么雙重可選項也是一樣的:

雙重可選項
高級運算符
溢出運算符 &+,&-,&*

我們知道UInt8的取值范圍是0~255,Int8的取值范圍是-128~127.那么如果經(jīng)過運算后的結(jié)果超出了這個范圍會怎么樣呢?

溢出會發(fā)生運行時錯誤

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

溢出運算符

溢出運算符的運算是循環(huán)的,比如說age的值是255, 加 1后就變成了0.

運算符重載

如果我們想為系統(tǒng)原有的運算符添加額外的功能,可以對原有的運算符進行重載.重載就是函數(shù)名相同,參數(shù)不同.功能不同.

系統(tǒng)的運算符的本質(zhì)都是方法:

+

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

對結(jié)構(gòu)體進行加法運算

可以看到結(jié)構(gòu)體是不支持 + 運算的.

所以我們只能對+進行重載,達到我們的目的:


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)

因為我們是給 Point 提供的額外的運算功能,所以我們應該把重載的方法放到 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){
        //先運算
        lhs += Point(x: 1, y: 1)
    }
    
    // ++ 在后
    static postfix func ++ (lhs: inout Point) -> Point{
        //先保存原來的
        let p = lhs
        lhs += Point(x: 1, y: 1)
        return p
    }

==運算符

如果想要比較兩個Point對象是否相等可以使用==運算符,但是Swift==運算符默認不支持結(jié)構(gòu)體類型,所以需要我們重載==運算符.

== 運算符默認不支持結(jié)構(gòu)體

重載==運算符有兩步:

  1. 實現(xiàn) Equatable 協(xié)議
  2. 重載 == 運算符
重載 == 運算符

上圖可以看到,我們把==運算符的重載方法注釋了,也就是說并沒有重載==運算符.為什么也可以使用==運算符呢?

Swift會為一下三種情況提供默認的Equatable實現(xiàn):
1. 只擁有遵守了 Equatable 協(xié)議的存儲屬性的結(jié)構(gòu)體
2. 只關(guān)聯(lián)了遵守 Equatable 協(xié)議類型的枚舉
3. 沒有關(guān)聯(lián)值的枚舉

以上三種情況,Swift會默認提供Equatable協(xié)議的實現(xiàn).Point的兩個存儲屬性都是Int類型,而Int類型顯然是遵守了Equatable協(xié)議的,所以不用重載==.

沒有關(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)類型實現(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類型,必須實現(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類可以不實現(xiàn)Equatable協(xié)議,==方法同樣可以正常使用,但是還是建議實現(xiàn)Equatable協(xié)議,因為這樣做有兩個好處:

1. 一眼就可以看出 Person 類是可比較的
2. 適用于泛型限定

比如說我們寫一個比較傳進來的泛型參數(shù)是否相等的方法:

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

如果我們實現(xiàn)了Equatale協(xié)議,重載了==方法,等價于重載了!=運算符,我們可以直接使用!=運算符.

如果要比較兩個引用類型引用的是不是同一個對象,要使用===運算符:


var person1 = Person(age: 16, name: "Jack")
var person2 = Person(age: 18, name: "Jorn")

print(person1 === person2) // false

==是判斷兩個對象是否相等,如果我們相比較大小呢?

如果要比較兩個對象大小,要遵守Comparable協(xié)議,然后重載> , >= , < , <=運算符.

Comparable里面就聲明了這四個方法:

自定義運算符

如果現(xiàn)有的運算符還是不能滿足我們的需求,我們可以根據(jù)自己的需求,自定義運算符.

自定義運算符時需要注意兩點:
1. 要指明是前綴運算符,后綴運算符,還是中綴運算符
2. 如果是中綴運算符,要設定優(yōu)先級

下面我們分別自定義前綴運算符,后綴運算符,后綴運算符:


//優(yōu)先級組
precedencegroup plus3Prece{
    //結(jié)合性
    associativity: none //left / right / none
    //比誰的優(yōu)先級高
    higherThan: AdditionPrecedence
    //比誰的優(yōu)先級低
    lowerThan: MultiplicationPrecedence
    //在可選鏈操作中,和賦值運算符一樣的優(yōu)先級
    assignment: true
}
//聲明
prefix operator +++
postfix operator +++
infix operator +++ : plus3Prece

//前綴運算符
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)先級組precedencegroup需要介紹一下:
associativity:組合型,left表示從左到右計算.right表示從右到左計算.none表示沒有組合型,也就是說不能1 + 2 + 3這樣運算.

higherThan:表示比哪個優(yōu)先級高.

lowerThan:表示比哪個優(yōu)先級低.

assignment:表示在可選鏈操作中和賦值運算符有著同樣的優(yōu)先級.

assignment:可能有些抽象,我們回憶一下可選鏈中的賦值運算符.

可選鏈中的賦值運算符

assignment和可選鏈中的賦值運算符一樣:

assignment 作用

另外higherThan , lowerThan后面跟著運算符的名稱,不是瞎寫的.運算符的名稱可以去先面這個網(wǎng)址去找:
https://developer.apple.com/documentation/swift/swift_standard_library/operator_declarations?changes=latest_minor

擴展 Extension

擴展類似于 OC 中的分類.擴展可以為類,結(jié)構(gòu)體,枚舉,協(xié)議添加新的功能.

使用擴展的注意點:
1. 不能覆蓋原有的功能,OC的分類可以,swift擴展不可以
2. 不能添加存儲屬性,因為存儲屬性保存在實例內(nèi)存中.擴展不能夠改變能存結(jié)構(gòu)
3. 不能通過擴展來添加父類,因為添加父類也有可能改變內(nèi)存結(jié)構(gòu)
4. 不能添加指定初始化器,重要的初始化器不能通過擴展添加.

Swift 的擴展功能很強大,它可以添加方法,計算屬性,下標,便捷初始化器,嵌套類型,協(xié)議等等.

擴展計算屬性:


//擴展計算屬性
extension Double{
    var km: Double{ self / 1_000.0 }
    var m: Double{ self }
}

print(128.6.km)

用擴展下標:


//使用擴展添加下標

extension Array{
    subscript (nulable index: Int) -> Element?{
        if (startIndex ..< endIndex).contains(index){
            return self[index]
        }
        return nil
    }
}


擴展嵌套類型:


//擴展嵌套類型
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)

擴展協(xié)議
如果一個類之前沒有遵守某個協(xié)議,我們可以通過擴展,讓他遵守協(xié)議:


class Person{}

protocol Runable {
    func run()
}

extension Person: Runable{
    func run() {
        print("run")
    }
}

var person = Person()
person.run()


如果協(xié)議中有初始化器方法,那么遵守了此協(xié)議的類必須把協(xié)議中的初始化器聲明為required必要初始化器,這樣做的目的是為了讓子類都有這個初始化器:


protocol Runable {
    init(speed: Int)
}

class Person: Runable{
    required init(speed: Int) {
        print("run")
    }
}

注意: 協(xié)議中不能聲明 required 初始化器

我們知道,如果我們自定義了初始化器,那么系統(tǒng)就不會再幫我們自動生成初始化器.有一種辦法可以既保留系統(tǒng)生成的初始化器,也可以自定義初始化器.

擴展可以保留系統(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)

//通過擴展自定義的初始化器
var p5 = Point(p: p4)

比如說現(xiàn)在有這樣一個需求,給整數(shù)擴展一個方法,判斷整數(shù)是不是偶數(shù).

我們很快會想到可以這樣做:


extension Int{
    func isEven () -> Bool{
        self % 2 == 0
    }
}

print(4.isEven())

Int類型擴展一個方法.但是這樣做不完善,因為整數(shù)包括有符號整數(shù)和無符號整數(shù).那我們怎樣囊括所有整數(shù)呢?我們只要找到整數(shù)的共同點就好了.

swift中所有整數(shù)都遵守了BinaryInteger協(xié)議,所以我么可以直接給BinaryInteger協(xié)議擴展一個方法就好了.

給一個協(xié)議擴展方法,凡是遵守了這個協(xié)議的類型都有這個方法:


extension BinaryInteger{
    func isEven () -> Bool{
        self % 2 == 0
    }
}

print(4.isEven())

var uNum: UInt = 10
uNum.isEven()

我們知道協(xié)議中所有的東西都必須實現(xiàn),如果實現(xiàn)的不完整就會編譯不通過:

協(xié)議中的聲明必須全部實現(xiàn)

有時候我們可能只想實現(xiàn)協(xié)議中的某些方法,可不可以做到呢?

可以使用擴展給協(xié)議中的方法提供默認實現(xiàn)來間接實現(xiàn)協(xié)議的可選效果:

在擴展中添加協(xié)議的默認實現(xiàn),達到間接可選效果

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

通過擴展給協(xié)議添加方法

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

為什么會這樣呢?

因為我們指明了類型是遵守了 Runable 協(xié)議.而Runable協(xié)議中并沒有test2方法的聲明.所以Xcode會認為遵守了此協(xié)議的類中可能沒有test2方法.所以他就會優(yōu)先從協(xié)議中找這個方法.

擴展中的泛型:

擴展中依然可以使用原類型中的泛型:

Array 中的泛型
擴展中可以使用原類型中的泛型

符合條件后才擴展:


protocol Runable {
    func test1()
}

class Person<C>{
    var pet: C
    init(p: C) {
        self.pet = p
    }
}

//只有當 Person 中的泛型 C 遵守了 Runable 協(xié)議后
//才會給 Person 擴展協(xié)議
extension Person: Runable where C: Runable{
    func test1() {
        
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370