可選項是Swift
的一大特色,那么可選項的本質(zhì)是什么呢?
比如下面代碼:
我們從打印結(jié)果可以看到,age
是Optional
類型,其實可選項本質(zhì)就是Optional
類型.?
只是語法糖的一種寫法.我們看看Optional
類型的本質(zhì)是什么:
可以看到Optional
的本質(zhì)就是一個枚舉類型,有兩個成員值和一個初始化方法.既然知道可選項的本質(zhì)是枚舉類型,那上面的代碼可以這樣寫:
還可以簡化:
可選項類型綁定
我么知道可選項和if
組合使用可以使用可選項綁定來判斷可選項中是否有值:
可選項和if
組合時,如果可選項包裝有值,那么就進行解包,并把值賦值給a
.
其實可選項還可以和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
,nil
是none
.那么雙重可選項也是一樣的:
高級運算符
溢出運算符 &+,&-,&*
我們知道UInt8
的取值范圍是0~255
,Int8
的取值范圍是-128~127
.那么如果經(jīng)過運算后的結(jié)果超出了這個范圍會怎么樣呢?
可以看到,如果溢出后會產(chǎn)生運行時錯誤.所以Swift
提供了溢出運算符&+,&-,&*
溢出運算符的運算是循環(huán)的,比如說age
的值是255, 加 1后就變成了0.
運算符重載
如果我們想為系統(tǒng)原有的運算符添加額外的功能,可以對原有的運算符進行重載.重載就是函數(shù)名相同,參數(shù)不同.功能不同.
系統(tǒng)的運算符的本質(zhì)都是方法:
比如說+
只能對基本數(shù)據(jù)類型進行加法運算,如果我們要對結(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)體類型,所以需要我們重載==
運算符.
重載==
運算符有兩步:
- 實現(xiàn)
Equatable
協(xié)議 - 重載
==
運算符
上圖可以看到,我們把==
運算符的重載方法注釋了,也就是說并沒有重載==
運算符.為什么也可以使用==
運算符呢?
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
和可選鏈中的賦值運算符一樣:
另外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àn)協(xié)議中的某些方法,可不可以做到呢?
可以使用擴展給協(xié)議中的方法提供默認實現(xiàn)來間接實現(xiàn)協(xié)議的可選效果
:
我們還可以通過擴展給協(xié)議添加協(xié)議中沒有的方法:
注意一個小細節(jié),如果我們創(chuàng)建Person
實例的時候,指明是遵守了Runable
協(xié)議的類型,會怎么樣呢?
為什么會這樣呢?
因為我們指明了類型是遵守了 Runable 協(xié)議
.而Runable
協(xié)議中并沒有test2
方法的聲明.所以Xcode
會認為遵守了此協(xié)議的類中可能沒有test2
方法.所以他就會優(yōu)先從協(xié)議中找這個方法.
擴展中的泛型:
擴展中依然可以使用原類型中的泛型:
符合條件后才擴展:
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() {
}
}