美文网首页
Swift(二十四)协议

Swift(二十四)协议

作者: 冰三尺 | 来源:发表于2016-11-20 13:42 被阅读25次
    杭城下雨,路边随手拍的。

    Protocol(协议)用于统一方法和属性的名称,而不实现任何功能。协议能够被类,枚举,结构体实现,满足协议要求的类,枚举,结构体被称为协议的遵循者。

    1.协议语法
    2.属性要求
    3.方法要求
    4.突变方法要求
    5.初始化要求
    6.协议类型
    7.委托
    8.给协议添加扩展
    9.协议类型的集合
    10.协议的继承
    11.只有类才能使用的协议
    12.协议组合
    13.检查协议一致性
    14.可选协议要求
    15协议扩展
    

    1.协议的语法

    协议的定义与类,结构体,枚举的定义非常相似,如下所示:

    protocol SomeProtocol {
        // protocol definition goes here
    }
    

    在类,结构体,枚举的名称后加上协议名称,中间以冒号:分隔即可实现协议;实现多个协议时,各协议之间用逗号,分隔,如下所示:

    struct SomeStructure: FirstProtocol, AnotherProtocol { 
        // 结构体内容 
    } 
    

    2.属性要求

    协议能够要求其遵循者必须含有一些特定名称和类型的实例属性(instance property)或类属性 (type property),也能够要求属性的(设置权限)settable 和(访问权限)gettable,但它不要求属性是存储型属性(stored property)还是计算型属性(calculate property)。
    当某个类含有父类的同时并实现了协议,应当把父类放在所有的协议之前,如下所示:

    class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol { 
        // 类的内容 
    } 
    

    通常前置var关键字将属性声明为变量。在属性声明后写上{ get set }表示属性为可读写的。{ get }用来表示属性为可读的。即使你为可读的属性实现了setter方法,它也不会出错。

    protocol SomeProtocol { 
        var musBeSettable : Int { get set } 
        var doesNotNeedToBeSettable: Int { get } 
    }
    
    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")
    

    这个例子定义了一个名为结构Person,它代表一个特定的人。Person结构体含有一个名为fullName的存储型属性,完整的遵循了协议。(若协议未被完整遵循,编译时则会报错)。
    下面是一个更复杂的类,它也采用了和符合FullyNamed协议:

    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")
    print(ncc1701.fullName)
    // ncc1701.fullName is "USS Enterprise"
    

    Starship类将fullName实现为可读的计算型属性。它的每一个实例都有一个名为name的必备属性和一个名为prefix的可选属性。 当prefix存在时,将prefix插入到name之前来为Starship构建fullName。

    3.方法要求

    协议能够要求其遵循者必备某些特定的实例方法和类方法。协议方法的声明与普通方法声明相似,但它不需要方法内容。

    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.37464991998171"
    print("And another one: \(generator.random())")
    // Prints "And another one: 0.729023776863283"
    

    4.突变方法要求

    能在方法或函数内部改变实例类型的方法称为突变方法。在值类型(Value Type)(译者注:特指结构体和枚举)中的的函数前缀加上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()
    

    5.初始化要求

    协议可以要求通过符合类型实现特定的初始化。你写这些初始化在完全相同的方式为正常初始化协议定义的一部分,但没有花括号或初始化体:

    protocol SomeProtocol {
            init(someParameter: Int)
    }
    

    类实现协议的初始化要求
    如果该类接受了协议,你可以实现协议的初始化方法,但是此时需要使用关键字required来标记

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

    注意: 你并不需要标记协议初始化实现与required上标有类修改final修改,因为最终的类不能被继承。

    如果一个子类实现了父类的初始化方法,但同事也实现了协议的初始化方法,则使用关键字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
        }
    }
    

    6.协议类型

    协议本身实际上没有实现任何功能。但是,您创建的任何协议将成为一个不折不扣的类型,在代码中使用。
    因为它是一个类型,可以使用在许多地方使用。
    使用场景:

    1. 作为函数,方法或构造器中的参数类型,返回值类型
    2. 作为常量,变量,属性的类型
    3. 作为数组,字典或其他容器中的元素类型
      这里是作为一个类型的协议的一个例子:
    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.委托

    委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能交由(委托)给其他的类型。

    委托模式的实现很简单: 定义协议来封装那些需要被委托的函数和方法, 使其遵循者拥有这些被委托的函数和方法。

    具体使用官方例子比较长,请自行查阅

    8.给协议添加扩展

    protocol TextRepresentable {
        var textualDescription: String { get }
    }
    

    本着软件设计的开闭原则,对扩展开放,对修改关闭,我们可以对协议进行扩展,而添加新的方法

    extension TextRepresentable {  
          //扩展的新内容
    }
    

    也可以扩展一个类,结构体使其遵循新的协议

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

    通过延展补充协议声明
    当一个类型已经实现了协议中的所有要求,却没有声明时,可以通过扩展来补充协议声明:

    struct Hamster { 
        var name: String 
        func asText() -> String { 
            return "A hamster named \(name)" 
        } 
    } 
    extension Hamster: TextRepresentabl {} 
    

    注意:即时满足了协议的所有要求,类型也不会自动转变,因此你必须为它做出明显的协议声明

    9.协议类型的集合

    协议类型可以被集合使用,表示集合中的元素均为协议类型:

    protocol oneProtocol {
        func test()
    }
    class oneClass: oneProtocol {
        func test() {
            print("oneClass")
        }
    }
    let one = oneClass();
    
    class twoClass: oneProtocol {
        func test() {
            print("twoClass")
        }
    }
    let two = twoClass();
    //该集合里面的元素全部是遵从oneProtocol协议的对象
    let things: [oneProtocol] = [one, two]
    for thing in things {
        print(thing.test())
    }
    

    10.协议的继承

    协议能够继承一到多个其他协议。语法与类的继承相似,多个协议间用逗号,分隔

    protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
          // protocol definition goes here
    }
    

    继承该协议,同时,也要实现该协议的方法

    protocol inheritProtocol {
        func inherit()
    }
    //oneProtocol继承协议inheritProtocol, 接受oneProtocol协议的类同时也要接受inheritProtocol
    protocol oneProtocol:inheritProtocol {
        func test()
    }
    class oneClass: oneProtocol {
    //inheritProtocol
        func test() {
            print("oneClass")
        }
    //inheritProtocol
        func inherit() {
            
        }
    }
    

    11.只有类才能使用的协议

    就是指这个协议已经被特殊限制,只能有类来实现,结构体或者枚举都不能使用

    protocol oneProtocol {
        
    }
    //使用关键字class来声明,如果该协议需要继承其他协议,需要写在其他协议最前面
    protocol SomeClassOnlyProtocol: class, oneProtocol {
        // class-only protocol definition goes here
    }
    class classA: SomeClassOnlyProtocol {
        
    }
    
    struct structA: SomeClassOnlyProtocol {
            //    报错error: non-class type 'structA' cannot conform to class protocol 'SomeClassOnlyProtocol'
    }
    

    12.协议组合

    当一个类,结构体需要同时符合多个协议时可以使用协议组合

    protocol Named {
        var name: String { get }
    }
    protocol Aged {
        var age: Int { get }
    }
    struct Person: Named, Aged {
        var name: String
        var age: Int
    }
    //参数必须同时符合Named和Aged协议,中间用&链接
    func wishHappyBirthday(celebrator: Named & Aged) {
        print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
    }
    let birthdayPerson = Person(name: "Malcolm", age: 21)
    //Person已经遵循了这两个协议
    wishHappyBirthday(celebrator: birthdayPerson)
    // Prints "Happy birthday, Malcolm, you're 21!"
    

    注意:协议合成并不会生成一个新协议类型,而是将多个协议合成为一个临时的协议,超出范围后立即失效。

    13.检验协议的一致性

    使用is检验协议一致性,使用as将协议类型向下转换(downcast)为的其他协议类型。检验与转换的语法和之前相同(详情查看类型检查):

    1. is操作符用来检查实例是否遵循了某个协议。
    2. as?返回一个可选值,当实例遵循协议时,返回该协议类型;否则返回nil
      3.as! 强制解析,如果可选值为nil,会产生运行时错误

    14.可选协议要求

    15协议扩展


    //协议作为类型使用
    protocol RandomGenerable {
        func randomNumber() -> Int
    }
    
    struct RandomNumber : RandomGenerable {
        func randomNumber() -> Int {
            return 100
        }
    }
    
    class TenRandomNumber : RandomGenerable {
        func randomNumber() -> Int {
            return 6
        }
    }
    
    struct Dice {
        var side : Int
        var randomPro : RandomGenerable
        
        func play() -> Int {
            return self.randomPro.randomNumber()
        }
    }
    
    let aDice = Dice(side: 6, randomPro: TenRandomNumber())
    aDice.play()
    

    相关文章

      网友评论

          本文标题:Swift(二十四)协议

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