Swift進(jìn)階(十一)協(xié)議 & 元類型

協(xié)議(Protocol)

  • 協(xié)議可以用來定義方法屬性霎挟、下標(biāo)的聲明,協(xié)議可以被枚舉麻掸、結(jié)構(gòu)體酥夭、遵守(多個協(xié)議之間用逗號隔開)
protocol Test {
    func fun()
    var x: Int { get set }
    var y: Int { get }
    subscript(index: Int) -> Int {get set}
}

protocol Test1 {}
protocol Test2 {}
protocol Test3 {}
class TestClass: Test1, Test2, Test3 {}
  • 協(xié)議中定義方法時(shí)不能有默認(rèn)參數(shù)值
  • 默認(rèn)情況下,協(xié)議中定義的內(nèi)容必須全部實(shí)現(xiàn)

協(xié)議中的屬性

  • 協(xié)議中定義屬性時(shí)脊奋,必須用var關(guān)鍵字
  • 實(shí)現(xiàn)協(xié)議時(shí)熬北,屬性的權(quán)限不能小于協(xié)議中定義的屬性權(quán)限
    ① 協(xié)議定義getset诚隙,則用var存儲屬性 或者 get讶隐、set計(jì)算屬性去實(shí)現(xiàn)
    ② 協(xié)議定義get,用任何屬性都可以實(shí)現(xiàn)
    eg:
protocol Drawable {
    func draw()
    var x: Int { get set }
    var y: Int { get }
    subscript(index: Int) -> Int { get set }
}

class Person: Drawable {
    var x: Int = 0
    var y: Int = 0
    
    func draw() { print("Person draw") }
    
    subscript(index: Int) -> Int {
        get { index }
        set {}
    }
}

/*-----或者-----*/

class Person: Drawable {
    var x: Int {
        get { 0 }
        set {}
    }
    var y: Int { 0 }
    
    func draw() { print("Person draw") }
    
    subscript(index: Int) -> Int {
        get { index }
        set {}
    }
}

static久又、class

  • 為了保證通用巫延,協(xié)議中必須使用static定義類型方法類型屬性地消、類型下標(biāo)
    因?yàn)?code>class修飾的類型相關(guān)的事物炉峰,只能被適用與結(jié)構(gòu)體枚舉無法使用脉执。
protocol Drawable {
    static func draw()
}

class Person1: Drawable {
    static func draw() {
        print("Person1 draw")
    }
}

struct Person2: Drawable {
    static func draw() {
        print("Person2 draw")
    }
}
  • 如果說疼阔,父類遵守了某個協(xié)議,子類想要去重寫協(xié)議方法半夷,那么在父類中婆廊,協(xié)議方法就要用class來修飾否彩,否則就要static來修飾列荔。
protocol Drawable {
    static func draw()
}

class Person1: Drawable {
    class func draw() {
        print("Person1 draw")
    }
}

class Person2: Person1 {
    override class func draw() {
        print("Person2 draw")
    }
}

mutating

  • 只有將協(xié)議中的實(shí)例方法標(biāo)記為mutating砂吞,才允許結(jié)構(gòu)體蜻直、枚舉的具體實(shí)現(xiàn)修改自身內(nèi)存。
  • \color{orange}{實(shí)現(xiàn)方法時(shí)}不用加mutating赎瑰,枚舉結(jié)構(gòu)體才需要加mutating鲜漩。
protocol Drawable {
    mutating func draw()
}

class Person1: Drawable {
    var x = 0
    func draw() {
        x += 1
    }
}

struct Point: Drawable {
    var x = 0
    mutating func draw() {
        x += 1
    }
}

init

  • 協(xié)議中還可以定義初始化器init孕似,非final類實(shí)現(xiàn)時(shí)必須加上required
    因?yàn)?code>final修飾的類鳞青,表明該類不能被繼承,則不用擔(dān)心子類不實(shí)現(xiàn)init的問題
protocol Drawable {
    init(x: Int, y: Int)
}

class Person1: Drawable {
    required init(x: Int, y: Int) {}
}

final class Person2: Drawable {
    init(x: Int, y: Int) {}
}
  • 如果從協(xié)議中實(shí)現(xiàn)的初始化器习寸,剛好是重寫了父類的指定初始化器,那么這個初始化必須同時(shí)加上requiredoverride
