协议(Protocols)

协议 定义了一个蓝图,规定了用来实现某一特定工作或者功能所必需的方法和属性。类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能。任意能够满足协议要求的类型被称为 遵循(confor
m) 这个协议。

  • 协议的语法(Protocol Syntax)

  • 对属性的规定(Property Requirements)

  • 对方法的规定(Method Requirements)

  • 对Mutating方法的规定(Mutating Method Requirements)

  • 对构造器的规定(Initializer Requirements)

  • 协议类型(Protocols as Types)

  • 委托(代理)模式(Delegation)

  • 在扩展中添加协议成员(Adding Protocol Conformance with an Extension)

  • 通过扩展补充协议声明(Declaring Protocol Adoption with an Extension)

  • 集合中的协议类型(Collections of Protocol Types)

  • 协议的继承(Protocol Inheritance)

  • 类专属协议(Class-Only Protocol)

  • 协议合成(Protocol Composition)

  • 检验协议的一致性(Checking for Protocol Conformance)

  • 对可选协议的规定(Optional Protocol Requirements)

  • 协议扩展(Protocol Extensions)

1.协议的语法

协议的定义方式与类,结构体,枚举的定义非常相似。

protocol SomeProtocol {
// 协议内容
}

要使类遵循某个协议,需要在类型名称后加上协议名称,中间以冒号 : 分隔,作为类型定义的一部分。遵循多个协议时,各协议之间用逗号,分隔。

struct SomeStructure: FirstProtocol,AnotherProtocol {
// 结构体内容
}

如果类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以逗号分隔。

class SomeClass: SomeSuperClass,FirstProtocol,AnotherProtocol {
// 类的内容
}

2.对属性的规定

协议可以规定其 遵循者 提供特定名称和类型的 实例属性(instance property) 或 类属性(type property) ,而不指定是 存储型属性(stored property) 还是 计算型属性(calculate property) 。此外还必须指明是只读的还是可读可写的。

如果协议规定属性是可读可写的,那么这个属性不能是常量或只读的计算属性。如果协议只要求属性是只读的(gettable),那个属性不仅可以是只读的,如果你代码需要的话,也可以是可写的。

协议中的通常用var来声明属性,在类型声明后加上 { set get } 来表示属性是可读可写的,只读属性则用 { get} 来表示。

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

在协议中定义类属性(type property)时,总是使用 static 关键字作为前缀。当协议的遵循者是类时,可以使用 class 或 static 关键字来声明类属性,但是在协议的定义中,仍然要使用 static 关键字。

protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}

如下所示,这是一个含有一个实例属性要求的协议。

protocol FullyNamed {
var fullName: String { get }
}

FullyNamed 协议除了要求协议的遵循者提供fullName属性外,对协议对遵循者的类型并没有特别的要求。这个协议表示,任何遵循 FullyNamed 协议的类型,都具有一个可读的 String 类型实例属性 fullName 。

下面是一个遵循 FullyNamed 协议的简单结构体。

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

这个例子中定义了一个叫做 Person 的结构体,用来表示具有名字的人。从第一行代码中可以看出,它遵循了 FullyNamed 协议。

Person 结构体的每一个实例都有一个叫做 fullName , String 类型的存储型属性。这正好满足了 FullyNamed 协议的要求,也就意味着,Person 结构体完整的 遵循 了协议。(如果协议要求未被完全满足,在编译时会报错)

下面是一个更为复杂的类,它采用并遵循了 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")
// ncc1701.fullName is "USS Enterprise"

Starship类把 fullName 属性实现为只读的计算型属性。每一个 Starship 类的实例都有一个名为 name 的属性和一个名为 prefix 的可选属性。 当 prefix 存在时,将 prefix 插入到 name 之前来为Starship构建 fullName , prefix 不存在时,则将直接用 name 构建 fullName 。

3.对方法的规定

协议可以要求其遵循者实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通的方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相
同。但是在协议的方法定义中,不支持参数默认值。

正如对属性的规定中所说的,在协议中定义类方法的时候,总是使用 static 关键字作为前缀。当协议的遵循者是类的时候,虽然你可以在类的实现中使用 class 或者 static 来实现类方法,但是在协议中声明类方法,仍然要使用 static 关键字。

protocol SomeProtocol {
static func someTypeMethod()
}

下面的例子定义了含有一个实例方法的协议。

protocol RandomNumberGenerator {
func random() -> Double
}

RandomNumberGenerator 协议要求其遵循者必须拥有一个名为 random , 返回值类型为 Double 的实例方法。尽管这里并未指明,但是我们假设返回值在[0,1)区间内。

