Swift基礎-15(協(xié)議上)

協(xié)議 定義了一個藍圖喊儡,規(guī)定了用來實現(xiàn)某一特定任務或者功能的方法颊糜、屬性哩治,以及其他需要的東西。類衬鱼、結(jié)構(gòu)體或枚舉都可以遵循協(xié)議业筏,并為協(xié)議定義的這些要求提供具體實現(xiàn)。某個類型能夠滿足某個協(xié)議的要求馁启,就可以說該類型遵循這個協(xié)議驾孔。
除了遵循協(xié)議的類型必須實現(xiàn)的要求外,還可以對協(xié)議進行擴展惯疙,通過擴展來實現(xiàn)一部分要求或者實現(xiàn)一些附加功能翠勉,這樣遵循協(xié)議的類型就能夠使用這些功能。

1.協(xié)議語法

協(xié)議的定義方式與類霉颠、結(jié)構(gòu)體和枚舉的定義非常相似:

protocol SomeProtocol {
   // 這里是協(xié)議的定義部分
}

要讓自定義類型遵循某個協(xié)議对碌,在定義類型時,需要在類型名稱后加上協(xié)議名稱蒿偎,中間以冒號(:)分隔朽们。遵循多個協(xié)議時,各協(xié)議之間用逗號(,)分隔:

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 這里是結(jié)構(gòu)體的定義部分
}

擁有父類的類在遵循協(xié)議時诉位,應該將父類名放在協(xié)議名之前骑脱,以逗號分隔:

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
    // 這里是類的定義部分
}
2.屬性要求

協(xié)議可以要求遵循協(xié)議的類型提供特定名稱和類型的實例屬性或類型屬性。協(xié)議不指定屬性是存儲型屬性還是計算型屬性苍糠,它只指定屬性的名稱和類型叁丧。此外,協(xié)議還指定屬性是可讀的還是可讀可寫的岳瞭。

如果協(xié)議要求屬性是可讀可寫的拥娄,那么該屬性不能是常量屬性或只讀的計算型屬性。如果協(xié)議只要求屬性是可讀的瞳筏,那么該屬性不僅可以是可讀的稚瘾,如果代碼需要的話,還可以是可寫的姚炕。

協(xié)議總是用 var 關鍵字來聲明變量屬性摊欠,在類型聲明后加上 { set get } 來表示屬性是可讀可寫的丢烘,可讀屬性則用 { get } 來表示:

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

在協(xié)議中定義類型屬性時,總是使用 static 關鍵字作為前綴凄硼。當類類型遵循協(xié)議時铅协,除了 static 關鍵字,還可以使用 class 關鍵字來聲明類型屬性:

protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
}

如下所示摊沉,這是一個只含有一個實例屬性要求的協(xié)議:

protocol FullyNamed {
    var fullName: String { get }
}

FullyNamed 協(xié)議除了要求遵循協(xié)議的類型提供 fullName 屬性外狐史,并沒有其他特別的要求。這個協(xié)議表示说墨,任何遵循 FullyNamed 的類型骏全,都必須有一個可讀的 String 類型的實例屬性 fullName。

