Swift 協定


協定規定了用來實作某一特定功能所必需的方法和屬性。

任意能夠滿足協定要求的型別稱為遵循(conform)這個協定。

類,結構體或枚舉類型都可以遵循協議,並提供具體實作來完成協議定義的方法和功能。

語法

協議的語法格式如下:

protocol SomeProtocol {
    // 协议内容
}

要使類別遵循某個協議,需要在類型名稱後面加上協議名稱,中間以冒號:分隔,作為類型定義的一部分。遵循多個協議時,各協議之間以逗號,分隔。

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 结构体内容
}

如果類別在遵循協定的同時擁有父類,應該將父類別名稱放在協定名稱之前,以逗號分隔。

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
    // 类的内容
}

對屬性的規定

協定用於指定特定的實例屬性或類別屬性,而不用指定是儲存型屬性或運算型屬性。另外還必須指明是唯讀的還是可讀可寫的。

協定中的通常用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 协议"
    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)

以上程式執行輸出結果為:

98
true
false
Swift 协议
Swift

對 Mutating 方法的規定

有時需要在方法中改變它的實例。

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

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()

以上程式執行輸出結果為:

Wednesday

對建構器的規定

協定可以要求它的遵循者實作指定的建構器。

你可以像書寫普通的建構器一樣,在協定的定義裡寫下建構器的聲明,但不需要寫花括號和建構器的實體,語法如下:

protocol SomeProtocol {
   init(someParameter: Int)
}

實例

protocol tcpprotocol {
   init(aprot: Int)
}

協定建構器規定在類別中的實作

你可以在遵循該協定的類別中實作建構器,並指定其為類別的指定建構器或便利構造器。在這兩種情況下,你都必須給建構器實作標上"required"修飾符:

class SomeClass: SomeProtocol {
   required init(someParameter: Int) {
      // 构造器实现
   }
}

protocol tcpprotocol {
   init(aprot: Int)
}

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

使用required修飾符可以保證:所有的遵循該協定的子類,同樣能為建構器規定提供一個明確的實作或繼承實作。

如果一個子類別重寫了父類別的指定建構器,並且該建構器遵循了某個協定的規定,那麼該建構器的實作需要同時標示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)
    }
    // 因为遵循协议,需要加上"required"; 因为继承自父类,需要加上"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)")

以上程式執行輸出結果為:

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

協定類型

儘管協定本身並不會實作任何功能,但是協定可以被當作類型來使用。

協定可以像其他普通型別一樣使用,使用場景:

  • 作為函數、方法或建構器中的參數型別或傳回值型別

  • #作為常數、變數或屬性的型別

  • 作為陣列、字典或其他容器中的元素類型

實例

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}))

以上程式執行輸出結果為:

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

在擴充功能中加入協定成員

我們可以透過擴充來擴充已存在型別( 類,結構體,列舉等)。

擴充功能可以為已存在的類型新增屬性,方法,下標腳本,協定等成員。

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"
      }
   }
}

協議的繼承

協議能夠繼承一個或多個其他協議,可以在繼承的協議基礎上增加新的內容要求。

協定的繼承語法與類別的繼承相似,多個被繼承的協定間用逗號分隔:


protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // 协议定义
}

實例

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("学生尝试 \(sum) 次通过")
        
        if no1 <= 0 {
            print("学生缺席考试")
        }
    }
}

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)

以上程式執行輸出結果為:

学生尝试 1 次通过
学生尝试 1 次通过
学生尝试 1 次通过
学生尝试 5 次通过
学生尝试 5 次通过
学生缺席考试
学生尝试 5 次通过
学生缺席考试

類別專屬協定

你可以在協定的繼承清單中,透過新增class關鍵字,限制協定只能適配到類別(class)類型。

該class關鍵字必須是第一個出現在協定的繼承清單中,其後,才是其他繼承協定。格式如下:

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
    // 协议定义
}

實例

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)
    }
    // 因为遵循协议,需要加上"required"; 因为继承自父类,需要加上"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)")

以上程式執行輸出結果為:

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

協定合成

Swift 支援合成多個協議,這在我們需要同時遵循多個協議時非常有用。

語法格式如下:

protocol<SomeProtocol, AnotherProtocol>

實例

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)

以上程式執行輸出結果為:

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

檢定協定的一致性

你可以使用is和as運算子來檢查是否遵循某一協定或強制轉換為某一類型。

  • is運算子用來檢查實例是否遵循了某個協定

  • as?傳回一個可選值,當實例遵循協定時,傳回該協定類型;否則傳回nil

  • as用以強制向下轉型,如果強轉失敗,會造成執行時錯誤。

實例

下面的範例定義了一個HasArea 的協議,要求有一個Double類型可讀的area:

protocol HasArea {
    var area: Double { get }
}

// 定义了Circle类,都遵循了HasArea协议
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协议
class Country: HasArea {
    var area: Double
    init(area: Double) { self.area = area }
}

// Animal是一个没有实现HasArea协议的类
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 {
    // 对迭代出的每一个元素进行检查,看它是否遵循了HasArea协议
    if let objectWithArea = object as? HasArea {
        print("面积为 \(objectWithArea.area)")
    } else {
        print("没有面积")
    }
}

以上程式執行輸出結果為:

面积为 12.5663708
面积为 243610.0
没有面积