protocol Drawable {
    init(x: Int, y: Int)
}

class Person {
    init(x: Int, y: Int) {}
}

class Man: Person, Drawable {
    required override init(x: Int, y: Int) {}
}

init匿级、init?津函、init!

  • 協(xié)議中定義的init?尔苦、init!行施,可以用init稠项、init?皿渗、init!去實(shí)現(xiàn)
  • 協(xié)議中定義的init轻腺,可以用initinit!去實(shí)現(xiàn)
protocol Drawable {
    init()
    init?(x: Int)
    init!(y: Int)
}

class Person: Drawable {
    required init() {}
//    required init!() {}
    
    required init?(x: Int) {}
//    required init!(x: Int) {}
//    required init(x: Int) {}
    
    required init!(y: Int) {}
//    required init?(y: Int) {}
//    required init(y: Int) {}
}

協(xié)議的繼承

  • 一個協(xié)議可以繼承其他協(xié)議
protocol Runnable {
    func run()
}

protocol Livable: Runnable {
    func breath()
}

class Person: Livable {
    func breath() {}
    func run() {}
}

協(xié)議的組合

  • 協(xié)議組合的時(shí)候贬养,可以包含1個類類型(最多1個)
protocol Runnable {}
protocol Livable {}
class Person {}

// 接收Person 或 其子類的實(shí)例
func fn0(obj: Person) {}

// 接收遵守 Livable協(xié)議 的實(shí)例
func fn1(obj: Livable) {}

// 接收同時(shí)遵守 Livable、Runable協(xié)議 的實(shí)例
func fn2(obj: Livable & Runnable) {}

// 接收同時(shí)遵守 Livable仰美、Runable協(xié)議,并且是 Person 或者 其子類的實(shí)例
func fn3(obj: Person & Livable & Runnable) {}

typealias RealPerson = Person & Livable & Runnable
// 接收同時(shí)遵守 Livable儿礼、Runable協(xié)議,并且是 Person 或者 其子類的實(shí)例
func fn4(obj: RealPerson) {}

CaseIterable

  • 讓枚舉遵守CasIterable協(xié)議蚊夫,可以實(shí)現(xiàn)遍歷枚舉值
enum Season: CaseIterable {
    case spring, summer, autumn, winter
}
let seasons = Season.allCases // [Season]
for season in seasons {
    print(season)
}
/*輸出結(jié)果*/
spring
summer
autumn
winter

我們可以看一下CaseIterable里面的內(nèi)容

/// The compiler can automatically provide an implementation of the
/// `CaseIterable` requirements for any enumeration without associated values
/// or `@available` attributes on its cases. The synthesized `allCases`
/// collection provides the cases in order of their declaration.
///
/// You can take advantage of this compiler support when defining your own
/// custom enumeration by declaring conformance to `CaseIterable` in the
/// enumeration's original declaration. The `CompassDirection` example above
/// demonstrates this automatic implementation.
public protocol CaseIterable {

    /// A type that can represent a collection of all values of this type.
    associatedtype AllCases : Collection where Self == Self.AllCases.Element

    /// A collection of all values of this type.
    static var allCases: Self.AllCases { get }
}

可以看到allCases是一個可讀的類型屬性,通過官方給的注釋伍绳,可以知道allCases是所有值的集合乍桂,那么在 當(dāng)前情況下:
Season.allCases就等價(jià)于[Season.spring, Season.summer, Season.autumn, Season.winter]

CustomStringConvertible

  • 遵守CustomStringConvertible漠趁、CustomDebugStringConvertible協(xié)議扁凛,都可以自定義實(shí)例的打印字符串
// 遵守 CustomStringConvertible & CustomDebugStringConvertible 協(xié)議
class Person: CustomStringConvertible, CustomDebugStringConvertible {
    var age = 0
    var description: String { "person_\(age)" }
    var debugDescription: String { "debug_person_\(age)" }
}

var person = Person()
print(person)
debugPrint(person)

print("------------------------------------")

// 不遵守 CustomStringConvertible & CustomDebugStringConvertible 協(xié)議
class Man {
    var age = 10
}
var man = Man()
print(man)
debugPrint(man)
/*輸出結(jié)果*/
person_0
debug_person_0
------------------------------------
test.Man
test.Man
  • print調(diào)用的是CustomStringConvertible協(xié)議的description
  • debugPrintpo調(diào)用的是CustomDebugStringConvertible協(xié)議的debugDescription

