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
可以提供默认的实现,也可以为协议添加限制条件
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容