原創|使用教程|編輯:龔雪|2014-06-19 09:33:07.000|閱讀 4525 次
概述:本文為Swift編程語言中文教程第二十一部分,講解協議(Protocol),內容包括:Swift協議的語法(Protocol Syntax)、屬性要求(Property Requirements)、協議類型(Protocols as Types)、協議的繼承(Protocol Inheritance)等。Swift是蘋果公司在WWDC2014發布的一門編程語言,與Objective-C相比,對學習新手比較友好。慧都控件網根據官方教程以及網上中文資源整理了Swift編程語言中文教程,希望幫助想要學習Swift的朋友,由于技術有限,可能有不足的地方,希望大家指正。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
相關鏈接:
本頁包含內容:
Protocol(協議)用于統一方法和屬性的名稱,而不實現任何功能。協議能夠被類,枚舉,結構體實現,滿足協議要求的類,枚舉,結構體被稱為協議的遵循者。
遵循者需要提供協議指定的成員,如屬性,方法,操作符,下標等。
協議的定義與類,結構體,枚舉的定義非常相似,如下所示:
protocol SomeProtocol {
    // 協議內容
}
在類,結構體,枚舉的名稱后加上協議名稱,中間以冒號:分隔即可實現協議;實現多個協議時,各協議之間用逗號,分隔,如下所示:
struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 結構體內容
}
當某個類含有父類的同時并實現了協議,應當把父類放在所有的協議之前,如下所示:
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
    // 類的內容
}
協議能夠要求其遵循者必須含有一些特定名稱和類型的實例屬性(instance property)或類屬性 (type property),也能夠要求屬性具有(設置權限)settable 和(訪問權限)gettable,但它不要求屬性是存儲型屬性(stored property)還是計算型屬性(calculate property)。
如果協議要求屬性具有設置權限和訪問權限,那常量存儲型屬性或者只讀計算型屬性都無法滿足此要求。如果協議只要求屬性具有訪問權限,那任何類型的屬性都可以滿足此要求,無論這些屬性是否具有設置權限。
通常前置var關鍵字將屬性聲明為變量。在屬性聲明后寫上{ get set }表示屬性為可讀寫的。{ get }用來表示屬性為可讀的。即使你為可讀的屬性實現了setter方法,它也不會出錯。
protocol SomeProtocol {
    var musBeSettable : Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}