RandomNumberGenerator 协议并不在意每一个随机数是怎样生成的,它只强调这里有一个随机数生成器。

如下所示,下边的是一个遵循了 RandomNumberGenerator 协议的类。该类实现了一个叫做 线性同余生成器(linear congruential generator) 的伪随机数算法。

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()
print("Here's a random number: \(generator.random())")
// 输出 : "Here's a random number: 0.37464991998171"
print("And another one: \(generator.random())")
// 输出 : "And another one: 0.729023776863283"

4.对Mutating方法的规定

有时需要在方法中改变它的实例。例如,值类型(结构体,枚举)的实例方法中,将 mutating 关键字作为函数的前缀,写在 func 之前,表示可以在该方法中修改它所属的实例及其实例属性的值。

如果你在协议中定义了一个方法旨在改变遵循该协议的实例,那么在协议定义时需要在方法前加 mutating 关键字。这使得结构和枚举遵循协议并满足此方法要求。

注意:
用类实现协议中的 mutating 方法时,不用写 mutating 关键字;用结构体,枚举实现协议中的 mutating 方法时,必须写 mutating 关键字。
如下所示, Togglable 协议含有名为 toggle 的实例方法。根据名称推测, toggle() 方法将通过改变实例属性,来切换遵循该协议的实例的状态。

toggle() 方法在定义的时候,使用 mutating 关键字标记,这表明当它被调用时该方法将会改变协议遵循者实例的状态。

protocol Togglable {
mutating func toggle()
}

当使用 枚举 或 结构体 来实现 Togglable 协议时,需要提供一个带有 mutating 前缀的 toggle 方法。

下面定义了一个名为 OnOffSwitch 的枚举类型。这个枚举类型在两种状态之间进行切换,用枚举成员 On 和 Off 表示。枚举类型的 toggle 方法被标记为 mutating 以满足 Togglable 协议的要求。

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()
//lightSwitch 现在的值为 .On

5.对构造器的规定

协议可以要求它的遵循者实现指定的构造器。你可以像书写普通的构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:

protocol SomeProtocol {
init(someParameter: Int)
}

5.1 协议构造器规定在类中的实现

你可以在遵循该协议的类中实现构造器,并指定其为类的指定构造器(designated initializer)或者便利构造器(convenience initializer)。在这两种情况下,你都必须给构造器实现上”required”修饰符:

class SomeClass: SomeProtocol {
required init(someParameter: Int) {
//构造器实现
}
}

使用 required 修饰符可以保证:所有的遵循该协议的子类,同样能为构造器规定提供一个显式的实现或继承实现。

注意:
如果类已经被标记为 final ,那么不需要在协议构造器的实现中使用 required 修饰符。因为final类不能有子类。

如果一个子类重写了父类的指定构造器,并且该构造器遵循了某个协议的规定,那么该构造器的实现需要被同时标示 required 和 override 修饰符

protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
// 构造器的实现
}
}
class SomeSubClass: SomeSuperClass,SomeProtocol {
// 因为遵循协议,需要加上"required"; 因为继承自父类,需要加上"override"
required override init() {
// 构造器实现
}
}

5.2可失败构造器的规定

可以通过给协议 Protocols 中添加可失败构造器 (页 0)来使遵循该协议的类型必须实现该可失败构造器。

如果在协议中定义一个可失败构造器,则在遵顼该协议的类型中必须添加同名同参数的可失败构造器或非可失败

构造器。如果在协议中定义一个非可失败构造器,则在遵循该协议的类型中必须添加同名同参数的非可失败构造器或隐式解析类型的可失败构造器( init! )。

6. 协议类型

尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。

协议可以像其他普通类型一样使用,使用场景:

  • 作为函数、方法或构造器中的参数类型或返回值类型

  • 作为常量、变量或属性的类型

  • 作为数组、字典或其他容器中的元素类型

注意:
协议是一种类型,因此协议类型的名称应与其他类型(Int,Double,String)的写法相同,使用大写字母开头的驼峰式写法,例( FullyNamed 和 RandomNumberGenerator )如下所示,这个示例中将协议当做类型来使用

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
}
}

例子中定义了一个 Dice 类,用来代表桌游中的拥有N个面的骰子。 Dice 的实例含有 sides 和 generator 两个属性,前者是整型,用来表示骰子有几个面,后者为骰子提供一个随机数生成器。

generator 属性的类型为 RandomNumberGenerator ,因此任何遵循了 RandomNumberGenerator 协议的类型的实例都可以赋值给generator ,除此之外,无其他要求。

Dice 类中也有一个构造器(initializer),用来进行初始化操作。构造器中含有一个名为 generator ,类型为 RandomNumberGenerator 的形参。在调用构造方法时创建 Dice 的实例时,可以传入任何遵循RandomNumberGenerator 协议的实例给generator。

