Swift协议

「协议」(protocol)声明一系列方法、属性、下标等用来约束其「遵循者」,进而保证「遵循者」能够完成限定的工作。「协议」本身不实现任何功能,它仅仅描述了「遵循者」的实现。「协议」能被类、结构体、枚举所遵循,若某个类型遵循某「协议」,则称该类型遵循(conform to)某协议。

协议的语法

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

protocol SomeProtocol {
    // 协议内容
}

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

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

呵,继承也是使用:哦!当某个类含有父类的同时并实现了协议,应该把父类放在所有协议之前,如下:

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

属性要求

「协议」能够要求其遵循者必须含有一些特定名称和类型的「实例属性」和「类型属性」,也能够要求属性的读写权限(即gettable和settable),但它不要求属性是「存储型属性」还是「计算型属性」。

如果「协议」要求某个属性可写,但「其遵循者」将该属性定义为常量,那么认为该类型不满足「协议」的property requirements;另外一种情况,若「协议」要求某个属性可读(仅仅gettable),但某个「遵循者」定义该属性时允许设置(settable),那么认为该类型满足「协议」的property requirements。

一般而言,「协议」中声明的属性都是变量(即都用var修饰)。Gettable和settable属性用{ get set}修饰;Gettable属性由{ get }修饰,如下:

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

也可以在「协议」中声明类型属性,在「协议」中声明「类型属性」和在类/结构体/枚举中定义「类型属性」不太一样。在结构体和枚举中,定义「类型属性」时使用关键字static修饰;在类中定义「类型属性」时可以使用static,也可以使用class,关于二者不同这里不赘述了,详见这里但是对于protocol,Swift规定无论什么情况下都使用static修饰静态属性。但是在「协议」的「遵循者」类中定义时,可以根据需要使用staticclass

方法要求

「协议」能够要求其遵循者必须实现某些指定名称和类型的实例方法和类方法。协议方法的声明与普通方法声明相似,但它不需要方法body。

值得注意的是,协议方法支持变长参数(variadic parameter),但不支持默认参数(default parameter)。

和「属性要求」类似,「协议」要求无论在什么时候,声明「类型方法」时都是用关键字static修饰。

除此之外,「协议」中还可以声明mutable类型方法。

构造器要求

「协议」还可以要求遵循者必须定义某些指定的构造器。在「协议」中声明构造器和在类中声明构造器差不多,只是没有initializer body,如下:

protocol SomeProtocol {
    init(someParameter: Int)
}

当类实现协议的构造器时

假设有某个协议SomeProtocol,类SomeClass遵循该协议,那么意味着SomeClass的子类也遵循该协议。为什么呢?因为若SomeProtocol中声明了一些属性和方法以及下标,则SomeClass必然会定义这些属性和方法以及下标,通过继承,SomeClass的子类也必然包括这些属性和方法以及下标,所以我们说:SomeClass的子类也遵循SomeProtocol。

然而,上面的这种说法不严密,实际情况更复杂一些,因为协议SomeProtocol中不光可以声明方法/属性/下标,还可以声明构造器但在Swift中,除了某些特殊情况外,构造器是不被子类继承的,所以SomeClass中虽然能够保证定义了protocol要求的构造器,但不能保证SomeClass的子类中也定义了protocol要求的构造器。

P.S:哪些特殊情况?看这里

所以这里有一个八阿哥(bug),SomeClass定义SomeProtocol中声明的构造器没问题,如何保证SomeClass的子类也定义SomeProtocol中声明的构造器呢?

关键字required可以解决这个问题。

Swift规定,SomeClass在定义SomeProtocol声明的构造器时,必须加上一个required修饰符作为前缀,如下:

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

required的作用是确保SomeClass的子类必须也得实现这个构造器,这里有更详细的描述。

当然,如果SomeProtocol的遵循者是一个final class(不能被继承的类),那么上面这种情况就不需要required来修饰initializer了。

还有一种复杂的情况,如果某个子类需要override父类中的designated initializer,同时该子类还遵循了某个协议,该协议也定义了一个与该designated initializer同名的构造器,那么子类在定义该initializer时需要同时使用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
    }
}

然后,协议还可以声明failable initializer。

协议类型

协议本身不具备任何功能,但在实际使用中,你完全可以把它当做一个类型来使用。使用场景包括:

  • 作为函数/方法/构造器中的参数类型或返回类型;
  • 作为常量/变量/属性的类型;
  • 作为array/set/dictionary等容器中元素的类型;

举个栗子,定义一个类Dice(表桌游中的骰子),如下:

protocol RandomNumberGenerator {
    func random() -> Double
}
    
class Dice {
    let sides: Int        // 表示「骰子」有几个面
    let generator: RandomNumberGenerator    // 随机数生成器
    
    // 构造器,RandomNumberGenerator是一个Protocol名
    init(sides: Int, generator: RandomNumberGenerator) {
        self.sides = sides
        self.generator = generator
    }
    
    // 摇动「骰子」
    func roll() -> Int {
        return Int(generator.random() * Double(sides)) + 1
    }
}

Dice含有两个属性sides和generator,前者用来表示骰子有几个面,后者为骰子提供一个随机数生成器。由于后者为RandomNumberGenerator的协议类型,所以它能够被赋值为任意遵循该协议的类型。

