Swift 協議 筆記

定義:
協議為方法、屬性以及其他特定的任務需求或功能定義藍圖

協議可被類、結構體、或枚舉類型采納以提供所需功能的具體實現。
滿足了協議中需求的任意類型都叫做遵循了該協議。

還可以擴展一個協議以實現其中的一些需求或實現一個符合類型的可以利用的附加功能。

屬性要求

協議可以要求所有遵循該協議的類型提供特定名字和類型的實例屬性或類型屬性。
協議并不會具體說明屬性是儲存型屬性還是計算型屬性——它只具體要求屬性有特定的名稱和類型
協議同時要求一個屬性必須明確是可讀的或可讀的和可寫的。

若協議要求一個屬性為可讀和可寫的,那么該屬性要求不能用常量存儲屬性或只讀計算屬性來滿足。

若協議只要求屬性為可讀的,那么任何種類的屬性都能滿足這個要求,而且如果你的代碼需要的話,該屬性也可以是可寫的。

屬性要求定義為變量屬性,在名稱前面使用 var 關鍵字。可讀寫的屬性使用 { get set } 來寫在聲明后面來明確,使用 { get } 來明確可讀的屬性

protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}

在協議中定義類型屬性時在前面添加 static 關鍵字
當類的實現使用 class 或 static 關鍵字前綴聲明類型屬性要求時,這個規則仍然適用:

protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
}
protocol FullyNamed {
    var fullName: String { get }
}
struct Person: FullyNamed {
    var fullName: String
}
let john = Person(fullName: "John Appleseed")
john.fullName is "John Appleseed"

若協議的要求沒有完全達標,Swift 在編譯時會報錯

方法要求

協議可以要求采納的類型實現指定的實例方法和類方法。
這些方法作為協議定義的一部分,書寫方式與正常實例和類方法的方式完全相同,但是不需要大括號和方法的主體
允許變量擁有參數,與正常的方法使用同樣的規則。
但在協議的定義中,方法參數不能定義默認值

正如類型屬性要求的那樣,當協議中定義類型方法時,你總要在其之前添加 static 關鍵字。即使在類實現時,類型方法要求使用 class 或 static 作為關鍵字前綴,前面的規則仍然適用
類型方法協議:

protocol SomeProtocol {
    static func someTypeMethod()
}

實例方法要求的協議:

protocol RandomNumberGenerator {
    func random() -> Double
}

異變方法要求

有時一個方法需要改變(或異變)其所屬的實例。
例如值類型的實例方法(即結構體或枚舉),在方法的 func 關鍵字之前使用 mutating 關鍵字,來表示在該方法可以改變其所屬的實例,以及該實例的所有屬性。

若你定義了一個協議的實例方法需求,想要異變任何采用了該協議的類型實例,只需在協議里方法的定義當中使用 mutating 關鍵字。這允許結構體和枚舉類型能采用相應協議并滿足方法要求。

如果你在協議中標記實例方法需求為 mutating ,在為類實現該方法的時候不需要寫 mutating 關鍵字。 mutating 關鍵字只在結構體和枚舉類型中需要書寫**。

protocol Togglable {
    mutating func toggle()
}

enum OnOffSwitch: Togglable {
    case off, on
    mutating func toggle() {
        switch self {
        case .off:
            self = .on
        case .on:
            self = .off
        }
    }
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
// lightSwitch is now equal to .on

初始化器要求

protocol SomeProtocol {
    init(someParameter: Int)
}

你可以通過實現指定初始化器或便捷初始化器來使遵循該協議的類滿足協議的初始化器要求。在這兩種情況下,你都必須使用 required 關鍵字修飾初始化器的實現

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // initializer implementation goes here
    }
}

在遵循協議的類的所有子類中, required 修飾的使用保證了你為協議初始化器要求提供了一個明確的繼承實現

final 的類不會有子類,如果協議初始化器實現的類使用了 final 標記,你就不需要使用 required 來修飾了。因為這樣的類不能被繼承子類

如果一個子類重寫了父類指定的初始化器,并且遵循協議實現了初始化器要求,那么就要為這個初始化器的實現添加 required 和 override 兩個修飾符:

protocol SomeProtocol {
    init()
}
 
class SomeSuperClass {
    init() {
        // initializer implementation goes here
    }
}
 
class SomeSubClass: SomeSuperClass, SomeProtocol {
    // "required" from SomeProtocol conformance; "override" from SomeSuperClass
    required override init() {
        // initializer implementation goes here
    }
}

將協議作為類型

