Swift学习之协议

协议

协议定义了用来实现某一特定任务或者功能的属性、方法以及需要的东西。
类、结构体、枚举都可以采用协议,但是都必须实现协议所必须的要求。除了采纳协议规定的要求,还可以给协议扩展一下方法、属性,这样采纳该协议的类型就能够使用这些功能。

1.语法

protocol SomeProtocol1{
    //定义一下使用该协议要实现的内容
}
要让自定义的类型采用协议,需要在需要的类型名后加上协议名,并用 “:” 隔开,如果采纳多个协议,协议之间用逗号隔开。如果类型继承某个类,需要将继承的类写在协议的最前面。
class someClass:SomeSuperClass,SomeProtocol{

}

2.属性要求

协议可以要求采纳该协议的类型提供特定名称和类型的实例属性或者类型属性。但协议不指定协议的属性是存储还是计算型属性。它指定名字和类型。此外,还可以指定属性是可读还是可写的。
通常用var来指定属性,在类型声明后加{set get}来表明属性是可读还是可写的。
protocol SomeProtocol2{
    var musetBeSettable:Int {set get} //如果有set,必须有get
    var doesnotBeSettable:Int {get}
}
在协议中定义类属性的时候,使用static 关键字修饰,当类类型采纳协议时,还可以用class 来声明类型属性
protocol FullName{
    var fullName:String{get}
}

struct Person:FullName {
    var fullName:String{ //提供FullName的属性的get实现
        return "iyaqi"
    }
}
print(Person().fullName) // iyaqi

3.方法要求

协议可以要求采纳该协议的类型必须实现指定的实例方法或者类方法,但是这些方法不需要提供具体的实现。类似属性,可以类方法需用static关键字修饰,类类型还可以使用class修饰。
protocol RandomNumberGenerator{
    func random()->Double
    //static func someMethod()
}
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()
generator.random() //0.3746499199817101
generator.random() //0.729023776863283
如果需要修改实例或者实例的属性的值,需要在方法的func前面加 mutating。实现协议的 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 switcher = OnOffSwitch.Off
print("switcher's status:\(switcher)") //switcher's statu:Off\n
switcher.toggle()
print("switcher's status:\(switcher)") //switcher's statu:On\n

4.构造器要求

协议可以要求采纳协议的类型实现指定的构造器。类似与普通的构造器一样,但是不需要具体实现。
protocol aProtocol{
    init (someParameters:Int)
}
构造器在类中的实现
在类中的实现,无需指定是指定构造器还是便利构造器,并且需要加上 required 修饰符
class someClass:aProtocol{
    init(a:Int){}
    required init(someParameters: Int) {
        //这里是实现部分。
    }
}

class superClass {
    init(someParameters:Int){}
}

class subClass: superClass,aProtocol {
    //父类和协议都定义了这个构造器,所以要加上 override & required
    override required init( someParameters: Int) {
        
        super.init(someParameters: 1)
    }
}

5.协议作为类型

//协议可以 像普通类型一样,作为参数、返回值,或者是属性,数组、字典中等容器的元素。
class Dice{
    let sides:Int
    let generator :LinearCongruentialGenerator //协议作为属性
    init(sides:Int,generator:LinearCongruentialGenerator){ //协议作为参数
        self.sides = sides
        self.generator = generator
    }
    func roll()->Int{
        return Int(generator.random() * Double(sides)) + 1
    }
}


var dice = Dice(sides: 3, generator: LinearCongruentialGenerator())
for _ in 1...5{
    print(dice.roll()) //2,3,2,3,2
}

6.代理(委托)模式

委托是一种设计模式,它允许类和结构体将一些功能委托给其他类型的实例来操作。委托的实现很简单:定义协议封装那些要实现的功能,这样采纳该协议的类型就能够提供这些功能。
protocol DiceGame{
    var dice:Dice {get}
    func play()
}
protocol DiceGameDelegate{
    func diceDidStart(game:DiceGame)
    func game(game:DiceGame,didStartNewTurnWithDiceRoll dicRoll:Int)
    func gameEnd(game:DiceGame)
}

