協(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é)議定義get
、set
诚隙,則用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)存。 -
類
在不用加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í)加上
required
、override
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
轻腺,可以用init
、init!
去實(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
-
debugPrint
、po
調(diào)用的是CustomDebugStringConvertible
協(xié)議的debugDescription
我們來看一下控制臺的po
:
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
在可以轉(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é)
結(jié)合Xcode注釋和
movq
指令可以斷定X.self
占8個字節(jié)(q
代表8個字節(jié)垮斯,常用的匯編指令大家可以網(wǎng)上搜一下)接下來我們再找一下
pType
里面存放的內(nèi)容是什么:我們由下往上推論
那么我們可以在第12行處,打斷點(diǎn)只祠,看一下
%rax
里面存放的是什么可以看到Xcode也給出了答案兜蠕,此時(shí)
%rax
里面存放的就是metadata
信息。那么接下來我們再看一下p里面存放的信息:
可以看到
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
-
① 從匯編代碼來看type(of: <#T##T#>)
并不是一個函數(shù)調(diào)用,只是看起來像一個函數(shù)较沪,這里大家可以通過匯編來查看一下鳞绕,會發(fā)現(xiàn)其對應(yīng)的并不是call
指令。(大家可以自行查看一下)
② 通過Xcode的提示尸曼,type(of: <#T##T#>)
又是一個函數(shù)们何,見下圖:
具體原因,我暫時(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)
從打印結(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())