在協議中定義類屬性 (type property)時使用class前綴關鍵字,即使在結構體或者枚舉中類屬性是要求使用static前綴關鍵字:
protocol AnotherProtocol {
    class var someTypeProperty: Int { get set }
}
protocol FullyNamed {
    var fullName: String { get }
}
FullyNamed協議含有fullName屬性。因此其遵循者必須含有一個名為fullName,類型為String的可讀屬性。
struct Person: FullyNamed{
    var fullName: String
}
let john = Person(fullName: "John Appleseed")
//john.fullName 為 "John Appleseed" 
Person結構體含有一個名為fullName的存儲型屬性,完整的遵循了協議。(若協議未被完整遵循,編譯時則會報錯)。
如下所示,Startship類遵循了FullyNamed協議:
class Starship: FullyNamed {
    var prefix: String?
    var name: String
    init(name: String, prefix: String? = nil ) {
        self.anme = name
        self.prefix = prefix
    }
    var fullName: String {
    return (prefix ? prefix ! + " " : " ") + name
    }
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName == "USS Enterprise"
Starship類將fullName實現為可讀的計算型屬性。它的每一個實例都有一個名為name的必備屬性和一個名為prefix的可選屬性。 當prefix存在時,將prefix插入到name之前來為Starship構建fullName。
協議能夠要求其遵循者必備某些特定的實例方法和類方法。協議方法的聲明與普通方法聲明相似,但它不需要方法內容。
注意: 協議方法支持變長參數(variadic parameter),不支持默認參數(default parameter)。
前置class關鍵字表示協議中的成員為類成員;當協議用于被枚舉或結構體遵循時,則使用static關鍵字。如下所示: 像類屬性的要求一樣,協議中定義類方法時也總是使用class關鍵字,即使類方法在枚舉或結構體中實現時要求使用static關鍵字
protocol SomeProtocol {
    class func someTypeMethod()
}
protocol RandomNumberGenerator {
    func random() -> Double
}
RandomNumberGenerator協議要求其遵循者必須擁有一個名為random, 返回值類型為Double的實例方法。(我們假設隨機數在[0,1]區間內)。
LinearCongruentialGenerator類遵循了RandomNumberGenerator協議,并提供了一個叫做線性同余生成器(linear congruential generator)的偽隨機數算法。
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()
println("Here's a random number: \(generator.random())")
// 輸出 : "Here's a random number: 0.37464991998171"
println("And another one: \(generator.random())")
// 輸出 : "And another one: 0.729023776863283"
能在方法或函數內部改變實例類型的方法稱為突變方法。在值類型(Value Type)(譯者注:特指結構體和枚舉)中的的函數前綴加上mutating關鍵字來表示該函數允許改變該實例和其屬性的類型。 這一變換過程在實例方法(Instance Methods)章節中有詳細描述。
(譯者注:類中的成員為引用類型(Reference Type),可以方便的修改實例及其屬性的值而無需改變類型;而結構體和枚舉中的成員均為值類型(Value Type),修改變量的值就相當于修改變量的類型,而Swift默認不允許修改類型,因此需要前置mutating關鍵字用來表示該函數中能夠修改類型)
注意: 用class實現協議中的mutating方法時,不用寫mutating關鍵字;用結構體,枚舉實現協議中的mutating方法時,必須寫mutating關鍵字。
如下所示,Togglable協議含有toggle函數。根據函數名稱推測,toggle可能用于切換或恢復某個屬性的狀態。mutating關鍵字表示它為突變方法:
protocol Togglable {
    mutating func toggle()
}
當使用枚舉或結構體來實現Togglabl協議時,必須在toggle方法前加上mutating關鍵字。
如下所示,OnOffSwitch枚舉遵循了Togglable協議,On,Off兩個成員用于表示當前狀態
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 現在的值為 .On
協議本身不實現任何功能,但你可以將它當做類型來使用。
使用場景:
注意: 協議類型應與其他類型(Int,Double,String)的寫法相同,使用駝峰式
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
    }
}
這里定義了一個名為 Dice的類,用來代表桌游中的N個面的骰子。
Dice含有sides和generator兩個屬性,前者用來表示骰子有幾個面,后者為骰子提供一個隨機數生成器。由于后者為RandomNumberGenerator的協議類型。所以它能夠被賦值為任意遵循該協議的類型。
此外,使用構造器(init)來代替之前版本中的setup操作。構造器中含有一個名為generator,類型為RandomNumberGenerator的形參,使得它可以接收任意遵循RandomNumberGenerator協議的類型。
roll方法用來模擬骰子的面值。它先使用generator的random方法來創建一個[0-1]區間內的隨機數種子,然后加工這個隨機數種子生成骰子的面值。
如下所示,LinearCongruentialGenerator的實例作為隨機數生成器傳入Dice的構造器
var d6 = Dice(sides: 6,generator: LinearCongruentialGenerator())
for _ in 1...5 {
    println("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
委托是一種設計模式,它允許類或結構體將一些需要它們負責的功能交由(委托)給其他的類型。
委托模式的實現很簡單: 定義協議來封裝那些需要被委托的函數和方法, 使其遵循者擁有這些被委托的函數和方法。
委托模式可以用來響應特定的動作或接收外部數據源提供的數據,而無需要知道外部數據源的類型。
下文是兩個基于骰子游戲的協議:
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)
}
DiceGame協議可以在任意含有骰子的游戲中實現,DiceGameDelegate協議可以用來追蹤DiceGame的游戲過程。
如下所示,SnakesAndLadders是Snakes and Ladders(譯者注:控制流章節有該游戲的詳細介紹)游戲的新版本。新版本使用Dice作為骰子,并且實現了DiceGame和DiceGameDelegate協議
class SnakesAndLadders: DiceGame {
    let finalSquare = 25
    let dic = Dice(sides: 6, generator: LinearCongruentialGenerator())
    var square = 0
    var board: Int[]
    init() {
        board = Int[](count: finalSquare + 1, repeatedValue: 0)
        board[03] = +08; board[06] = +11; borad[09] = +09; board[10] = +02
        borad[14] = -10; board[19] = -11; borad[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)
     }
}
游戲的初始化設置(setup)被SnakesAndLadders類的構造器(initializer)實現。所有的游戲邏輯被轉移到了play方法中。
注意: 因為delegate并不是該游戲的必備條件,delegate被定義為遵循DiceGameDelegate協議的可選屬性
DicegameDelegate協議提供了三個方法用來追蹤游戲過程。被放置于游戲的邏輯中,即play()方法內。分別在游戲開始時,新一輪開始時,游戲結束時被調用。
因為delegate是一個遵循DiceGameDelegate的可選屬性,因此在play()方法中使用了可選鏈來調用委托方法。 若delegate屬性為nil, 則委托調用優雅地失效。若delegate不為nil,則委托方法被調用
如下所示,DiceGameTracker遵循了DiceGameDelegate協議
class DiceGameTracker: DiceGameDelegate {
    var numberOfTurns = 0
    func gameDidStart(game: DiceGame) {
        numberOfTurns = 0
        if game is SnakesAndLadders {
            println("Started a new game of Snakes and Ladders")
        }
        println("The game is using a \(game.dice.sides)-sided dice")
    }
    func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
        ++numberOfTurns
        println("Rolled a \(diceRoll)")
    }
    func gameDidEnd(game: DiceGame) {
        println("The game lasted for \(numberOfTurns) turns")
    }
}
DiceGameTracker實現了DiceGameDelegate協議的方法要求,用來記錄游戲已經進行的輪數。 當游戲開始時,numberOfTurns屬性被賦值為0;在每新一輪中遞加;游戲結束后,輸出打印游戲的總輪數。
gameDidStart方法從game參數獲取游戲信息并輸出。game在方法中被當做DiceGame類型而不是SnakeAndLadders類型,所以方法中只能訪問DiceGame協議中的成員。
DiceGameTracker的運行情況,如下所示:
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
即便無法修改源代碼,依然可以通過擴展(Extension)來擴充已存在類型(譯者注: 類,結構體,枚舉等)。擴展可以為已存在的類型添加屬性,方法,下標,協議等成員。詳情請在擴展章節中查看。
注意: 通過擴展為已存在的類型遵循協議時,該類型的所有實例也會隨之添加協議中的方法
TextRepresentable協議含有一個asText,如下所示:
protocol TextRepresentable {
    func asText() -> String
}
通過擴展為上一節中提到的Dice類遵循TextRepresentable協議
extension Dice: TextRepresentable {
    cun asText() -> String {
        return "A \(sides)-sided dice"
    }
}
從現在起,Dice類型的實例可被當作TextRepresentable類型:
let d12 = Dice(sides: 12,generator: LinearCongruentialGenerator()) println(d12.asText()) // 輸出 "A 12-sided dice"
SnakesAndLadders類也可以通過擴展的方式來遵循協議:
extension SnakeAndLadders: TextRepresentable {
    func asText() -> String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}