class SnakeAndLadders: DiceGame {
    let finalSquare = 25
    var dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
    var square = 0
    var board = [Int]()
    init(){
        board = [Int](count: finalSquare, repeatedValue: 0)
        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?.diceDidStart(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?.gameEnd(self)
    }
}

7.通过扩展添加协议一致性

即便无法修改源代码,依然可以用扩展令已有类型采纳并符合协议。协议可以为类型添加属性、方法、下标等来让类型符合协议的要求
protocol TextRepresentable{
    var textualDescription:String{get}
}
extension Dice:TextRepresentable{
    var textualDescription:String{
        return "a side of Dice"
    }
}
通过扩展来采纳并实现协议,跟在原始定义中实现是同样的效果。

8.协议的集合

let things:[TextRepresentable] = [dice]
for thing in things{
    print(thing.textualDescription)
}

9.协议的继承

协议能够继承一个或者多个协议,多个协议之间用逗号隔开,语法跟类的继承相同。如果继承的协议还继承了其他协议,那么采纳该协议的类型也必须实现其他协议的要求。比如,协议B 继承 协议A,类型C采纳了B协议,那么C必须也实现协议A规定的东西。

10.类类型专属协议

可以在协议的继承列表中,添加class 关键字表示该协议只能被类类型采纳。其他类型不能采纳。
protocol someProtocol4:class,TextRepresentable{
    //这里是类类型的定义部分
}

11.协议合成

如果采用多个协议,可以将多个协议放在protocol<>里面。表示采纳的协议列表,称协议合成,协议之间用逗号隔开
protocol Named{
    var name:String{get}
}
protocol Aged{
    var age:Int {get set}
}
struct People :Named,Aged{
    var name:String
    var age:Int
}   
协议合成,这个方法的参数不关心类型,之关心这个参数是否符合Named,Aged协议。
func wishHappyBirthday(celebrator:protocol<Named,Aged>){
    print("Happy birthday \(celebrator.name) for \(celebrator.age)")
}

let birthdayPerson = People(name: "iyaqi", age: 25)
print(wishHappyBirthday(birthdayPerson))

12.检查协议一致性

使用is as来检查协议一致性。即是否符合某协议,并且可以转换到指定的协议类型
is 用来检查实例是否符合某个协议,若符合则返回 true,否则返回 false。
as? 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 nil。
as! 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。

protocol HasArea{
    var area:Double{get}
}

class Circle: HasArea {
    let pi:Double = 3.1415
    var radius:Double
    init(radius:Double){
        self.radius = radius;
    }
    var area:Double{
        return radius * radius * pi
    }
}
class Country: HasArea {
    var area:Double
    init(area:Double){self.area = area}
}

class Animal {
    var legs:Int
    init(legs:Int){self.legs = legs}
}

var objects:[AnyObject] = [Circle(radius: 3),Country(area: 2000),Animal(legs: 2)]
for object in objects{
    if let hasAreaType = object as? HasArea{
        //在这里object会被转化为 HasArea类型,虽然他们仍然是circle、country、animal类型,当object 赋给 hasAreaType时,只能访问area属性
        print(hasAreaType.area) // 28.2735 ,200
    }
}

13.可选的协议要求

可以在协议中定义可以选择实现的要求,采纳该协议的类型可以选择是否采用这些要求。用optional关键字修饰。注意,可选的协议要求只能用在标记@objc的协议中,标记@objc的协议只能被OC的类或者是@objc类采纳,其他类型均不能采纳
@objc protocol CounterDataSource {
    optional func incrementForCount(count:Int)->Int
    optional var fixedIncrement:Int{get}
}

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
        }
    }
}
var counter = Counter()

class threeSource: NSObject,CounterDataSource {
    let fixedIncrement = 3
}

counter.datasource = threeSource()
for _ in 1..<4{
    counter.increment() //
    print(counter.count) // 3 6 9
}

class  TowardsZeroSource:NSObject,CounterDataSource{
    func incrementForCount(count: Int) -> Int {
        if count == 0  {
            return 0
        }else if count < 0{
            return 1
        }else{
            return -1
        }
    }
}

print(counter.count) //9
counter.datasource = TowardsZeroSource()
counter.increment()
print(counter.count) //8

14.协议扩展

协议可以通过扩展来添加方法、属性、下标等,基于此,可以通过给协议添加扩展,来实现这些功能,而无需在每个采纳该协议的类型中添加这些功能。通过协议扩展,所有采纳该协议的类型都会自动获取到该扩展增加的方法实现,无需任何额外修改。
extension RandomNumberGenerator{
    func randomBool()->Bool{
        return random() > 0.5
    }
}

let generator2 = LinearCongruentialGenerator()
print(generator.random()) // 0.636466906721536
print(generator.randomBool()) // 0.636466906721536 > 0.5 返回 true
可以提供默认的实现,也可以为协议添加限制条件

推荐阅读更多精彩内容