實際上協議自身并不實現功能。不過你創建的任意協議都可以變為一個功能完備的類型在代碼中使用。

由于它是一個類型,你可以在很多其他類型可以使用的地方使用協議,包括:

在函數、方法或者初始化器里作為形式參數類型或者返回類型;
作為常量、變量或者屬性的類型;
作為數組、字典或者其他存儲器的元素的類型。

委托

委托是一個允許類或者結構體放手(或者說委托)它們自身的某些責任給另外類型實例的設計模式
這個設計模式通過定義一個封裝了委托責任的協議來實現,比如遵循了協議的類型(所謂的委托)來保證提供被委托的功能。
委托可以用來響應一個特定的行為,或者從外部資源取回數據而不需要了解資源具體的類型

protocol RandomNumberGenerator {
    func random() -> Double
}

class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m))
        return lastRandom / m
    }
}

class Dice {
    let sides: Int
    let generator: RandomNumberGenerator
    init(sides: Int, generator: RandomNumberGenerator) {
        self.sides = sides
        self.generator = generator
    }
    func roll() -> Int {
        return Int(generator.random() * Double(sides)) + 1
    }
}

protocol DiceGame {
    var dice: Dice { get }
    func play()
}
protocol DiceGameDelegate {
    func gameDidStart(_ game: DiceGame)
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(_ game: DiceGame)
}


class SnakesAndLadders: DiceGame {
    let finalSquare = 25
    let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
    var square = 0
    var board: [Int]
    init() {
        board = Array(repeating: 0, count: finalSquare + 1)
        board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
        board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
    }
    var delegate: DiceGameDelegate?
    func play() {
        square = 0
        delegate?.gameDidStart(self)
        gameLoop: while square != finalSquare {
            let diceRoll = dice.roll()
            delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
            switch square + diceRoll {
            case finalSquare:
                break gameLoop
            case let newSquare where newSquare > finalSquare:
                continue gameLoop
            default:
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}


class DiceGameTracker: DiceGameDelegate {
    var numberOfTurns = 0
    func gameDidStart(_ game: DiceGame) {
        numberOfTurns = 0
        if game is SnakesAndLadders {
            print("Started a new game of Snakes and Ladders")
        }
        print("The game is using a \(game.dice.sides)-sided dice")
    }
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
        numberOfTurns += 1
        print("Rolled a \(diceRoll)")
    }
    func gameDidEnd(_ game: DiceGame) {
        print("The game lasted for \(numberOfTurns) turns")
    }
}

let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()

log:
Started a new game of Snakes and Ladders
The game is using a 6-sided dice
Rolled a 3
Rolled a 5
Rolled a 4
Rolled a 5
The game lasted for 4 turns

在擴展里添加協議遵循

擴展一個已經存在的類型來采納和遵循一個新的協議,就算是你無法訪問現有類型的源代碼也行。擴展可以添加新的屬性、方法和下標到已經存在的類型,并且因此允許你添加協議需要的任何需要

類型已經存在的實例會在給它的類型擴展中添加遵循協議時自動地采納和遵循這個協議

協議繼承

協議可以繼承一個或者多個其他協議并且可以在它繼承的基礎之上添加更多要求。
協議繼承的語法與類繼承的語法相似,只不過可以選擇列出多個繼承的協議,使用逗號分隔:

類專用的協議

通過添加 class 關鍵字到協議的繼承列表,你就可以限制協議只能被類類型采納(并且不是結構體或者枚舉)。

class 關鍵字必須出現在協議繼承列表的最前邊,在任何繼承的協議之前:

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
    // class-only protocol definition goes here
}

協議遵循的檢查

可以使用類型轉換中描述的 is 和 as 運算符來檢查協議遵循,還能轉換為特定的協議。檢查和轉換協議的語法與檢查和轉換類型是完全一樣的

如果實例遵循協議is運算符返回 true 否則返回 false ;
as? 版本的向下轉換運算符返回協議的可選項,如果實例不遵循這個協議的話值就是 nil ;
as! 版本的向下轉換運算符強制轉換協議類型并且在失敗是觸發運行時錯誤。

可選協議要求

你可以給協議定義可選要求,這些要求不需要強制遵循協議的類型實現。

可選要求使用 optional 修飾符作為前綴放在協議的定義中。
可選要求允許你的代碼與 Objective-C 操作。協議和可選要求必須使用 @objc 標志標記。

注意 @objc 協議只能被繼承自 Objective-C 類或其他 @objc 類采納。它們不能被結構體或者枚舉采納。

協議擴展

提供默認實現

給協議擴展添加限制

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

推薦閱讀更多精彩內容