Dice 类也提供了一个名为 roll 的实例方法用来模拟骰子的面值。它先使用 generator 的 random() 方法来创建一个[0,1)区间内的随机数,然后使用这个随机数生成正确的骰子面值。因为generator遵循了 RandomNumberGenerator 协议,因而保证了 random 方法可以被调用。
下面的例子展示了如何使用 LinearCongruentialGenerator 的实例作为随机数生成器创建一个六面骰子:

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

总结:这篇就今天就写到协议类型,下篇从委托(代理)模式开始总结。

Swift学习第七枪--协议一的更多相关文章

  1. ios – swift中自定义NSManagedObject类核心数据的问题

    我最近开始学习swift,我想使用一些用ObjectiveC编写的数据模型类.当我尝试从输入框保存数据时,我遇到了一个奇怪的错误:Users.hUsers.m这是数据模型截图:这是保存功能:来自lldb的代码:解决方法这与swift无关.如果更新coredata模型而不定义AppleeDoc中提到的合并/版本控制规则,则需要在设备或模拟器上删除并重新安装应用程序.查看错误消息:从模拟器/或设备中删

  2. 寒城攻略:Listo 教你 25 天学会 Swift 语言 - 13 Methods

    方法还可以给它隐含的self属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例structPoint1{0.0,y=mutatingfuncmoveByX{x+=deltaXy+=deltaY}}varsomePoint1=Point1">1.0)注意定义结构体实例时必须为变量不能为常量somePoint1moveByX2.03.0)")//在变异方法中给self赋值structPoint2{Double){self=Point2}}varsomePoint2=Point2(x:somePoi

  3. 【Swift初见】Swift结构体

    访问rect这个实例里面的width和height方法也是用点语法:这样我们就完成了一个结构体的定义和创建一个结构体实例。结构体的成员方法:与C还有OC结构体不一样的是,swift的结构体可以包含成员方法,即行为,这就跟面向对象中的类的概念比较类似:我们给刚刚Rect结构体加上一个面积的方法:我们可以看出,方法的定义跟外部全局函数的定义是一样的,那么如何使用该成员方法呢?

  4. 寒城攻略:Listo 教你 25 天学会 Swift 语言 - 21 Nested Types

    //***********************************************************************************************//1.nestedTypes(类型嵌套)//________________________________________________________________________________

  5. 寒城攻略:Listo 教你 25 天学会 Swift 语言 - 22 Extensions

    //***********************************************************************************************//1.Extensions(扩展)//___________________________________________________________________________________

  6. Swift语法基础:5 - Swift的枚举和结构体

    在Siwft中的枚举类型以及结构体,是和OC中差不多的,但Swift中又有一些特性,下面让我们来看看:1.枚举的声明及使用PS:这里解释一下,枚举类型第一个开始的参数都是1,无论你是有多少case,都会递增的,比如例子的的Ace是1,那么Two就是名副其实的2,Three就是3,以此类推,一直到King,就是13,而enum里面有一个方法,这里面这个方法只是说可以在enum里定义方法,但我这个例子

  7. Swift语法基础:6 - Swift的Protocol和Extensions

    前面我们知道了枚举类型和结构体的声明,嵌套,以及基本的使用,现在让我们来看看Swift中的另外两个,一个是Protocol(协议),一个是Extensions(类别):1.声明ProtocolPS:在声明协议的时候,如果你要修改某个方法属性,你必须的在该方法前加上mutating这个关键字,否则不能修改.2.使用Protocol同样的,在结构体里一样可以使用,但是必须的加上mutating:Protocol也可以这么使用:PS:这里的a是前面例子里面的a.3.声明Extensions好了,这次我们就讲到这

  8. 【Swift初见】Swift构造过程

    构造过程是通过构造器来实现的,其实每个构造器就可以看作是一个函数,只是这个函数是为了执行初始化的。每个类都必须拥有一个指定构造器。

  9. Swift面向对象的类型

    1、类2、结构体3、枚举在swift语言中通过类和结构体实现面向对象,在Swift语言中,枚举也具有面向对象的特性示例和对象在面向对象中,将类创建对象的过程称为实例化,因此将对象称为实例化,但是在swift中,枚举和结构体不能称为对象,因为结构体和枚举并不是彻底的面向对象类型,而是只包含了一些面向对象的特定,例如,在Swift中继承只发生在类上,结构体和枚举不能继承

  10. Swift基础语法: 25 - Swift的类和结构体

    2.有理由预计一个结构体实例在赋值或传递时,封装的数据将会被拷贝而不是被引用。

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部