Swift学习笔记十七之协议

1、协议:定义一个规则去实现特定的功能,类、结构体、枚举都可以遵守这样的协议,并为这个协议的规则提供具体实现

protocol SomeProtocol1 {//协议语法
//协议内容
}

struct SomeStructure: SomeProtocol1 {//遵守协议,冒号(:)后面加协议名称,多个协议之间用逗号隔开
//结构体内容
}

class SomeClass: NSObject,SomeProtocol1 {//有父类的类遵守协议,要将父类名放在协议名之前,用逗号隔开
//类的实现
}

2、在协议中定义属性:协议中的属性可以是实例属性也可以是类型属性,协议中的属性只能指定名称和类型以及可读可写

protocol SomeProtocol2 {
var mustBeSettable: Int{ get set}//类型后面加{ get set }表示该属性可读可写
var onlyRead: Int{ get } //类型后面加{ get }表示该属性可读
static var someTypeProperty: Int { get set }//类型属性前面加关键字static

}

protocol FullyNamed { // 这个协议中只包含一个实例属性
var fullName: String { get }
}

struct Person: FullyNamed { // Person遵守FullyNamed协议表示必需要实现fullName属性
var name: String
var fullName: String { // 这个fullName属性可以实现为只读的
return "Barack Hussein (name)"
}
}
let obama = Person(name: "Obama")
print(obama.fullName) // Barack Hussein Obama
3、在协议中定义方法:协议可以要求实现指定的实例方法和类方法,定义的方式和普通方法相同,但不需要大括号和方法体

protocol SomeProtocol3 {
static func someTypeMethod() // 定义类方法的时候用static作前缀
}

protocol RandomNum { // 要求遵守协议的类型必须有一个名为random的方法
func random() -> Int
}
class RandomNumGenerator: RandomNum{
func random() -> Int {
return Int(arc4random() % 10)
}
}
let randomNum = RandomNumGenerator()
print(randomNum.random()) // 0~9的随机数

4、Mutating关键字在协议中的应用:在结构体和枚举即值类型的实例方法中,不能直接修改其实例属性,需要在其方法前面加Mutating关键字

protocol toggleProtocol {
mutating func toggle() // 对于需要结构体和枚举遵守的协议方法需要在前面添加mutating
}

enum Toggle: toggleProtocol {
case Off, On
mutating func toggle() {
switch self {
case .Off:
self = .On
case .On:
self = .Off
}
}
}

var lightSwitch = Toggle.Off
lightSwitch.toggle() // 置反
print(lightSwitch == .On) // true

5、在协议中定义构造器:写下构造器的声明,但不需要写花括号和结构器实体
protocol SomeProtocol4 {
init(someParameter: Int)
}

class SomeInitClass: SomeProtocol4 {
// 遵守协议的构造器都必须在前面带required修饰符,来确保所有子类都要实现此构造器
required init(someParameter: Int) {
// 构造器实现部分
}
}

6、协议作为类型使用:可以作为函数方法和构造器中的参数类型或返回值类型,作为常量变量或属性的类型,作为数组字典或其他容器中元素的类型
class Dice { // 定义一个骰子
let generator: RandomNum // 协议类型的存储属性
init(generator: RandomNum) {
self.generator = generator
}
func roll() -> Int { // 产生一个随机数
return generator.random()
}
}

class RandomNumGenerator1: RandomNum{ // 定义一个类遵守该协议
func random() -> Int {
return Int(arc4random() % 10)
}
}

var d6 = Dice(generator: RandomNumGenerator1()) // 就可以将遵守该协议的类当作参数了
print(d6.roll()) // 随机数

7、代理设计模式:可以将类或结构体的一些功能委托给其他类型去实现,代理可以用来响应事件或接收外部数据源数据
class Baby {
var needNumFood: Int? // baby需要的食物数量
var babyDelegate: BabyDelegate? // 代理属性
func eat() { // 吃这个方法
babyDelegate?.feedBaby(baby: self) // 调用代理方法
}
}

class Nanny: BabyDelegate{ // nanny遵守代理
func feedBaby(baby: Baby) { // nanny实现喂食物的代理方法
baby.needNumFood = 10
print("喂baby食物:(baby.needNumFood!)") // 喂baby食物:10
}
}

let baby = Baby()
let nanny = Nanny()

baby.babyDelegate = nanny // 将baby委托给nanny
baby.eat() // baby调用吃的方法委托nanny喂食物

8、在extention中实现协议

protocol SomeProtocol5 {
// 协议内容
}
extension Nanny: SomeProtocol5 { // 在扩展中遵守协议的效果和在原始类中一样
// 在实际开发中实现协议的时候推荐这样做,有利于提高代码的阅读性
}
9、通过扩展遵守协议:当一个类实现了协议中的方法,却还没有遵守该协议时,可以通过空扩展体来遵守该协议

