Swift 協(xié)議

協(xié)議規(guī)定了用來實(shí)現(xiàn)某一特定功能所必需的方法和屬性。

任意能夠滿足協(xié)議要求的類型被稱為遵循(conform)這個(gè)協(xié)議。

類,結(jié)構(gòu)體或枚舉類型都可以遵循協(xié)議,并提供具體實(shí)現(xiàn)來完成協(xié)議定義的方法和功能。

語法

協(xié)議的語法格式如下:

protocol SomeProtocol {
    // 協(xié)議內(nèi)容
}

要使類遵循某個(gè)協(xié)議,需要在類型名稱后加上協(xié)議名稱,中間以冒號(hào):分隔,作為類型定義的一部分。遵循多個(gè)協(xié)議時(shí),各協(xié)議之間用逗號(hào),分隔。

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 結(jié)構(gòu)體內(nèi)容
}

如果類在遵循協(xié)議的同時(shí)擁有父類,應(yīng)該將父類名放在協(xié)議名之前,以逗號(hào)分隔。

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
    // 類的內(nèi)容
}

對(duì)屬性的規(guī)定

協(xié)議用于指定特定的實(shí)例屬性或類屬性,而不用指定是存儲(chǔ)型屬性或計(jì)算型屬性。此外還必須指明是只讀的還是可讀可寫的。

協(xié)議中的通常用var來聲明變量屬性,在類型聲明后加上{ set get }來表示屬性是可讀可寫的,只讀屬性則用{ get }來表示。

protocol classa {
    
    var marks: Int { get set }
    var result: Bool { get }
    
    func attendance() -> String
    func markssecured() -> String
    
}

protocol classb: classa {
    
    var present: Bool { get set }
    var subject: String { get set }
    var stname: String { get set }
    
}

class classc: classb {
    var marks = 96
    let result = true
    var present = false
    var subject = "Swift 協(xié)議"
    var stname = "Protocols"
    
    func attendance() -> String {
        return "The \(stname) has secured 99% attendance"
    }
    
    func markssecured() -> String {
        return "\(stname) has scored \(marks)"
    }
}

let studdet = classc()
studdet.stname = "Swift"
studdet.marks = 98
studdet.markssecured()

print(studdet.marks)
print(studdet.result)
print(studdet.present)
print(studdet.subject)
print(studdet.stname)

以上程序執(zhí)行輸出結(jié)果為:

98
true
false
Swift 協(xié)議
Swift

對(duì) Mutating 方法的規(guī)定

有時(shí)需要在方法中改變它的實(shí)例。

例如,值類型(結(jié)構(gòu)體,枚舉)的實(shí)例方法中,將mutating關(guān)鍵字作為函數(shù)的前綴,寫在func之前,表示可以在該方法中修改它所屬的實(shí)例及其實(shí)例屬性的值。

protocol daysofaweek {
    mutating func show()
}

enum days: daysofaweek {
    case sun, mon, tue, wed, thurs, fri, sat
    mutating func show() {
        switch self {
        case sun:
            self = sun
            print("Sunday")
        case mon:
            self = mon
            print("Monday")
        case tue:
            self = tue
            print("Tuesday")
        case wed:
            self = wed
            print("Wednesday")
        case mon:
            self = thurs
            print("Thursday")
        case tue:
            self = fri
            print("Friday")
        case sat:
            self = sat
            print("Saturday")
        default:
            print("NO Such Day")
        }
    }
}

var res = days.wed
res.show()

以上程序執(zhí)行輸出結(jié)果為:

Wednesday

對(duì)構(gòu)造器的規(guī)定

協(xié)議可以要求它的遵循者實(shí)現(xiàn)指定的構(gòu)造器。

你可以像書寫普通的構(gòu)造器那樣,在協(xié)議的定義里寫下構(gòu)造器的聲明,但不需要寫花括號(hào)和構(gòu)造器的實(shí)體,語法如下:

protocol SomeProtocol {
   init(someParameter: Int)
}

實(shí)例

protocol tcpprotocol {
   init(aprot: Int)
}

協(xié)議構(gòu)造器規(guī)定在類中的實(shí)現(xiàn)

你可以在遵循該協(xié)議的類中實(shí)現(xiàn)構(gòu)造器,并指定其為類的指定構(gòu)造器或者便利構(gòu)造器。在這兩種情況下,你都必須給構(gòu)造器實(shí)現(xiàn)標(biāo)上"required"修飾符:

class SomeClass: SomeProtocol {
   required init(someParameter: Int) {
      // 構(gòu)造器實(shí)現(xiàn)
   }
}

protocol tcpprotocol {
   init(aprot: Int)
}

