协议定义了一个蓝图,规定了用来实现某一特定工作或者功能所必需的方法和属性。类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能。任意能够满足协议要求的类型被称为遵循(conform)这个协议。
协议定义,协议中的属性需求,协议中的方法需求
//土豪修别墅,看看如何使用协议
@objc protocol Villa{
//土豪朋友必须知道有多少层,实例属性
var floors: Int {get}
//土豪朋友必须知道要什么风格,实例属性
var style: String {get}
//别墅必须有门可以打开,实例方法
func openGate() -> String
//别墅必须要有空调
func openAirCondition() -> Bool
//别墅有个狗窝更好
optional func hasDogHouse() -> Bool
}
//土豪朋友以前也是码农,虽然改了别墅,但也必须要有一个工作室写程序
protocol Workshop:class{
//必须要有桌子
var desk: String {get set}
//必须要有一本编程书籍
var book: String {get}
//可以编写程序
func program(code: String) -> Bool
//可以调试
func debug()
}
//码农离不开两样东西,电脑和游戏,必须要有游戏屋
protocol GameRoom{
var games: String {get}
}
//最终土豪朋友拿着这份组合协议的要求去招标
protocol CombineRequirement: Villa,Workshop{
//这里不能够使用class,类属性
static var name: String {get}
//在协议中声明类方法,仍然要使用static关键字。
static func finish() -> Bool
}
//实现了别墅建设要求的组合协议
class 别墅: CombineRequirement{
@objc let floors = 4
@objc var style:String{
return "全欧式建筑"
}
@objc func openGate() -> String {
return "全自动门,自由打开!"
}
@objc func openAirCondition() -> Bool {
return true
}
var desk: String{
get{
return "码农专办公室"
}
set{
print("新的值为:\(newValue)")
}
}
var book = "编程玩转 Swift"
func program(code: String) -> Bool {
print("土豪哥写的Swift程序:\(code)")
return true
}
func debug() {
print("调试通过哦!")
}
class var name: String{
return "别墅"
}
class func finish() -> Bool {
return true
}
}
上述代码分析: 普通格式: protocol 协议名称 {协议体} 如:protocol GameRoom{协议体}
继承格式:protocol 协议名称:协议1,协议2,...{协议体}如:protocol CombineRequirement: Villa,Workshop{协议体}
类特定协议格式1:@objc protocol 协议名称 {协议体} 如:@objc protocol Villa{协议体}
类特定协议格式2:protocol 协议名称: class {协议体} 如:protocol Workshop:class{协议体}
注意点:
要使类遵循某个协议,需要在类型名称后加上协议名称,中间以冒号:分隔,作为类型定义的一部分。遵循多个协议时,各协议之间用逗号,分隔。
class SomeClass: FirstProtocol, AnotherProtocol {
// 类的内容
}
如果类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以逗号分隔。
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 类的内容
}
1)协议中描述属性需求时,必须使用var.
2)遵守类型实现协议属性需求时,不能够改变属性的名字和类型,比如Villa中的style,在其遵守类型中不能改为其他名字或者类型,否则会被认为没有遵守该需求.
3)协议中的需求可以描述为实例属性,也可以是类型属性.而不指定是存储型属性(stored property)还是计算型属性(calculate property),在协议中定义类属性(type property)时,总是使用static关键字作为前缀。当协议的遵循者是类时,可以使用class或static关键字来声明类属性,但是在协议的定义中,仍然要使用static关键字。
4)属性后面的{get}表示,协议遵守类型在实现该属性需求时,既可以把该属性定义为变量var,也可以是常量let.
5)属性后面的{get set}表示,协议遵守类型在实现该属性需求时,只能够是把该属性定义为变量var,而不能够为let.
1)既可以描述为实例方法,也可以描述为类型方法.像普通的方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是在协议的方法定义中,不支持参数默认值。
2)和属性要求一样,其遵守类型在实现方法需求时不能够改变协议方法的名称,返回值和参数必须完全匹配.
3)协议可以继承并且可以多继承,如:protocol CombineRequirement: Villa,Workshop{},CombineRequirement协议同时遵守了两个协议.
4)标准格式的协议可以被类,结构体和枚举三种类型遵守,但我们也可以通过使用"类特定协议格式1"和"类特定协议格式2"强制这个协议只能是被类类型遵守,如: Villa和 Workshop协议只能是被类类型遵守,如果使用值类型会提示和错误.
5)需要注意一个细节:如果一个协议 A 继承自多个协议(B,C,D 等),如果被继承的多个协议里面有一个是类特定使用的协议,则最终A协议及时没有要求只能够被类遵守,其结果也是类特定的协议.如上述代码:CombineRequirement,因为Villa是类特定协议,所以最终CombineRequirement也是类特定协议.
6)并不是所有的协议方法都需要在遵守类型中实现,如:optional func hasDogHouse() -> Bool,使用optional修饰,是可选的,不是强制性的实现.
7)在协议中定义类方法的时候,总是使用static关键字作为前缀。当协议的遵循者是类的时候,虽然你可以在类的实现中使用class或者static来实现类方法,但是在协议中声明类方法,仍然要使用static关键字。
协议中的突变方法
有时需要在方法中改变它的实例。例如,值类型(结构体,枚举)的实例方法中,将mutating
关键字作为函数的前缀,写在func
之前,表示可以在该方法中修改它所属的实例及其实例属性的值.
protocol Togglabel{
mutating func toggle()
}
//定义枚举开关类型,来实现Togglabel协议
enum Switcher:Togglabel{
case Off,On
mutating func toggle() {
switch self{
case .Off:
self = .On
case .On:
self = .Off
}
}
}
//定义结构体按钮类型,来实现Togglabel的协议
struct Button:Togglabel{
var status = "Normal"
mutating func toggle() {
if status == "Normal"{
self = Button(status: "Pressed")
}else{
self = Button(status: "Normal")
}
}
}
//定义控件类,来实现Togglabel协议
class UIControl:Togglabel{
//此时这个协议的突变方法,在类里面就是普通方法,因为类是引用类型,不支持突变方法.
func toggle() {
print("Toggle me")
}
}
func testTwo(){
var switcher:Switcher = .Off
switcher.toggle()
/*
注意的地方:
1:协议中的突变方法需求的定义格式,mutating func 方法名(),如:mutating func toggle()
2:突变方法只能够用于值类型,不能够用于引用类型.对于定义了突变方法需求的协议也可以被类类型实现,但是此时突变方法一普通方法对待,因为类类型是引用类型,不能够使用突变方法,所以实现要去掉前面的mutating 标示符.如: func toggle() { print("Toggle me") }
3:突变方法的目的是程序运行时改变自己,所以凡是要使用突变方法的类型在使用时,都要使用变量类型(var),而不能够是常量类型(let).ru : var switcher:Switcher = .Off
*/
}
协议中的构造器
//凡是侠客,都应该有一把武器
protocol KnightErrant{
init(weapon: String)
//注意:在协议中使用可失败构造器要求
init?(hasWeapon: Bool)
}
//武林低值都是侠客,所以应该遵守侠客的协议
class Disiple: KnightErrant{
//注意:协议中的构造器方法在遵守类中被实现为指定构造器
required init(weapon: String) {
}
//注意:在协议遵守类中实现协议中的可失败构造器的要求,使用init?方式
required init?(hasWeapon: Bool) {
}
//增加便捷构造器
convenience init(){
self.init(weapon: "青蛇剑")
}
}
//武当对自己的弟子也有要求,需要佩带剑
class WuDang{
init(weapon: String){
}
}
//武当弟子,既要遵守武当的门规也要符合侠客的协议
class WuDangDisiple:WuDang,KnightErrant{
//发现协议中的构造器和父类的构造器相同,所以要使用required和override
required override init(weapon: String) {
super.init(weapon: weapon)
}
//在协议遵守类中实现协议的可失败构造器的要求,使用init!方式.
required init!(hasWeapon: Bool) {
super.init(weapon: "武当")
if hasWeapon == false{
return nil
}
}
}
//隐居大侠也是侠客类,所以同样遵守侠客的协议
class Hermit: KnightErrant{
init(name: String){
}
required init?(hasWeapon: Bool) {
if hasWeapon == false{
return nil
}
}
//协议中的构造器方法在遵守类中被实现为便捷构造器
required convenience init(weapon: String){
self.init(name: "令狐冲")
}
}
//武林盟主更应该是侠客,所以遵守KnightErrant协议
final class 武林盟主: KnightErrant{
//注意:协议中的构造器方法在遵守类中被实现,但省略了required修饰符,因为该类是final类.所以可以省略.此处init(weapon: String)是构造器的要求.
init!(weapon: String) {
}
init?(hasWeapon: Bool) {
}
}
上面的代码演示了构造器的使用场景:
1:协议中的构造器可以在遵守类中实现为指定构造器或者便捷构造器.
2:协议遵守类在实现构造器需求时,构造器的定义要求使用required修饰符.
3:如果遵守类使用了final修饰,说明该类不会再被继承,所以此时实现的构造器方法可以省略required修饰符.
4:如果遵守类既遵守某个协议又继承某个类,发现父类中的指定构造器和协议中要求的构造器相同,这时在遵守类中的required和override都要写.
5:关于协议中的可失败构造器的使用,如我们定义了一个可失败构造器init?(hasWeapon: Bool) {},为了满足可失败构造器要求,在协议的实现类中,我们就可以使用init?的方式,也可以使用init!的方式实现.同时也要注意:对于非可失败构造器init(weapon: String),我们在遵守类中的实现也有2种方式,分别是init和init!方式.如: required init(weapon: String) {}和init?(hasWeapon: Bool) {} .
这里为什么一会要required一会不要呢?因为Swift中的所有的语法和修饰符的使用都是为了语法逻辑更加清晰,当一个类不被final修饰,说明该类可以被继承,被重写,甚至可能会被继承多次,所以需要写required,其目的是想告诉后面的继承者们,你们必须重写这个构造器,这个构造器是必须的,但是如果被final修饰了,说明该类不会被继承,那写不写required就没什么价值了,所以可以省略.
协议与委托
委托是一种设计模式,它允许类或者结构体将一些需要它们负责的功能交由(委托)其他类型的实例来完成.在这种设计模式中,主要有以下几个角色:
公共接口:负责封装起需要被委托的功能.
代理者:被委托通过它来调用委托的功能.
委托者:将它们负责的功能委托给其他的类或者结构体,及被委托者.
//异形者的委托协议
@objc protocol SkinChangeDelegate{
var skinType:String {get}
func spy()
optional func Investigate()
optional func attack()
}
//鹰,可以在空中侦察
class EagleSkin:SkinChangeDelegate{
@objc var skinType: String = "鹰"
@objc func spy() {
print("\(skinType)熬翔于天际,侦察敌情!")
}
}
//狼,可以在夜间侦察
class WolfSkin:SkinChangeDelegate{
@objc var skinType: String = "狼"
@objc func spy() {
print("\(skinType)穿行于黑夜,侦察敌情!")
}
}
//曼斯的野人军团
class WildArmy{
var warrior: Int = 1000
var giant: Int = 25
var mammoth: Int = 10
//异性者,凡是异性者都实现了这个委托协议
var animalDelegate: SkinChangeDelegate?
//进行侦察
func spyByAnimal(){
animalDelegate?.spy()
}
func attack(){
print("感谢异形者控制\(animalDelegate!.skinType)侦察信息,进行攻击!")
}
}
func testOne(){
//创建曼斯的野蛮军团
let armyOfMance = WildArmy()
//目前有两个动物可以被异形者控制,分别是狼和鹰
let animals:[SkinChangeDelegate] = [EagleSkin(),WolfSkin()]
for animal in animals{
//进入动物体内,通过委托协议来调用动物的方法.
armyOfMance.animalDelegate = animal
//通过异性者侦察
armyOfMance.spyByAnimal()
//攻击
armyOfMance.attack()
}
/*
鹰熬翔于天际,侦察敌情!
感谢异形者控制鹰侦察信息,进行攻击!
狼穿行于黑夜,侦察敌情!
感谢异性者控制狼侦察信息,进行攻击!
注意:
1:协议中定义了可委托给异性者的公共接口,并且实现了两个委托者,分别是鹰和狼,它们都遵守了异形者的协议,因此它们可把技能委托给代理者var animalDelegate: SkinChangeDelegate?,及异性者.
2:@objc的用法:
1)标志着当前的Swift类可以导入 OC 中使用
2)标志当前的协议只能够用于类类型.
3)允许在协议中使用可选的方法和属性.
*/
}
协议与扩展
//在保安的思维里好人一定开好车,所以定义了好人的协议就是必须要有一辆好车
protocol GoodMan{
func haveGoodCar()
}
//贼也可以开好车,虽然没有遵守好人的协议,但是满足保安对于好人开好车的要求.
class Thief{
var work = "贼"
func haveGoodCar(){
print("开好车的人不一定是好人,也可能是贼!")
}
}
//普通的民工,是好人,但没有好车
class Civilian{
var work = "挤奶工"
}
//1:扩展遵守内容,因为事先实现了协议内容,所以这里不再实现.出门时,保安看到贼开的好车,认为他们是好人,所以扩展给Thief好人的协议
extension Thief:GoodMan{}
//2:扩展遵守协议,实现协议内容.
extension Civilian:GoodMan{
func haveGoodCar() {
print("好人不一定开好车!")
}
}
func testThree(){
var 刘德华 = Thief()
刘德华.haveGoodCar()
var 傻根 = Civilian()
傻根.haveGoodCar()
/*
开好车的人不一定是好人,也可能是贼!
好人不一定开好车!
上述使用了<天下无贼>的例子说明了通过扩展使被扩展类型遵守协议的类中情况.
1:对于已经存在的类,结构体或者枚举,可以扩展它们,使它们遵守新的协议.如民工类Civilian,刚开始并没有遵守GoodMan协议,我们可以在扩展的时候,使它遵守GoodMan协议.
2:如果一个类,结构体或者枚举,即便已经实现某个协议要求的方法和属性,但是Swift不会认为这个类型已经遵守这个协议.就像Thief类已经实现了方法haveGoodCar,满足了GoodMan协议,但是Thief不会自动遵守这个协议.所以我们可以通过扩展,让Thief类遵守协议,如extension Thief:GoodMan{},这里注意:不需要重新实现协议,只需要把扩展的内容留空即可.
*/
}
可选协议
//定义一份保险合同的协议
@objc protocol CarInsurance{ //在@objc修饰后,其类型要和 OC 的类型想匹配,所以其中的属性不能够是可选,方法不能够返回可选类型,只是为了要实现Swift和 OC的互用和兼容!
//强制缴纳交通险
var compulsoryInsurance: Int {get}
//第三方责任险,可以不买,根据需求自行购买
optional var thirdPartyInsurance: Int {get}
//被盗险,可以不买,根据需求自行购买
optional var theftAgainstInsurance: Int {get}
//用于复杂的保险金额统计,如果只有一种保险,那就没有必要实现
optional func countInsuranceCost() -> Int
}
//保险公司给出的保险单
class CarInsuranceReport{
//车种类型
var carBrand:String
//车牌
var carId:String
//保险种类数据
var insuranceData: CarInsurance?
init(carBrand: String, carId: String){
self.carBrand = carBrand
self.carId = carId
}
//汇报购买保险的金额和种类
func reportInsuranceInformation(){
//如果购买了强制交通险
if let amount = insuranceData?.compulsoryInsurance{
print("\(carBrand)\(carId)买了如下保险:")
print("强制交通险:\(amount)")
} else{
print("\(carBrand)\(carId)没有保险记录:")
return
}
//如果购买了第三方责任险
if let amount = insuranceData?.thirdPartyInsurance{
print("第三方责任险:\(amount)")
}
//如果购买了被盗险
if let amount = insuranceData?.theftAgainstInsurance{
print("第三方被盗险:\(amount)")
}
//如果购买了复杂的保险金额统计
if let amount = insuranceData?.countInsuranceCost?(){
print("总计:\(amount)")
} else{
print("总价不要算了,这个屌丝只买了强制险!")
}
}
}
//保险公司为屌丝准备了一份合同
class DiaoSiContract: CarInsurance{
@objc let compulsoryInsurance = 960
}
//保险公司为土豪准备了一份合同
class TuHaoContract: CarInsurance{
@objc let compulsoryInsurance = 960
@objc let thirdPartyInsurance:Int
@objc let theftAgainstInsurance:Int
//土豪买了所有的保险
init(thirdPartyInsurance: Int,theftAgainstInsurance: Int){
self.thirdPartyInsurance = thirdPartyInsurance
self.theftAgainstInsurance = theftAgainstInsurance
}
@objc func countInsuranceCost() -> Int {
var amount = thirdPartyInsurance + theftAgainstInsurance
if amount > 5000{
return Int(Double(amount) * 0.9) + compulsoryInsurance //有打折
}else{
return compulsoryInsurance
}
}
}
func testFour(){
//迈巴赫霸气的牌子
var report = CarInsuranceReport(carBrand: "迈巴赫", carId: " A111111")
//买了车,没钱买保险,只能够暂时不买!
report.reportInsuranceInformation()
//被警察叔叔逮着,强制买了屌丝专用保险
report.insuranceData = DiaoSiContract()
report.reportInsuranceInformation()
//一不小心中了彩票,买了土豪专用保险,9折优惠!
report.insuranceData = TuHaoContract(thirdPartyInsurance: 3500, theftAgainstInsurance: 2500)
report.reportInsuranceInformation()
/*
迈巴赫 A111111没有保险记录:
迈巴赫 A111111买了如下保险:
强制交通险:960
总价不要算了,这个屌丝只买了强制险!
迈巴赫 A111111买了如下保险:
强制交通险:960
第三方责任险:3500
第三方责任险:2500
总计:6360
分析:
1:可选协议的声明必须使用@objc修饰,修饰符有以下两个用途:
一是用于把 Swift类型导出到 OC 代码时用.
二允许协议指定为可选.如:@objc protocol CarInsurance{}
2:对于协议中的可选属性和方法,需要使用optional修饰.
3:可选协议的遵守类可以只实现协议中的非可选需求.
4:在使用时,注意可选链:insuranceData?.countInsuranceCost?().第一个?表示insuranceData可能存在也可能不存在,第二个?表示,及时insuranceData存在了,但是countInsuranceCost方法可能没有实现,不存在.如DiaoSiContract就没有实现.
*/
}
协议的组合和转换
@objc protocol Money{
func haveMoney() ->Bool
}
//定义帅的协议
@objc protocol GoodLook{
func haveGoodLook() ->Bool
}
//既帅又有钱,人生赢家,符合帅和有钱的协议
class LifeWinner: Money,GoodLook{
var name = ""
@objc func haveGoodLook() -> Bool {
print("帅的一逼!")
return true
}
@objc func haveMoney() -> Bool {
print("富的流油!")
return true
}
}
//有钱但是不帅,比如李刚
class LiGang:Money{
@objc func haveMoney() -> Bool {
print("我是李刚,哥有钱!")
return true
}
}
struct MatchMakingAgent{
//对于婚姻中介,只要有征婚的需求,不管条件,都会放到获选人数据库里
var candidatsList:[Money] = [LifeWinner(),LiGang()];
var name = "百合网"
//为客户服务
func service(customer: String){
//有客户提出需求时,需要在候选人库里找
for candidate in candidatsList{
if candidate is GoodLook{
print("遍历获选人数据,发现这个人确实很帅!")
}
//如果是人生赢家,优先推荐
if candidate as? LifeWinner != nil{
print("遍历候选人数据库,这个候选人看起来是人生赢家,莫非是郭富城,客户会满意的!")
//给客户看看这个男人怎么样!
findBoyFriendForCustomer(customer, candidate: candidate as! LifeWinner)
break
}
}
}
//为客户找一个男朋友,女人们都想找一个男人既帅又有钱,所以后面的参数是一个协议组合
func findBoyFriendForCustomer(customer:String,candidate:protocol){
if candidate.haveGoodLook() && candidate.haveMoney(){
print("\(customer)觉得这个人帅又有钱,人生赢家,\(customer)很开心!")
}
}
}
func testFive(){
var matchMakingAgent = MatchMakingAgent()
matchMakingAgent.service("凤姐")
/*
遍历获选人数据,发现这个人确实很帅!
遍历候选人数据库,这个候选人看起来是人生赢家,莫非是郭富城,客户会满意的!
帅的一逼!
富的流油!
凤姐觉得这个人帅又有钱,人生赢家,凤姐很开心!
分析:
1:定义协议组合的格式为:protocol<协议1,协议2,...>.如上述代码:func findBoyFriendForCustomer(customer:String,candidate:protocol),其意思是凡是传入方法的参数candidate必须同时满足Money,GoodLook协议.
2:之前说过,协议本身也是一种类型,所以可以被存储,被转换.如: var candidatsList:[Money] = [LifeWinner(),LiGang()];这里定义了一个Money协议的数组,里面存放着该协议的遵守类型.
协议可以像其他普通类型一样使用,使用场景:
作为函数、方法或构造器中的参数类型或返回值类型
作为常量、变量或属性的类型
作为数组、字典或其他容器中的元素类型
注意
协议是一种类型,因此协议类型的名称应与其他类型(Int,Double,String)的写法相同,使用大写字母开头的驼峰式写法,例如(FullyNamed和RandomNumberGenerator)
3:使用is判断一个类型是否遵守了某个协议.
4:使用as!和as?来实现协议的转换.其区别在前面文章提到过,as!属于强制转换,如果转换失败会报运行时错误,而as?则会返回一个可选类型,如果转换失败,其返回值为 nil,转换成功,则表示转换后的值.
*/
}