我們來看一下控制臺的po

image.png

Any闯传、AnyObject

  • Swift 提供了兩種特殊的類型:Any谨朝、AnyObject
    Any:可以代表任意類型(枚舉、結(jié)構(gòu)體甥绿、類字币,也包括函數(shù)類型)
    AnyObject:可以代表任意類型(在協(xié)議后面寫上:AnyObject代表只有類能遵守這個協(xié)議)。另外共缕,在協(xié)議后面寫上:class也代表只有能遵守這個協(xié)議洗出。

is、as?图谷、as!翩活、as

  • is用來判斷是否為某種類型,as用來強(qiáng)制類型轉(zhuǎn)換
protocol Runnable {
    func run()
}
class Person {}
class Man: Person, Runnable {
    func run() {
        print("Man run")
    }
    
    func sleep() {
        print("Man sleep")
    }
}

我們先看一下is的使用

var m: Any = 10
print(m is Int) // true

m = "Tom"
print(m is String) // true

m = Man()
print(m is Person) // true
print(m is Man) // true
print(m is Runnable) // true

我們再來看一下as?便贵、as!是如何用的
as? 表示轉(zhuǎn)化可能成功菠镇,也可能失敗。
as! 表示強(qiáng)制解包(注意:強(qiáng)制解包是有風(fēng)險(xiǎn)的)

var m: Any = 10
(m as? Man)?.sleep() // 沒有調(diào)用sleep

m = Man()
(m as? Man)?.sleep() // Man sleep
(m as! Man).sleep() // Man sleep
(m as? Runnable)?.run() // Man run

那我們來看一下這一句代碼

(m as? Man)?.sleep()

① 首先as? 表示轉(zhuǎn)化可能成功承璃,也可能失敗利耍。
② 緊接著第二個?是我們上一篇文章講到的可選鏈的內(nèi)容,如果前面的為nil后面的就不會執(zhí)行盔粹。

我們再來看一下as
as\color{orange}{百分之一百}可以轉(zhuǎn)換成功的時(shí)候使用

var list = [Any]()
list.append(Int("123") as Any)

var num = 10 as Double
print(num) // 10.0

X.self隘梨、X.Type、AnyClass

  • X.self是一個元類型(metadata)的指針舷嗡,metadata存放著類型相關(guān)的信息
  • X.self屬于X.Type類型
    這里大家要注意轴猎,實(shí)例對象在堆內(nèi)存中存放的前8個字節(jié)就是類型信息。而且X.self就是8個字節(jié)进萄,和實(shí)例對象在堆內(nèi)存中存放的前8個字節(jié)是一樣的税稼。
    下面我們來證明一下:
class Person {}

var p: Person = Person() // 注意:此處打上斷點(diǎn),進(jìn)入?yún)R編查看匯編代碼
var pType: Person.Type = Person.self

首先我們來證明一下X.self是8個字節(jié)

image.png

結(jié)合Xcode注釋和movq指令可以斷定X.self占8個字節(jié)(q代表8個字節(jié)垮斯,常用的匯編指令大家可以網(wǎng)上搜一下)
接下來我們再找一下pType里面存放的內(nèi)容是什么:
我們由下往上推論
image.png

那么我們可以在第12行處,打斷點(diǎn)只祠,看一下%rax 里面存放的是什么
image.png

可以看到Xcode也給出了答案兜蠕,此時(shí)%rax里面存放的就是metadata信息。
那么接下來我們再看一下p里面存放的信息:
image.png

可以看到p在堆內(nèi)存里面的前8個字節(jié)的內(nèi)容和pType一模一樣抛寝。因此我們上面的推論是正確的熊杨。

下面我們看一下繼承的關(guān)系

class Person {}
class Student: Person {}
var perType: Person.Type = Person.self
var stuType: Student.Type = Student.self
// 注意這樣做是可以的曙旭,因?yàn)閄.Type也是一個類型,并且Student繼承自Person
// 從賦值角度看:perType 和 stuType 都是8個字節(jié)晶府,也沒什么問題
// 另外需要注意的是桂躏,這里如果把Student改成跟Person沒有關(guān)系的其他類,比如Dog川陆,就會報(bào)錯剂习,這樣做是不允許的
perType = Student.self
var anyType: AnyObject.Type = Person.self
anyType = Student.self