class tcpClass: tcpprotocol {
   required init(aprot: Int) {
   }
}

使用required修飾符可以保證:所有的遵循該協(xié)議的子類,同樣能為構(gòu)造器規(guī)定提供一個(gè)顯式的實(shí)現(xiàn)或繼承實(shí)現(xiàn)。

如果一個(gè)子類重寫了父類的指定構(gòu)造器,并且該構(gòu)造器遵循了某個(gè)協(xié)議的規(guī)定,那么該構(gòu)造器的實(shí)現(xiàn)需要被同時(shí)標(biāo)示required和override修飾符:

protocol tcpprotocol {
    init(no1: Int)
}

class mainClass {
    var no1: Int // 局部變量
    init(no1: Int) {
        self.no1 = no1 // 初始化
    }
}

class subClass: mainClass, tcpprotocol {
    var no2: Int
    init(no1: Int, no2 : Int) {
        self.no2 = no2
        super.init(no1:no1)
    }
    // 因?yàn)樽裱瓍f(xié)議,需要加上"required"; 因?yàn)槔^承自父類,需要加上"override"
    required override convenience init(no1: Int)  {
        self.init(no1:no1, no2:0)
    }
}
let res = mainClass(no1: 20)
let show = subClass(no1: 30, no2: 50)

print("res is: \(res.no1)")
print("res is: \(show.no1)")
print("res is: \(show.no2)")

以上程序執(zhí)行輸出結(jié)果為:

res is: 20
res is: 30
res is: 50

協(xié)議類型

盡管協(xié)議本身并不實(shí)現(xiàn)任何功能,但是協(xié)議可以被當(dāng)做類型來使用。

協(xié)議可以像其他普通類型一樣使用,使用場(chǎng)景:

  • 作為函數(shù)、方法或構(gòu)造器中的參數(shù)類型或返回值類型
  • 作為常量、變量或?qū)傩缘念愋?/li>
  • 作為數(shù)組、字典或其他容器中的元素類型

實(shí)例

protocol Generator {
    typealias members
    func next() -> members?
}

var items = [10,20,30].generate()
while let x = items.next() {
    print(x)
}

for lists in [1,2,3].map( {i in i*5}) {
    print(lists)
}

print([100,200,300])
print([1,2,3].map({i in i*10}))

以上程序執(zhí)行輸出結(jié)果為:

10
20
30
5
10
15
[100, 200, 300]
[10, 20, 30]

在擴(kuò)展中添加協(xié)議成員

我們可以可以通過擴(kuò)展來擴(kuò)充已存在類型( 類,結(jié)構(gòu)體,枚舉等)。

擴(kuò)展可以為已存在的類型添加屬性,方法,下標(biāo)腳本,協(xié)議等成員。

protocol AgeClasificationProtocol {
   var age: Int { get }
   func agetype() -> String
}

class Person {
   let firstname: String
   let lastname: String
   var age: Int
   init(firstname: String, lastname: String) {
      self.firstname = firstname
      self.lastname = lastname
      self.age = 10
   }
}

extension Person : AgeClasificationProtocol {
   func fullname() -> String {
      var c: String
      c = firstname + " " + lastname
      return c
   }
   
   func agetype() -> String {
      switch age {
      case 0...2:
         return "Baby"
      case 2...12:
         return "Child"
      case 13...19:
         return "Teenager"
      case let x where x > 65:
         return "Elderly"
      default:
         return "Normal"
      }
   }
}

協(xié)議的繼承

協(xié)議能夠繼承一個(gè)或多個(gè)其他協(xié)議,可以在繼承的協(xié)議基礎(chǔ)上增加新的內(nèi)容要求。

協(xié)議的繼承語法與類的繼承相似,多個(gè)被繼承的協(xié)議間用逗號(hào)分隔:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // 協(xié)議定義
}

實(shí)例

protocol Classa {
    var no1: Int { get set }
    func calc(sum: Int)
}

protocol Result {
    func print(target: Classa)
}

class Student2: Result {
    func print(target: Classa) {
        target.calc(1)
    }
}

class Classb: Result {
    func print(target: Classa) {
        target.calc(5)
    }
}

class Student: Classa {
    var no1: Int = 10
    
    func calc(sum: Int) {
        no1 -= sum
        print("學(xué)生嘗試 \(sum) 次通過")
        
        if no1 <= 0 {
            print("學(xué)生缺席考試")
        }
    }
}

class Player {
    var stmark: Result!
    
    init(stmark: Result) {
        self.stmark = stmark
    }
    
    func print(target: Classa) {
        stmark.print(target)
    }
}

var marks = Player(stmark: Student2())
var marksec = Student()

