swift4.1 系統學習十四 協議
// 協議
/*
OC中也有協議,swift中的協議的作用與OC中基本一樣,只是在寫法上有一點區別。
我們使用 protocol關鍵字來定義一個協議。在一個協議中只能存放計算式屬性以及方法的宣告,
而不能對他們進行定義。
*/
// 1. 協議的定義
// 定義一個協議 protocol MyProt { /// 宣告一個普通的方法 func foo() /// 宣告一個可修改儲存式例項屬性的方法 mutating func doSomething(a: Int) -> Int? /// 宣告一個靜態方法 static func typeMethod() /// 宣告一個初始化器方法 init(a: Int) /// 宣告一個下標 subscript(index: Int) -> Int {get set} /// 宣告一個計算式屬性 var property: Int { get set } /// 宣告一個計算式屬性,並且是隻讀的 static var typeP: Double { get } }
// 2. 協議的遵循
/*
只有協議是沒有意義的,協議是用來遵循的,是需要實現的。
swift中,列舉、類以及結構體型別都可以遵循協議。
遵循了某一協議的型別必須實現該協議中所生命的所有方法與計算式屬性,其中也包括初始化器
*/
do { struct Test: MyProt { /// 定義自己的儲存式屬性a var a = 100 func foo() { print("foo") } mutating func doSomething(a: Int) -> Int? { print("doSomething") self.a = a return a == 0 ? nil : self.a } static func typeMethod() { print("study") } init(a: Int) { self.a = a } subscript(index: Int) -> Int { get { return a + index } set { a = newValue + index } } var property: Int { get { return self.a / 2 } set { self.a = newValue / 2 } } static var typeP: Double { return Double.pi } } var t =Test(a: 10) t.foo() print("---- \(t.doSomething(a: 100))") t.property = 12 print("a: \(t.a)") print("p: \(t.property)") }
/*
協議是一種比較靈動的動態型別,根據為它所初始化的物件例項的性質不同,它所採取的拷貝與引用
策略也會有不同。
*/
protocol P { func foo() } do { print("\n") struct TestA: P { var a: Int = 0 func foo() { print("這是一個foo") print("a = \(a)") } } /// 定義列舉型別,遵守協議P enum TestB: Int, P { case one = 1, two, three func foo() { print("enum = \(self)") print("value = \(self.rawValue)") } } var a = TestA() // 宣告P協議型別的物件p,用a對它初始化 var p: P = a p.foo() withUnsafePointer(to: a.foo) { print("\($0)") } withUnsafePointer(to: p.foo) { print("\($0)") } withUnsafePointer(to: &a) { print("p:\($0)") } withUnsafePointer(to: &p) { print("p:\($0)") } a.a = 10 p.foo() withUnsafePointer(to: &a) { print("\($0)") } withUnsafePointer(to: &p) { print("p:\($0)") } /* 列印: 這是一個foo a = 0 這是一個foo a = 0 */ /* 結果說明,p物件不受物件a的影響,為什麼呢? 因為結構體和列舉都是值型別,值型別和引用型別是不一樣的。 執行var p: P = a的時候,系統已經分別給 p開闢了新的空間,所以,改變a,並不會對p造成什麼影響。 */ p = TestB.two p.foo() }
/*
寫時拷貝
由於協議型別是一種抽象型別,swift在實現它的時候採用了一種十分靈活的機制——寫時拷貝。
對於像列舉、結構體這種值型別的物件例項,即便用一個他們所遵循的協議去指向值型別的物件例項,
當協議型別自身或它所指向的物件例項任一方修改了儲存式例項屬性的值的時候,此時就會發生寫時拷貝。
這時,swift會將協議型別物件分配一個新的儲存空間,然後將它所指向的值型別的物件例項的當前狀態
拷貝過去。
*/
// 一個型別可以遵循多個協議,我們可以用逗號來分割遵循的多個協議的名稱。一個型別若遵循了多個協議
//,那麼它必須實現它所遵循的所有協議中宣告的所有方法和屬性。
protocol ProtA { func foo() func method() var property: Int { get set } } protocol ProtB { mutating func method(a: Int) static var property: Double { get } } do { print("\n") struct Test: ProtA, ProtB { var a = 0 func foo() { print("協議A的方法,foo") } func method() { print("協議A的方法, method") } var property: Int { get { return a } set { a = newValue } } mutating func method(a: Int) { print("協議B的方法,method,a = \(a)") self.a = a } static var property: Double { return M_E } } let a: ProtA = Test() a.foo() a.method() print("a = \(a.property)") var b: ProtB = Test() b.method(a: 10) print("a = \(type(of: b).property)") }
// 3. 協議繼承
/*
在swift程式設計中,一個協議可以繼承自另一個協議或者其他多個協議。當一個協議繼承了其他協議的時候,該
協議會將它所繼承的其他協議中的所有宣告的方法與屬性全部包含在自己的協議之中。
注意:
設計的時候,注意命名。
*/
/// 定義一個協議C,繼承自協議A和協議B protocol ProtC: ProtA, ProtB { /// 過載了ProtA的方法 func foo() /// 自己的方法 func Coo() } do { print("\n") /// 自定義結構體,遵守了ProtC協議 struct Test: ProtC { var x = 100 // 以下是要實現的協議方法和屬性 func foo() { print("我就是個方法,foo") } func Coo() { print("我是C協議的方法,Coo") } func method() { print("我是A的方法,method") } var property: Int { get { return x } set { x = newValue } } mutating func method(a: Int) { print("我是B的方法,method, 引數a = \(a)") x = a } static var property: Double { get { return 0 } } } var t = Test() t.foo() t.Coo() t.method() t.method(a: 60) print("property: \(t.property)") print("static property: \(Test.property)") }
// 4. class協議
/*
我們可以將一個協議限定為只適用於類型別。我們只需要在一個協議後面使用:class 即可將該協議宣告
為類協議,這樣只有類型別才能遵守該協議。
*/
/// 定義一個協議ProtD,是一個類協議 protocol ProtD: class { /// 宣告型別計算式屬性 static var type: String {get set } func hello() } /// 定義一個協議ProtE,普通協議 protocol ProtE { func welcome() } /// 定義一個協議ProtF, 是一個類協議,繼承自ProtE protocol ProtF: class, ProtE { var property: Int { get set } } /// 定義一個協議ProtG,繼承自ProtD和ProtE protocol ProtG: ProtD, ProtE { } do { print("\n") class Dog: ProtF { var name: String = "ww" /// 使用class覆蓋協議中的類型別屬性 class var type: String { get { return "中華田園犬" } set { } } var property: Int { get { return 10 } set { } } /// 實現ProtE的方法 func welcome() { print("歡迎來到狗狗之家") } } let dog = Dog() dog.name = "小花花" dog.welcome() print("這隻小狗已經 \(dog.property) 歲了") print("這隻狗狗的品種是 \(Dog.type)") /// 定義了結構體 Cat,並實現ProtE struct Cat: ProtE { var name = "mimi" /// 定義自己的方法 func eat(fishCount: Int) { print("給貓咪餵了 \(fishCount) 只小魚") } /// 實現ProtE協議的方法 func welcome() { print("你們好,這裡是貓咪的樂園") } } var cat = Cat() cat.name = "小黃" cat.welcome() cat.eat(fishCount: 3) /* 注意,如果一個協議繼承了某個類協議,那麼他自己也就成了類協議。 如果一個非類型別遵循了一個類協議,那麼就會引發編譯報錯。 */ }
// 5.協議組合
/*
有時,我們需要宣告一個物件,其型別需要遵循多個協議,以便能夠呼叫這些協議中的方法或者訪問屬性。
我們可以在定義一個協議,然後繼承我們想要遵循的協議,但是這會顯得很繁瑣。swift中提供了“協議組合”
這一語法特性,使得我們能夠很輕鬆的將所要遵循的協議給組合起來。
*/
protocol P1 { func foo() func method() } protocol P2 { mutating func method(a: Int) var age: Int { get set } } do { print("\n") // 定義結構體Test,遵循了p1,p2協議 struct Test: P1,P2 { /// 自己的屬性a var a = 0 /// 以下是實現協議的方法和屬性 func foo() { print("P1:這個世界怎麼了,這個世界上的人太瘋狂了") } func method() { print("P1: 還能怎麼辦呢?滄海一粟,宇宙一蜉蝣而已,小小屁民,苟活於世") } mutating func method(a: Int) { print("p2: 好好生活,好好掙錢吧,多掙一點,養活好自己和家人。a: \(a)") self.a = a } var age: Int { get { return a } set { self.a = newValue } } } var t = Test() t.a = 100 t.foo() t.method() t.method(a: 50) print("age: \(t.age)") t.age = 102 print("向天再借: \(t.age) 年") /// 宣告一個物件p,遵循了P1和P2,這裡就使用了協議組合的語法特性。 var p: P1 & P2 = Test() p.foo() p.age = 100 p.method(a: 6) print("age = \(p.age)") }
// 6.關聯型別
/*
我們在協議中宣告一些方法,這些方法的引數型別根據不同的實現可能會有所不同,此時swift提供了協議的
“關聯型別”語法特性,使得我們可以在協議中宣告泛化的抽象型別,每個遵循它的型別可以指定其自己的
具體型別。
*/
protocol P6A { /// 聲明瞭關聯型別DataType associatedtype DataType /// 聲明瞭例項方法method,具有一個關聯型別引數value func method(value: DataType) } do { print("\n") struct MyStruct:P6A { /// 通過typealias指定協議關聯型別的具體型別,這裡把DataType指定為Int型別 typealias DataType = Int let data: DataType = 100 func method(value: Int) { print("哈哈哈哈,這裡是關聯型別的測試場地: \(data + value)") } } /// 在定義一個列舉型別 enum MyEnum: String, P6A { // 這裡把DataType指定為String型別 typealias DataType = String case one = "1", two = "2", three = "3" func method(value: String) { print("這裡是一個列舉,\(self.rawValue + value)") } } // 定義一個函式test // 泛型 T 必須遵循P6A協議 func test<T>(prot: T, param: T.DataType) where T: P6A { prot.method(value: param) } test(prot: MyStruct(), param: -1) test(prot: MyEnum.two, param: "apple") }
// 7.關於協議中的self型別
/*
在swift程式語言的協議中有一個內建的關聯型別——self,用於指代遵循該協議的具體型別。
*/
protocol P7A { mutating func method(obj: Self) } do { print("\n") struct MyStruct:P7A { var data = 1 mutating func method(obj: MyStruct) { data += obj.data } } enum MyEnum: String, P7A { case red = "red", green = "green",blue = "blue" case red_and_green = "red and green" case red_and_blue = "red and blue" case green_and_blue = "green and blue" mutating func method(obj: MyEnum) { guard let newValue = MyEnum(rawValue: self.rawValue + "and" + obj.rawValue) else { return } self = newValue } } func test<T>(dst: inout T, src: T) where T: P7A { dst.method(obj: src) } var sd = MyStruct() let ss = MyStruct(data: 100) test(dst: &sd, src: ss) print("sd = \(sd.data)") var ed = MyEnum.red let es = MyEnum.green test(dst: &ed, src: es) print("ed = \(ed)") }