println(game.asText())
// 輸出 "A game of Snakes and Ladders with 25 squares"
當一個類型已經實現了協議中的所有要求,卻沒有聲明時,可以通過擴展來補充協議聲明:
struct Hamster {
    var name: String
    func asText() -> String {
        return "A hamster named \(name)"
    }
}
extension Hamster: TextRepresentabl {}
從現在起,Hamster的實例可以作為TextRepresentable類型使用
let simonTheHamster = Hamster(name: "Simon") let somethingTextRepresentable: TextRepresentabl = simonTheHamester println(somethingTextRepresentable.asText()) // 輸出 "A hamster named Simon"
注意: 即時滿足了協議的所有要求,類型也不會自動轉變,因此你必須為它做出明顯的協議聲明
協議類型可以被集合使用,表示集合中的元素均為協議類型:
let things: TextRepresentable[] = [game,d12,simoTheHamster]
如下所示,things數組可以被直接遍歷,并調用其中元素的asText()函數:
for thing in things {
    println(thing.asText())
}
// A game of Snakes and Ladders with 25 squares
// A 12-sided dice
// A hamster named Simon
thing被當做是TextRepresentable類型而不是Dice,DiceGame,Hamster等類型。因此能且僅能調用asText方法
協議能夠繼承一到多個其他協議。語法與類的繼承相似,多個協議間用逗號,分隔
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // 協議定義
}
如下所示,PrettyTextRepresentable協議繼承了TextRepresentable協議
protocol PrettyTextRepresentable: TextRepresentable {
    func asPrettyText() -> String
} 
遵循``PrettyTextRepresentable協議的同時,也需要遵循TextRepresentable`協議。
如下所示,用擴展為SnakesAndLadders遵循PrettyTextRepresentable協議:
extension SnakesAndLadders: PrettyTextRepresentable {
    func asPrettyText() -> String {
        var output = asText() + ":\n"
        for index in 1...finalSquare {
            switch board[index] {
                case let ladder where ladder > 0:
                output += "▲ "
            case let snake where snake < 0:
                output += "▼ "
            default:
                output += "○ "
            }
        }
        return output
    }
}
在for in中迭代出了board數組中的每一個元素:
任意SankesAndLadders的實例都可以使用asPrettyText()方法。
println(game.asPrettyText()) // A game of Snakes and Ladders with 25 squares: // ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
一個協議可由多個協議采用protocol<SomeProtocol, AnotherProtocol>這樣的格式進行組合,稱為協議合成(protocol composition)。
舉個例子:
protocol Named {
    var name: String { get }
}
protocol Aged {
    var age: Int { get }
}
struct Person: Named, Aged {
    var name: String
    var age: Int
}
func wishHappyBirthday(celebrator: protocol<Named, Aged>) {
    println("Happy birthday \(celebrator.name) - you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(birthdayPerson)
// 輸出 "Happy birthday Malcolm - you're 21!
Named協議包含String類型的name屬性;Aged協議包含Int類型的age屬性。Person結構體遵循了這兩個協議。
wishHappyBirthday函數的形參celebrator的類型為protocol<Named,Aged>。可以傳入任意遵循這兩個協議的類型的實例
注意: 協議合成并不會生成一個新協議類型,而是將多個協議合成為一個臨時的協議,超出范圍后立即失效。
使用is檢驗協議一致性,使用as將協議類型向下轉換(downcast)為的其他協議類型。檢驗與轉換的語法和之前相同(詳情查看類型檢查):
@objc protocol HasArea {
    var area: Double { get }
}
注意: @objc用來表示協議是可選的,也可以用來表示暴露給Objective-C的代碼,此外,@objc型協議只對類有效,因此只能在類中檢查協議的一致性。詳情查看。
class Circle: HasArea {
    let pi = 3.1415927
    var radius: Double
    var area:≈radius }
    init(radius: Double) { self.radius = radius }
}
class Country: HasArea {
    var area: Double
    init(area: Double) { self.area = area }
}
Circle和Country都遵循了HasArea協議,前者把area寫為計算型屬性(computed property),后者則把area寫為存儲型屬性(stored property)。
如下所示,Animal類沒有實現任何協議
class Animal {
    var legs: Int
    init(legs: Int) { self.legs = legs }
}
Circle,Country,Animal并沒有一個相同的基類,所以采用AnyObject類型的數組來裝載在它們的實例,如下所示:
let objects: AnyObject[] = [
    Circle(radius: 2.0),
    Country(area: 243_610),
    Animal(legs: 4)
]
如下所示,在迭代時檢查object數組的元素是否遵循了HasArea協議:
for object in objects {
    if let objectWithArea = object as? HasArea {
        println("Area is \(objectWithArea.area)")
    } else {
        println("Something that doesn't have an area")
    }
}
// Area is 12.5663708
// Area is 243610.0
// Something that doesn't have an area
當數組中的元素遵循HasArea協議時,通過as?操作符將其可選綁定(optional binding)到objectWithArea常量上。
objects數組中元素的類型并不會因為向下轉型而改變,當它們被賦值給objectWithArea時只被視為HasArea類型,因此只有area屬性能夠被訪問。
可選協議含有可選成員,其遵循者可以選擇是否實現這些成員。在協議中使用@optional關鍵字作為前綴來定義可選成員。
可選協議在調用時使用可選鏈,詳細內容在可選鏈章節中查看。
像someOptionalMethod?(someArgument)一樣,你可以在可選方法名稱后加上?來檢查該方法是否被實現。可選方法和可選屬性都會返回一個可選值(optional value),當其不可訪問時,?之后語句不會執行,并返回nil。
注意: 可選協議只能在含有@objc前綴的協議中生效。且@objc的協議只能被類遵循。
Counter類使用CounterDataSource類型的外部數據源來提供增量值(increment amount),如下所示:
@objc protocol CounterDataSource {
    @optional func incrementForCount(count: Int) -> Int
    @optional var fixedIncrement: Int { get }
}
CounterDataSource含有incrementForCount的可選方法和fiexdIncrement的可選屬性。
注意: CounterDataSource中的屬性和方法都是可選的,因此可以在類中聲明但不實現這些成員,盡管技術上允許這樣做,不過最好不要這樣寫。
Counter類含有CounterDataSource?類型的可選屬性dataSource,如下所示:
@objc class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    func increment() {
        if let amount = dataSource?.incrementForCount?(count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement? {
            count += amount
        }
    }
}
count屬性用于存儲當前的值,increment方法用來為count賦值。
increment方法通過可選鏈,嘗試從兩種可選成員中獲取count。
在調用incrementForCount方法后,Int型可選值通過可選綁定(optional binding)自動拆包并賦值給常量amount。
當incrementForCount不能被調用時,嘗試使用可選屬性``fixedIncrement來代替。
ThreeSource實現了CounterDataSource協議,如下所示:
class ThreeSource: CounterDataSource {
    let fixedIncrement = 3
}
使用ThreeSource作為數據源開實例化一個Counter:
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
    counter.increment()
    println(counter.count)
}
// 3
// 6
// 9
// 12
TowardsZeroSource實現了CounterDataSource協議中的incrementForCount方法,如下所示:
class TowardsZeroSource: CounterDataSource {
func incrementForCount(count: Int) -> Int {
        if count == 0 {
            return 0
        } else if count < 0 {
            return 1
        } else {
            return -1
        }
    }
}
下邊是執行的代碼:
counter.count = -4
counter.dataSource = TowardsZeroSource()
for _ in 1...5 {
    counter.increment()
    println(counter.count)
}
// -3
// -2
// -1
// 0
// 0
					本站文章除注明轉載外,均為本站原創或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@ke049m.cn