协议(protocol)

规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的
东西。类、结构体 或枚举都可以遵循协议,并为协议定义的这些要求
提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型
遵循这个协议。除了遵循协议的类型必须实现的要求外,还可以对协议
进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样遵
循协议的类型就能够使用这些功能;

协议语法

protocol SomeProtocol {
    // 这里是协议的定义部分
} 

遵守协议的格式

class SomeClass: SomeSuperClass, FirstProtocol,AnotherProtocol {
    // 类的内容
    // 实现协议中的方法
}

注意点:
1:要让自定义类型遵循某个协议,在定义类型时,需要在类型名称后加
上协议名称,中间以冒号(:)分隔。遵循 多个协议时,各协议之间用逗号
(,)分隔;

2:拥有父类的类在遵循协议时,应该将父类名放在协议名之前,以逗号分隔;

属性要求

协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属
性。协议不指定属性是存储型属性还是计算型属性,它只指定属性的名
称和类型。此外,协议还指定属性是可读的还是可读可写的;

例子

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

协议中定义类型属性

协议中定义类型属性时,总是使用static关键字作为前缀。当类类型遵
循协议时,除了static关键字,还可以使用class关键字来声明类型属性;

例子

protocol FullyNamed {
  var fullName: String { get }
}

struct Person: FullyNamed {
  var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName 为 "John Appleseed"

方法要求

1:协议可以要求遵循协议的类型实现某些指定的实例方法或类方法,协议
中的方法不支持为协议中的方法的参数提供默认值;
2:在协议中定义类方法的时候,总是使用static关键字作为前缀。当类类
型遵循协议时,除了static关键字,还可以使用class关键字作为前缀;

例子

protocol SomeProtocol {
   static func someTypeMethod()
   func random() -> Double
}

Mutating方法要求

在需要在方法中改变方法所属的实例,在值类型(即结构体和枚举)的实
例方法中,将mutating关键写在func关键字之前,表示可以在该方法中
修改它所属的实例以及实例的任意属性的值;

注意点:
实现协议中的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
  }
}

构造器要求

协议可以要求遵循协议的类型实现指定的构造器;

例子1

protocol SomeProtocol {
    init(someParameter: Int)
}

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
    // 这里是构造器的实现部分 
    }
}
 在遵循协议的类中实现构造器,无论是作为指定构造器,还是便利构
造器。你都必须为构造器实现标上required修饰符;

例子2

protocol SomeProtocol {
   init()
}

class SomeSuperClass {
   init() {
   // 这里是构造器的实现部分
   }
}

class SomeSubClass: SomeSuperClass, SomeProtocol { // 因为遵循协议,需要加上 required
   // 因为继承自父类,需要加上 override
   required override init() {
   // 这里是构造器的实现部分
   }
}
一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的
要求,那么该构造器的实现需要同时标注required和override修饰符;

可失败构造器要求

遵循协议的类型可以通过可失败构造器(init?)或非可失败构造器(init)来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(init)或隐式解包可失败构造器(init!)来满足

协议作为类型

尽管协议本身并未实现任何功能,但是协议可以被当做一个成熟的类型来使用;

应用场景

1:作为函数、方法或构造器中的参数类型或返回值类型;
2:作为常量、变量或属性的类型;
3:作为数组、字典或其他容器中的元素类型;

注意点

协议是一种类型,因此协议类型的名称应与其他类型(例如
Int,Double,String)的写法相同,使用大写字母开头的驼峰式写法;

通过扩展添加协议一致性

1:即便无法修改源代码,依然可以通过扩展令已有类型遵循并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器;
2:通过扩展遵循并符合协议,和在原始定义中遵循并符合协议的效果完全相同;

注意点:
通过扩展令已有类型遵循并符合协议时,该类型的所有实例也会随之获
得协议中定义的各项功能;

类类型专属协议

协议的继承列表中,通过添加class关键字来限制协议只能被类类型遵
循,而结构体或枚举不能遵循该协议。class关键字必须第一个出现在协
议的继承列表中,在其他继承的协议之前;
 
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
    // 这里是类类型专属协议的定义部分 
}

协议合成

有时候需要同时遵循多个协议,你可以将多个协议采用
SomeProtocol&AnotherProtocol这样的格式进行组合,称为 协议合成
(protocol composition)。你可以罗列任意多个你想要遵循的协议,以与
符号(&)分隔;

例子

protocol Named {
  var name: String { get }
}
protocol Aged {
  var age: Int { get }
}
struct Person: Named, Aged {
  var name: String
  var age: Int
}

class ViewController: UIViewController {

override func viewDidLoad() {
  super.viewDidLoad()
  let p = Person(name: "枫叶", age: 18)
  wishHappyBirthday(to: p)

  view.backgroundColor = UIColor.orange

}

func wishHappyBirthday(to celebrator: Named & Aged) {
     print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}

wishHappyBirthday(to:)函数的参数celebrator的类型,为Named&Aged,
这意味着它不关心参数的具体类,只要参数符合这两个协议即可;

注意点:
协议合成并不会生成新的、永久的协议类型,而是将多个协议中的要求
合成到一个只在局部作用域有效的临时协议中;
}

检查协议一致性

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

例子

protocol HasArea {
    var area: Double { get }
 }
 
 
class Circle: HasArea {
    let pi = 3.1415927
    var radius: Double
    var area: Double {
        return pi * radius * radius
    }
 
    init(radius: Double) {
        self.radius = radius
    }
 }
 
 class Country: HasArea {
    var area: Double
    init(area: Double) {
        self.area = area
    }
 }
 
 
 class Animal {
    var legs: Int
    init(legs: Int) {
        self.legs = legs
    }
 }
 
 
 override func viewDidLoad() {
    let objects: [AnyObject] = [Circle(radius: 2.0),Country(area: 243_610),Animal(legs: 4)]
 }
 
1:objects数组可以被迭代,并对迭代出的每一个元素进行检查,看它是
否符合HasArea协议,当迭代出的元素符合HasArea协议时,将as?操作符
返回的可选值通过可选绑定,绑定到objectWithArea常量上,objectWithArea是HasArea协议类型的实例;

2:objects数组中的元素的类型并不会因为强转而丢失类型信息,它们仍
然是 Circle,Country,Animal类型。然而,当它们被赋值给
objectWithArea常量时,只被视为HasArea类型,因此只有area属性能够
被访问;
 

可选的协议要求

协议可以定义可选实现,协议中使用optional关键字作为前缀来定义需要
选择实现的方法,因为这个是OC特性,所以协议和可选实现的方法都必须
带上@objc,标记有@objc特性的协议只能被继承自Objective-C类的类或
者@objc类遵循,其他类以及结构体和枚举均不能遵循这种协议;

swift中的协议默认是必须实现的;

协议扩展

协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实
现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每
个遵循协议的类型中都重复同样的实现,也无需使用全局函数;

提供默认实现

可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实
现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定
义实现将会替代扩展中的默认实现被使用;

注意点

通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然
在这两种情况下,遵循协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用;

为协议扩展添加限制条件

在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足
这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写
在协议名之后,使用where子句来描述;

例子

extension CollectionType where Generator.Element: TextRepresentable {
   var textualDescription: String {
   let itemsAsText = self.map { $0.textualDescription }
       return "[" + itemsAsText.joinWithSeparator(", ") + "]"
   }
}
扩展CollectionType协议,但是只适用于集合中的元素遵循了
TextRepresentable协议的情况;

注意点:
如果多个协议扩展都为同一个协议要求提供了默认实现,而遵循协议的
类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多
的那个协议扩展提供的默认实现;

注意点

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

推荐阅读更多精彩内容