protocol SomeProtocol6 {
var description: String { get }
}
struct Cat { // 并没有遵守协议
var name: String
var description: String { // 实现协议中的方法
return "A cat named: (name)"
}
}
extension Cat: SomeProtocol6 {} // 在扩展中实现协议

let lucyTheCat = Cat(name: "lucy")
let sp: SomeProtocol6 = lucyTheCat // 遵守协议
print(sp.description) // A cat named: lucy

10、协议本身也是类型,可以放到集合中使用
let things: [SomeProtocol6] = [lucyTheCat] // 用于存放遵守协议的类
for thing in things {
print(thing.description) // A cat named: lucy
}

11、协议的继承:和类的继承相似,但协议可以继承一个或多个其它协议

protocol InheritingProtocol: SomeProtocol5, SomeProtocol6 {
// 任何实现InheritingProtocol协议的同时,也必须实现SomeProtocol5和SomeProtocol6
}

12、 类的专属协议:通过添加class关键字来限制协议只能被类遵守
protocol SomeClassOnlyProtocol: class, InheritingProtocol { // class关键字必须出现在最前面
// 如果被结构体或枚举继承则会导致编译错误
}

13、协议合成:同时采纳多个协议,多个协议之间用&分割,协议的合成并不会生成新的协议类型,只是一个临时局部的
protocol Name {
var name: String { get }
}
protocol Age {
var age: Int { get }
}

struct People: Name, Age { // 遵守name age这两个协议
var name: String
var age: Int
}

func say(to people: Name & Age) { // 参数类型:Name & Age
print("This is (people.name), age is (people.age)") // This is Joan, age is 20
}

let p = People(name: "Joan", age: 20)
say(to: p)

14、检查协议的一致性,如果不一致可以进行转换

// is 检查实例是否符合某个协议,符合返回true,否则返回false
// as? 如果符合某个协议类型,返回类型为协议类型的可选值, 否则返回nil
// as! 将实例强制转化为某个协议类型,如果失败会引发运行时错误
protocol HasArea { // HasArea协议
var area: Double { get }
}
class Circle: HasArea { // 遵守HasArea协议
let pi = 3.1415927
var radius: Double
var area: Double { return pi * radius * radius}
init(radius: Double) { self.radius = radius }
}

class Country: HasArea { // 遵守HasArea协议
var area: Double
init(area: Double) { self.area = area }
}

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

15、将所有类对象作为AnyObject对象放到数组中

let objects: [AnyObject] = [ Circle(radius: 3.0), Country(area: 23460), Animal(legs: 4)]
for object in objects {
if let objectWithArea = object as? HasArea { // 判断object是否遵守area协议
print(objectWithArea.area) // 此时的objectWithArea是area协议类型的实例
print(objectWithArea.pi) // ❌, 所以只有area属性才能被访问
}else {
print("没有遵守area协议")
}
}

16、协议的可选要求:协议中所有的方法,属性并不都是一定要实现的,可以在实现和不实现的方法 面前加optional关键字,使用可选方法或属性时,他们的类型会自动变为可选的

// 注意: 可选的协议前面需要加@objc关键字.
// @objc:表示该协议暴露给OC代码,但即使不与OC交互只想实现可选协议要求,还是要加@objc关键字.
// 带有@objc关键字的协议只能被OC类,或者带有@objc关键字的类遵守,结构体和枚举都不能遵守.
@objc protocol CounterDataSource { // 用于计数的数据源
@objc optional var fixAdd: Int { get } // 可选属性
@objc optional func addForCount(count: Int) -> Int // 可选方法,用于增加数值
}
class Counter: CounterDataSource {
var count = 0 // 用来存储当前值
var dataSource: CounterDataSource?
func add() { // 增加count值
// 使用可选绑定和两层可选链式调用来调用可选方法
if let amount = dataSource?.addForCount?(count: count) {
count += amount
}else if let amount = dataSource?.fixAdd {
count += amount
}
}
}
class ThreeSource: NSObject, CounterDataSource {
let fixAdd = 3
}

var counter = Counter()
counter.dataSource = ThreeSource() // 将counter的数据源设置为ThreeSource
counter.add() // 增加3
counter.add() // 增加3
print(counter.count) // 6

17、协议的扩展:可以通过扩展协议来遵守协议的类型提供属性方法下标

protocol RandomNumG {
func random() -> Int
}
class RandomNumGen: RandomNumG {
var description: String {
return "RandomNumGen"
}
func random() -> Int {
return Int(arc4random() % 10) // 返回一个0~9的随机数
}
}

let randomNumG = RandomNumGen()
print(randomNumG.random()) // 0~9的随机数

extension RandomNumG {
var description: String {
return "extension"
}
func randomBool() -> Bool { // 可以通过扩展来为协议添加方法
return random() > 4 // 随机数是否大于4
}
}
print(randomNumG.randomBool()) // bool值
print(randomNumG.description) // RandomNumGen,协议扩展中的默认属性的优先级比自定义属性低

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

推荐阅读更多精彩内容