public typealias AnyClass = AnyObject.Type
var anyType2: AnyClass = perType.self
anyType2 = Student.self

我們再看一個語法糖 type(of: <#T##T#>)

var per = Person()
var perType = type(of: per) // Person.self
print(Person.self == type(of: per)) // true
  • \color{red}{這里大家要注意一下:}
    ① 從匯編代碼來看 type(of: <#T##T#>)并不是一個函數(shù)調(diào)用,只是看起來像一個函數(shù)较沪,這里大家可以通過匯編來查看一下鳞绕,會發(fā)現(xiàn)其對應(yīng)的并不是call指令。(大家可以自行查看一下)
    image.png

\color{red}{但是}通過Xcode的提示尸曼,type(of: <#T##T#>)又是一個函數(shù)们何,見下圖:

image.png

具體原因,我暫時(shí)也不清楚控轿,這里大家可以自行探索一下冤竹。之后弄明白了再給大家補(bǔ)上。

元類型的應(yīng)用

  • 比如開發(fā)中 tabbar的配置茬射,就可以利用元類型來做鹦蠕。
class Animal {
    required init() {}
}
class Cat: Animal {}
class Dog: Animal {}
class Pig: Animal {}

func create(_ clses: [Animal.Type]) -> [Animal] {
    var arr = [Animal]()
    for cls in clses {
        arr.append(cls.init())
    }
    return arr
}

print(create([Cat.self, Dog.self, Pig.self])

下面我們來看一個小知識點(diǎn)

image.png

從打印結(jié)果,大家可以看到:
Person好像還有一個父類躲株,但是根據(jù)Swift的文檔片部,Swift的類并沒有統(tǒng)一的基類,并且從代碼看Person也沒有繼承自任何類霜定。所以這里我們可以猜測档悠,在Swift庫里面應(yīng)該還有一個隱藏的基類的。當(dāng)然這個我們后續(xù)再驗(yàn)證望浩,有興趣的同學(xué)可以在Swift源碼里面找一下辖所。

Self

  • Self代表當(dāng)前類型
class Person {
    var age: Int = 0
    static var count = 2
    func fun() {
        print(self.age) // 0
        print(Self.count) // 2
    }
}
  • Self一般用作返回值類型,限定返回值跟方法調(diào)用者必須是同一類型(也可以作為參數(shù)類型)磨德,類似于OC的instancetype
protocol Runnable {
    func test() -> Self
}

class Person: Runnable {
    func test() -> Self {
        type(of: self).init()
    }
    
    required init() {}
}
class Student: Person {}

var p = Person()
print(p.test()) // Person

var s = Student() // Student
print(s.test())
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缘回,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子典挑,更是在濱河造成了極大的恐慌酥宴,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件您觉,死亡現(xiàn)場離奇詭異拙寡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)琳水,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門肆糕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來般堆,“玉大人,你說我怎么就攤上這事诚啃』此ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵始赎,是天一觀的道長和橙。 經(jīng)常有香客問我,道長极阅,這世上最難降的妖魔是什么胃碾? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮筋搏,結(jié)果婚禮上仆百,老公的妹妹穿的比我還像新娘。我一直安慰自己奔脐,他們只是感情好俄周,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著髓迎,像睡著了一般峦朗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上排龄,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天波势,我揣著相機(jī)與錄音,去河邊找鬼橄维。 笑死尺铣,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的争舞。 我是一名探鬼主播凛忿,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼竞川!你這毒婦竟也來了店溢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤委乌,失蹤者是張志新(化名)和其女友劉穎床牧,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遭贸,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡叠赦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片除秀。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖算利,靈堂內(nèi)的尸體忽然破棺而出册踩,到底是詐尸還是另有隱情,我是刑警寧澤效拭,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布暂吉,位于F島的核電站,受9級特大地震影響缎患,放射性物質(zhì)發(fā)生泄漏慕的。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一挤渔、第九天 我趴在偏房一處隱蔽的房頂上張望肮街。 院中可真熱鬧,春花似錦判导、人聲如沸嫉父。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绕辖。三九已至,卻和暖如春擂红,著一層夾襖步出監(jiān)牢的瞬間仪际,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工昵骤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留树碱,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓涉茧,卻偏偏與公主長得像赴恨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子伴栓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容