Swift(二十四)协议

杭城下雨,路边随手拍的。

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

推荐阅读更多精彩内容

  • 132.转换错误成可选值 通过转换错误成一个可选值,你可以使用 try? 来处理错误。当执行try?表达式时,如果...
    无沣阅读 1,244评论 0 3
  • 本章将会介绍 协议语法属性要求方法要求(Method Requirements)Mutating 方法要求构造器要...
    寒桥阅读 415评论 0 3
  • 协议(Protocols) 协议 定义了一个蓝图,规定了用来实现某一特定工作或者功能所必需的方法和属性。类,结构体...
    请叫我小东子阅读 215评论 1 2
  • 定义: 协议定义了一个蓝图,规定了用来实现某一特定的任务或者功能的方法、属性,或其他需要的东西。类、结构体、枚举都...
    geekLiu阅读 1,433评论 0 1
  • 每当理智为欲望所占据, 每当被欲望所驱使… 人刹那间变作恶鬼, 疯狂地发泄闯不出地狱! 欲望它是怎么一回事? 观察...
    张星耀阅读 353评论 0 0