struct Person: FullyNamed {
    var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName 為 "John Appleseed”

Person 結(jié)構(gòu)體的每一個實例都有一個 String 類型的存儲型屬性 fullName尼斧。這正好滿足了 FullyNamed 協(xié)議的要求姜贡,也就意味著 Person 結(jié)構(gòu)體正確地符合了協(xié)議。(如果協(xié)議要求未被完全滿足棺棵,在編譯時會報錯)楼咳。
看一個更加復雜的類,他遵循FullyNamed協(xié)議烛恤。

class Starship: FullyNamed {
    var prefix: String?
    var name: String
    init(name: String, prefix: String? = nil) {
        self.name = name
        self.prefix = prefix
    }
    var fullName: String {
        return (prefix != nil ? prefix! + " " : "") + name
    }
3.方法要求

協(xié)議可以要求遵循協(xié)議的類型實現(xiàn)某些指定的實例方法或類方法母怜。這些方法作為協(xié)議的一部分,像普通方法一樣放在協(xié)議的定義中缚柏,但是不需要大括號和方法體苹熏。可以在協(xié)議中定義具有可變參數(shù)的方法币喧,和普通方法的定義方式相同轨域。但是,不支持為協(xié)議中的方法的參數(shù)提供默認值杀餐。
正如屬性要求中所述干发,在協(xié)議中定義類方法的時候,總是使用 static 關鍵字作為前綴史翘。當類類型遵循協(xié)議時枉长,除了static 關鍵字,還可以使用 class關鍵字作為前綴:”

protocol SomeProtocol {
    static func someTypeMethod()
}

protocol RandomNumberGenerator {
    func random() -> Double
}

我們定義一個遵守RandomNumberGenerator協(xié)議的類

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) % m)
        return lastRandom / m
    }
}

let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// 打印 “Here's a random number: 0.37464991998171”
print("And another one: \(generator.random())")
// 打印 “And another one: 0.729023776863283
4.Mutating方法要求

有時需要在方法中改變方法所屬的實例恶座。例如搀暑,在值類型(即結(jié)構(gòu)體和枚舉)的實例方法中沥阳,將 mutating關鍵字作為方法的前綴跨琳,寫在 func 關鍵字之前,表示可以在該方法中修改它所屬的實例以及實例的任意屬性的值桐罕。

注意
實現(xiàn)協(xié)議中的 mutating 方法時脉让,若是類類型桂敛,則不用寫 mutating 關鍵字。而對于結(jié)構(gòu)體和枚舉溅潜,則必須寫 mutating 關鍵字术唬。

protocol Togglable {
    mutating func toggle()
}

定義了一個名為 OnOffSwitch 的枚舉。這個枚舉在兩種狀態(tài)之間進行切換滚澜,用枚舉成員 On 和 Off 表示粗仓。枚舉的 toggle() 方法被標記為 mutating,以滿足 Togglable 協(xié)議的要求:

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 現(xiàn)在的值為 .On
5.構(gòu)造器要求

協(xié)議可以要求遵循協(xié)議的類型實現(xiàn)指定的構(gòu)造器设捐。你可以像編寫普通構(gòu)造器那樣借浊,在協(xié)議的定義里寫下構(gòu)造器的聲明,但不需要寫花括號和構(gòu)造器的實體:

protocol SomeProtocol {
    init(someParameter: Int)
}
  • 構(gòu)造器要求在類中的實現(xiàn)

你可以在遵循協(xié)議的類中實現(xiàn)構(gòu)造器萝招,無論是作為指定構(gòu)造器蚂斤,還是作為便利構(gòu)造器。無論哪種情況槐沼,你都必須為構(gòu)造器實現(xiàn)標上 required 修飾符:

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // 這里是構(gòu)造器的實現(xiàn)部分
    }
}

使用 required 修飾符可以確保所有子類也必須提供此構(gòu)造器實現(xiàn)曙蒸,從而也能符合協(xié)議。

注意:如果類已經(jīng)被標記為 final岗钩,那么不需要在協(xié)議構(gòu)造器的實現(xiàn)中使用 required 修飾符纽窟,因為 final 類不能有子類。關于 final 修飾符的更多內(nèi)容凹嘲。

如果一個子類重寫了父類的指定構(gòu)造器师倔,并且該構(gòu)造器滿足了某個協(xié)議的要求,那么該構(gòu)造器的實現(xiàn)需要同時標注requiredoverride 修飾符:

protocol SomeProtocol {
    init()
}
 
class SomeSuperClass {
    init() {
        // 這里是構(gòu)造器的實現(xiàn)部分
    }
}
 
class SomeSubClass: SomeSuperClass, SomeProtocol {
    // 因為遵循協(xié)議周蹭,需要加上 required
    // 因為繼承自父類趋艘,需要加上 override
    required override init() {
        // 這里是構(gòu)造器的實現(xiàn)部分
    }
}
6.協(xié)議作為類型