roll方法用来模拟骰子的面值,它先用generator的random方法来创建一个[0-1]区间内的随机数种子,然后加工这个随机数种子生成骰子的面值。

如下所示,LinearCongruentialGenerator的实例作为随机数生成器传入Dice的构造器:

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

代理设计模式

「委托」(delegation)是一种「设计模式」,它允许类或结构体将一些需要它们负责的功能交由(委托)给其他的类型的实例。在OC中,委托随处可见。在Swift实际开发中,委托设计模式的实现和协议密不可分。

基于protocol的「委托设计模式」太常见了,这里就赘述了,若以后碰到新知识点再补充吧!

协议和扩展

扩展中让已知类型遵循新协议

Swift扩展中已经提到,可以在「扩展」中让某个已存在的类型遵循某个新的协议。这样,哪怕我们没有权限访问该类型的源代码,也可以在「扩展」中为该类型添加新属性、方法、下标。

举个栗子,在上文骰子类Dice的基础上,定义一个新的协议TextRepresentable,并定义一个Dice扩展,使得Dice遵循该协议,如下:

protocol TextRepresentable {
    func asText() -> String
}
    
extension Dice: TextRepresentable {
    func asText() -> String {
        return "A \(self.sides)-sided dice"
    }
}

在扩展中添加协议成员

有的时候,某个类型从各个方面都符合某个协议的要求,只是没有明确说明它遵循该协议,可以在extension中指明,下面的结构体Hamster(仓鼠)完全遵循上面定义的TextRepresentable协议(该协议只声明了一个方法asText() -> String),但没有显式指明遵循该协议,通过extension可以处理一下:

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

注意:虽然可以在extension中让某个已知类型遵循某个协议,但是对协议是比较挑剔的;必须保证该协议中没有定义「存储型属性」。

Protocol的继承

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

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

Class-Only Protocol

在Swift中还可以限制某个protocol仅能被类类型遵循,在定义「协议」的时候加上关键字class即可,如下:

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

协议合成

一个协议可由多个协议采用protocol<SomeProtocol, AnotherProtocol>这样的格式进行组合,称为「协议合成」(protocol composition)。

「协议合成」经常在「协议」作为类型(譬如参数类型)时会使用到,举个栗子:

protocol Named {
    var name: String { get }
}
    
protocol Aged {
    var age: String { 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: "张不坏", age: 23)
wishHappyBirthday(birthdayPerson)
// print "Happy birthday 张不坏 - you're 23!"

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

检验协议的一致性

使用关键字is检验协议一致性,使用关键字as将协议类型向下转型(downcast)为其他协议类型:

  • is操作符用来判断某个实例是否遵循某个协议;
  • as?返回一个optional,当被检查的实例遵循某个协议时,返回该协议类型,否则返回nil;
  • as!强制转换,若成功,则返回协议类型,否则,抛出runtime错误;

协议的可选要求

OC中协议定义的属性和变量有required和optional(这和可选类型optional不是一个意思哦)之分,required类型资源(属性或者方法),「遵循者」必须要实现;optional类型资源,「遵循者」可选择性实现。

Swift也不例外,在Swift的「协议」中,它所提出的要求也可以是optional,具体的做法是在定义属性/方法/下标/构造器时加上关键字optional修饰即可。

Swift文档把这种在「协议」中声明的可选要求(属性、方法、下标等)称之为「optional protocol requirements」。

根据我的理解,Swift的设计理念是没有「optional protocol requirements」的,但是为了保持与OC兼容性,不得已支持;所以在Swift的「协议」中定义「optional protocol requirements」的前提是该协议被@objc修饰,关于@objc

  • @objc指示该协议暴露给OC,即可以为OC代码所用;
  • @objc修饰的协议仅仅可以被类类型遵循;

举个栗子,定义一个类Counter,它为「外部数据源」提供加法操作,这个「外部数据源」必须要遵循一个协议CounterDataSource,如下:

@objc protocol CounterDataSource {
    optional func incrementForCount(count: Int) -> Int
    optional var fixedIncrement: Int { get }
}
    
@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
        }
    }
}
定义类ThreeSource,该类遵循协议CounterDataSource,并实现了其中一个「optional protocol requirement」 — 属性fixedIncrement
@objc class ThreeSource: CounterDataSource {
    let fixedIncrement = 3
}
    
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
    counter.increment()
    println("\(counter.count) ")
}
// print:
// 3 6 9 12

作为对比,如下定义类TowardsZeroSource,该类也遵循协议CounterDataSource,只是实现了另一个「optional protocol requirement」 — 方法incrementForCount

class TowardsZeroSource: CounterDataSource {
    func incrementForCount(count: Int) -> Int {
        if count == 0 {
            return 0
        } else if count < 0 {
            return 1
        } else {
            return -1
        }
    }
}
    
var counter = Counter()
counter.count = -4
counter.dataSource = TowardsZeroSource()
for _ in 1...5 {
    counter.increment()
    print("\(counter.count) ")
}
// print:
// -3 -2 -1 0 0

 

posted @ 2015-06-29 18:00  脸大皮厚歌  阅读(659)  评论(0编辑  收藏  举报