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 .thurs:
            self = .thurs
            print("Wednesday")
        case .fri:
            self = .fri
            print("Firday")
        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 {
    associatedtype members
    func next() -> members?
}

var items = [10,20,30].makeIterator()
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 Stname {
    var name: String { get }
}

protocol Stage {
    var age: Int { get }
}

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

func show(celebrator: 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
沒有面積