盡管協(xié)議本身并未實現(xiàn)任何功能,但是協(xié)議可以被當做一個成熟的類型來使用凶朗。
協(xié)議可以像其他普通類型一樣使用瓷胧,使用場景如下:

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

注意
協(xié)議是一種類型,因此協(xié)議類型的名稱應與其他類型(例如 Int宛畦,Double瘸洛,String)的寫法相同,使用大寫字母開頭的駝峰式寫法次和,例如(FullyNamed 和 RandomNumberGenerator),RandomNumberGenerator 協(xié)議

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

var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
    print("Random dice roll is \(d6.roll())")
}
// Random dice roll is 3
// Random dice roll is 5
// Random dice roll is 4
// Random dice roll is 5
// Random dice roll is 4
7.委托(代理)模式

委托是一種設計模式反肋,它允許類或結(jié)構(gòu)體將一些需要它們負責的功能委托給其他類型的實例。委托模式的實現(xiàn)很簡單:定義協(xié)議來封裝那些需要被委托的功能踏施,這樣就能確保遵循協(xié)議的類型能提供這些功能石蔗。委托模式可以用來響應特定的動作罕邀,或者接收外部數(shù)據(jù)源提供的數(shù)據(jù),而無需關心外部數(shù)據(jù)源的類型养距。
通過一段代碼來看一下代理的使用:

protocol RandomNumberGenerator {
    func random() -> Double
}

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

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 SnakesAndLadders: DiceGame {
    
    let finalSquare = 25
    let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
    var square = 0
    var board: [Int]
    init() {
        board = [Int](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()

// 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”
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诉探,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子棍厌,更是在濱河造成了極大的恐慌肾胯,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耘纱,死亡現(xiàn)場離奇詭異阳液,居然都是意外死亡,警方通過查閱死者的電腦和手機揣炕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門帘皿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人畸陡,你說我怎么就攤上這事鹰溜。” “怎么了丁恭?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵曹动,是天一觀的道長。 經(jīng)常有香客問我牲览,道長墓陈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任第献,我火速辦了婚禮贡必,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘庸毫。我一直安慰自己仔拟,他們只是感情好,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布飒赃。 她就那樣靜靜地躺著利花,像睡著了一般。 火紅的嫁衣襯著肌膚如雪载佳。 梳的紋絲不亂的頭發(fā)上炒事,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機與錄音蔫慧,去河邊找鬼挠乳。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的欲侮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼肋联,長吁一口氣:“原來是場噩夢啊……” “哼威蕉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起橄仍,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤韧涨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后侮繁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體虑粥,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年宪哩,在試婚紗的時候發(fā)現(xiàn)自己被綠了娩贷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡锁孟,死狀恐怖彬祖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情品抽,我是刑警寧澤储笑,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站圆恤,受9級特大地震影響突倍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜盆昙,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一羽历、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧淡喜,春花似錦窄陡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至们镜,卻和暖如春币叹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背模狭。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工颈抚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓贩汉,卻偏偏與公主長得像驱富,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子匹舞,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 本章將會介紹 協(xié)議語法屬性要求方法要求(Method Requirements)Mutating 方法要求構(gòu)造器要...
    寒橋閱讀 419評論 0 3
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young閱讀 3,802評論 1 10
  • 擴展 擴展就是向一個已有的類褐鸥、結(jié)構(gòu)體、枚舉類型或者協(xié)議類型添加新功能赐稽。這包括在沒有權(quán)限獲取原始源代碼的情況下擴展類...
    cht005288閱讀 465評論 0 0
  • 國家電網(wǎng)公司企業(yè)標準(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 10,960評論 6 13
  • 定義: 協(xié)議定義了一個藍圖叫榕,規(guī)定了用來實現(xiàn)某一特定的任務或者功能的方法、屬性姊舵,或其他需要的東西晰绎。類、結(jié)構(gòu)體括丁、枚舉都...
    geekLiu閱讀 1,439評論 0 1