marks.print(marksec)
marks.print(marksec)
marks.print(marksec)
marks.stmark = Classb()
marks.print(marksec)
marks.print(marksec)
marks.print(marksec)

以上程序執(zhí)行輸出結(jié)果為:

學(xué)生嘗試 1 次通過
學(xué)生嘗試 1 次通過
學(xué)生嘗試 1 次通過
學(xué)生嘗試 5 次通過
學(xué)生嘗試 5 次通過
學(xué)生缺席考試
學(xué)生嘗試 5 次通過
學(xué)生缺席考試

類專屬協(xié)議

你可以在協(xié)議的繼承列表中,通過添加class關(guān)鍵字,限制協(xié)議只能適配到類(class)類型。

該class關(guān)鍵字必須是第一個(gè)出現(xiàn)在協(xié)議的繼承列表中,其后,才是其他繼承協(xié)議。格式如下:

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
    // 協(xié)議定義
}

實(shí)例

protocol TcpProtocol {
    init(no1: Int)
}

class MainClass {
    var no1: Int // 局部變量
    init(no1: Int) {
        self.no1 = no1 // 初始化
    }
}

class SubClass: MainClass, TcpProtocol {
    var no2: Int
    init(no1: Int, no2 : Int) {
        self.no2 = no2
        super.init(no1:no1)
    }
    // 因?yàn)樽裱瓍f(xié)議,需要加上"required"; 因?yàn)槔^承自父類,需要加上"override"
    required override convenience init(no1: Int)  {
        self.init(no1:no1, no2:0)
    }
}

let res = MainClass(no1: 20)
let show = SubClass(no1: 30, no2: 50)

print("res is: \(res.no1)")
print("res is: \(show.no1)")
print("res is: \(show.no2)")

以上程序執(zhí)行輸出結(jié)果為:

res is: 20
res is: 30
res is: 50

協(xié)議合成

Swift 支持合成多個(gè)協(xié)議,這在我們需要同時(shí)遵循多個(gè)協(xié)議時(shí)非常有用。

語法格式如下:

protocol<SomeProtocol, AnotherProtocol>

實(shí)例

protocol Stname {
    var name: String { get }
}

protocol Stage {
    var age: Int { get }
}

struct Person: Stname, Stage {
    var name: String
    var age: Int
}

func show(celebrator: protocol<Stname, Stage>) {
    print("\(celebrator.name) is \(celebrator.age) years old")
}

let studname = Person(name: "Priya", age: 21)
print(studname)

let stud = Person(name: "Rehan", age: 29)
print(stud)

let student = Person(name: "Roshan", age: 19)
print(student)

以上程序執(zhí)行輸出結(jié)果為:

Person(name: "Priya", age: 21)
Person(name: "Rehan", age: 29)
Person(name: "Roshan", age: 19)

檢驗(yàn)協(xié)議的一致性

你可以使用is和as操作符來檢查是否遵循某一協(xié)議或強(qiáng)制轉(zhuǎn)化為某一類型。

  • is操作符用來檢查實(shí)例是否遵循了某個(gè)協(xié)議
  • as?返回一個(gè)可選值,當(dāng)實(shí)例遵循協(xié)議時(shí),返回該協(xié)議類型;否則返回nil。
  • as用以強(qiáng)制向下轉(zhuǎn)型,如果強(qiáng)轉(zhuǎn)失敗,會(huì)引起運(yùn)行時(shí)錯(cuò)誤。

實(shí)例

下面的例子定義了一個(gè) HasArea 的協(xié)議,要求有一個(gè)Double類型可讀的 area:

protocol HasArea {
    var area: Double { get }
}

// 定義了Circle類,都遵循了HasArea協(xié)議
class Circle: HasArea {
    let pi = 3.1415927
    var radius: Double
    var area: Double { return pi * radius * radius }
    init(radius: Double) { self.radius = radius }
}

// 定義了Country類,都遵循了HasArea協(xié)議
class Country: HasArea {
    var area: Double
    init(area: Double) { self.area = area }
}

// Animal是一個(gè)沒有實(shí)現(xiàn)HasArea協(xié)議的類
class Animal {
    var legs: Int
    init(legs: Int) { self.legs = legs }
}

let objects: [AnyObject] = [
    Circle(radius: 2.0),
    Country(area: 243_610),
    Animal(legs: 4)
]

for object in objects {
    // 對(duì)迭代出的每一個(gè)元素進(jìn)行檢查,看它是否遵循了HasArea協(xié)議
    if let objectWithArea = object as? HasArea {
        print("面積為 \(objectWithArea.area)")
    } else {
        print("沒有面積")
    }
}

以上程序執(zhí)行輸出結(jié)果為:

面積為 12.5663708
面積為 243610.0
沒有面積