美文网首页iOS 深度好文SwiftSwift
Swift 协议(protocol)详解

Swift 协议(protocol)详解

作者: WSJay | 来源:发表于2018-08-15 14:58 被阅读330次

    1.协议的语法

    定义协议:

    protocol SomeProtocol {
        // protocol definition goes here
    }
    

    遵守协议:

    struct SomeStructure: FirstProtocol, AnotherProtocol {
        // structure definition goes here
    }
    

    当一个类既有父类,又遵守其他协议时,将父类名写在所遵守协议的前面:

    class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
        // class definition goes here
    }
    

    2.属性的要求

    • 在协议中,实例属性总是使用var声明为变量属性。可读的与可设置的属性在类型声明后通过写入{get set}表示,可读的属性通过写入{get}表示。
    protocol SomeProtocol {
        var mustBeSettable: Int { get set }
        var doesNotNeedToBeSettable: Int { get }
    }
    
    • 在协议中,类属性始终使用static关键字作为类属性声明的前缀。在类中实现时,可以使用classstatic关键字作类属性声明前缀。
    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"
    
    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
        }
    }
    var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
    // ncc1701.fullName is "USS Enterprise"
    
    

    3.方法的要求

    • 在协议中声明的实例方法和类方法没有方法体,允许可变参数,但是不能为协议中的方法参数指定默认值;
    • 在协议中,类方法始终使用static关键字作为前缀。在类中实现时,可以使用classstatic关键字作类方法声明前缀。
    protocol SomeProtocol {
        static func someTypeMethod()
    }
    

    示例:

    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
        }
    }
    let generator = LinearCongruentialGenerator()
    print("Here's a random number: \(generator.random())")
    // Prints "Here's a random number: 0.3746499199817101"
    print("And another one: \(generator.random())")
    // Prints "And another one: 0.729023776863283"
    
    • 在协议中,可变实例方法使用关键字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
    

    5.构造器的要求

    • 协议可以要求符合的类型来实现特定的构造器。可以将这些构造器作为协议定义的一部分。
    protocol SomeProtocol {
        init(someParameter: Int)
    }
    
    • 在符合构造器协议的类中,可以将协议构造器作为该类的指定构造器或便利构造器。在这两种情况中,必须使用required修饰这些构造器。required修饰符确保在该类的所有子类上提供构造器的显式或继承的实现。
    class SomeClass: SomeProtocol {
        required init(someParameter: Int) {
            // initializer implementation goes here
        }
    }
    

    注意:在使用final关键字修饰的类上,不需要使用required关键字标记协议构造器的实现。

    • 如果子类重写父类的一个指定构造器,同时还从协议中实现相匹配的构造器,则该构造器需要使用requiredoverride关键字修饰。
    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
        }
    }
    

    6.协议是一种类型

    协议是一种类型:

    • 作为函数、方法或构造器中的参数类型或返回值类型;
    • 作为常量、变量或属性的类型
    • 作为数组、字典或其他容器中的元素类型
      示例:
      定义一个新类Dice,它表示用于棋盘游戏的n面骰子。 Dice实例有一个sides的整数属性,它表示有多少边;还有一个类型为RandomNumberGenerator的属性generator,它提供了一个随机数生成器,可以从中生成骰子掷骰值,可以将其设置为遵守RandomNumberGenerator协议的任何类型的实例。
    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类创建一个带有 LinearCongruentialGenerator实例作为其随机数生成器的六面骰子:

    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
    

    注意:因为协议是一种类型,所以以大写字母开头,以匹配Swift中其他类型的名称(如Int、String和Double)。

    7.委托

    委托是一种设计模式,它允许类或结构体将其部分职责传递(或委托)给另一种类型的实例。这种设计模式是通过定义一个协议来实现的,该协议封装了委托者的职责,这样就保证了一致性类型(称为委托)能够提供已经委托的功能。委托可以用来响应特定的操作,或者从外部源检索数据,而不需要知道该源的底层类型。
    示例:
    DiceGame规定了涉及骰子的游戏所采用的协议,使用DiceGameDelegate 协议可以追踪DiceGame的进度:

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

    定义一个类SnakesAndLadders遵守DiceGame协议,并通知DiceGameDelegate其进度:

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

    下面示例显示了一个名为DiceGameTracker的类,它遵守DiceGameDelegate协议:

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

    以下是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
    

    8.使用扩展添加协议一致性

    • 当在扩展中将一致性添加到实例的类型时,类型的现有实例自动采用并遵守协议。
      示例:
      这个被称为TextRepresentable的协议,可以由任何能够表示为文本的类型实现。这可能是对其自身的描述,或其当前状态的文本版本:
    protocol TextRepresentable {
        var textualDescription: String { get }
    }
    

    上面的Dice类可以扩展为采用并遵守TextRepresentable协议:

    extension Dice: TextRepresentable {
        var textualDescription: String {
            return "A \(sides)-sided dice"
        }
    }
    

    任何Dice类的实例现在可以被视为TextRepresentable:

    let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
    print(d12.textualDescription)
    // Prints "A 12-sided dice"
    

    同样,SnakesAndLadders类也可以扩展为采用和遵守TextRepresentable协议:

    extension SnakesAndLadders: TextRepresentable {
        var textualDescription: String {
            return "A game of Snakes and Ladders with \(finalSquare) squares"
        }
    }
    print(game.textualDescription)
    // Prints "A game of Snakes and Ladders with 25 squares"
    
    • 泛型类型只能在特定条件下才能满足协议的要求,比如类型的泛型参数符合协议。在扩展类型时,可以通过列出约束条件使泛型类型在特定条件下符合协议。通过编写泛型where语句,在采用的协议名称之后添加这些约束。

    示例:
    下面的扩展使数组实例在存储符合TextRepresentable类型的元素时符合TextRepresentable协议。

    extension Array: TextRepresentable where Element: TextRepresentable {
        var textualDescription: String {
            let itemsAsText = self.map { $0.textualDescription }
            return "[" + itemsAsText.joined(separator: ", ") + "]"
        }
    }
    let myDice = [d6, d12]
    print(myDice.textualDescription)
    // Prints "[A 6-sided dice, A 12-sided dice]"
    
    • 如果一个类型已经符合协议的所有要求,但是还未声明采用了该协议,则可以让它采用一个空扩展的协议:
    struct Hamster {
        var name: String
        var textualDescription: String {
            return "A hamster named \(name)"
        }
    }
    extension Hamster: TextRepresentable {}
    

    现在,只要TextRepresentable是所需类型,就可以使用Hamster实例:

    let simonTheHamster = Hamster(name: "Simon")
    let somethingTextRepresentable: TextRepresentable = simonTheHamster
    print(somethingTextRepresentable.textualDescription)
    // Prints "A hamster named Simon"
    

    注意: 类型不会仅通过满足其要求而自动采用协议。 它们必须始终明确地声明它们采用了协议。

    9.协议类型的集合

    • 协议可以用作存储在数组或字典等集合中的类型。
      示例:
      这个例子创建了一个元素类型为TextRepresentable的数组:
    let things: [TextRepresentable] = [game, d12, simonTheHamster]
    for thing in things {
        print(thing.textualDescription)
    }
    // A game of Snakes and Ladders with 25 squares
    // A 12-sided dice
    // A hamster named Simon
    

    10.协议的继承

    • 协议可以继承一个或多个其他协议,并且可以在它继承的需求之上添加更多的需求。协议继承的语法类似于类继承的语法,但是可以列出多个继承的协议,用逗号分隔:
    protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
        // protocol definition goes here
    }
    

    下面是一个继承TextRepresentable协议的例子:

    protocol PrettyTextRepresentable: TextRepresentable {
        var prettyTextualDescription: String { get }
    }
    

    SnakesAndLadders类通过扩展来采用和遵守PrettyTextRepresentable协议:

    extension SnakesAndLadders: PrettyTextRepresentable {
        var prettyTextualDescription: String {
            var output = textualDescription + ":\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
        }
    }
    

    现在可以使用prettyTextualDescription属性打印任何SnakesAndLadders实例的漂亮文本描述:

    print(game.prettyTextualDescription)
    // A game of Snakes and Ladders with 25 squares:
    // ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
    

    11.仅支持类的协议

    • 通过将AnyObject协议添加到协议的继承列表中,来限制该协议只适用于class类型,而不适用于枚举和结构体。
    protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
        // class-only protocol definition goes here
    }
    

    12.协议组合

    • 当需要一个类型同时符合多个协议时,可以使用协议组合,将多个协议组合到单个需求中。
    • 协议组合不是定义新的协议类型;
    • 协议组合还可以包含一个类类型,可以使用它来指定所需的超类;
    • 协议组合使用符连接多个协议;
      示例:
      函数中的参数celebrator要求传入的参数类型必须同时遵守NamedAged协议:
    protocol Named {
       var name: String { get }
    }
    protocol Aged {
       var age: Int { get }
    }
    struct Person: Named, Aged {
       var name: String
       var age: Int
    }
    func wishHappyBirthday(to celebrator: Named & Aged) {
       print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
    }
    let birthdayPerson = Person(name: "Malcolm", age: 21)
    wishHappyBirthday(to: birthdayPerson)
    // Prints "Happy birthday, Malcolm, you're 21!"
    

    下面的示例,它将前面示例中的Named协议与Location类组合起来:

    class Location {
        var latitude: Double
        var longitude: Double
        init(latitude: Double, longitude: Double) {
            self.latitude = latitude
            self.longitude = longitude
        }
    }
    class City: Location, Named {
        var name: String
        init(name: String, latitude: Double, longitude: Double) {
            self.name = name
            super.init(latitude: latitude, longitude: longitude)
        }
    }
    func beginConcert(in location: Location & Named) {
        print("Hello, \(location.name)!")
    }
    
    let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
    beginConcert(in: seattle)
    // Prints "Hello, Seattle!"
    

    13. 检查协议一致性

    • 可以使用isas操作符来检查协议的一致性,并强制转换到特定的协议。
    • 如果一个实例符合协议,则is操作符返回true;否则返回false。
    • as?操作符返回协议类型的可选值,如果实例不符合该协议,则该值为nil。
    • as!操作符将强制转换为协议类型,并在转换失败时触发运行时错误。

    示例:

    protocol HasArea {
        var area: Double { get }
    }
    class Circle: HasArea {
        let pi = 3.1415927
        var radius: Double
        var area: Double { return pi * radius * radius }
        init(radius: Double) { self.radius = radius }
    }
    class Country: HasArea {
        var area: Double
        init(area: Double) { self.area = area }
    }
    
    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 {
        if let objectWithArea = object as? HasArea {
            print("Area is \(objectWithArea.area)")
        } else {
            print("Something that doesn't have an area")
        }
    }
    // Area is 12.5663708
    // Area is 243610.0
    // Something that doesn't have an area
    

    14.可选协议

    • 当协议中某些方法或属性不需要遵守协议的类型实现时,使用关键字optional来指明;
    • 协议和可选需求都必须用@objc属性标记;
    • @objc协议只能由继承自Objective-C类或其他@objc类的类使用,结构体或枚举不能使用。
      示例:
    @objc protocol CounterDataSource {
        @objc optional func increment(forCount count: Int) -> Int
        @objc optional var fixedIncrement: Int { get }
    }
    
    
    class Counter {
        var count = 0
        var dataSource: CounterDataSource?
        func increment() {
            if let amount = dataSource?.increment?(forCount: count) {
                count += amount
            } else if let amount = dataSource?.fixedIncrement {
                count += amount
            }
        }
    }
    
    
    class ThreeSource: NSObject, CounterDataSource {
        let fixedIncrement = 3
    }
    var counter = Counter()
    counter.dataSource = ThreeSource()
    for _ in 1...4 {
        counter.increment()
        print(counter.count)
    }
    // 3
    // 6
    // 9
    // 12
    
    
    
    class TowardsZeroSource: NSObject, CounterDataSource {
        func increment(forCount 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()
        print(counter.count)
    }
    // -3
    // -2
    // -1
    // 0
    // 0
    

    15.协议扩展

    • 可以对协议进行扩展,为遵守协议的类型提供方法、构造器、下标和计算属性的实现。
    • 协议扩展可以为遵守协议的类型添加实现,但它不能使协议从另一个协议扩展或继承。协议继承始终在协议声明本身指定。
      示例:
      RandomNumberGenerator协议通过扩展提供一个randomBool()方法,该方法使用random()的结果返回一个Bool值。
    extension RandomNumberGenerator {
        func randomBool() -> Bool {
            return random() > 0.5
        }
    }
    

    通过在协议中创建扩展,所有遵守该协议的类型将自动获得该方法的实现,而不需要添加额外的修改。

    let generator = LinearCongruentialGenerator()
    print("Here's a random number: \(generator.random())")
    // Prints "Here's a random number: 0.3746499199817101"
    print("And here's a random Boolean: \(generator.randomBool())")
    // Prints "And here's a random Boolean: true"
    
    • 如果协议扩展为该协议的任何方法或计算属性提供了默认实现,同时遵守协议的类型也提供了所需方法或属性的自身实现,则将使用类型本身的实现而不是协议扩展提供的实现。

    示例:

    extension PrettyTextRepresentable  {
        var prettyTextualDescription: String {
            return textualDescription
        }
    }
    
    • 在定义协议扩展时,可以指定符合类型必须满足的约束条件,然后才能使用扩展的方法和属性。通过编写泛型where语句,在要扩展的协议名称添加这些约束。
      示例:
    extension Collection where Element: Equatable {
        func allEqual() -> Bool {
            for element in self {
                if element != self.first {
                    return false
                }
            }
            return true
        }
    }
    
    let equalNumbers = [100, 100, 100, 100, 100]
    let differentNumbers = [100, 100, 200, 100, 200]
    
    print(equalNumbers.allEqual())
    // Prints "true"
    print(differentNumbers.allEqual())
    // Prints "false"
    

    16.其他专题模块

    Swift 4.2 基础专题详解

    相关文章

      网友评论

        本文标题:Swift 协议(protocol)详解

        本文链接:https://www.haomeiwen.com/subject/cunubftx.html