协议定义了一个蓝图,规定了用来实现某一特定工作或者功能所必需的方法和属性。类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能。任意能够满足协议要求的类型被称为遵循(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<Money,GoodLook>){ 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<Money,GoodLook>),其意思是凡是传入方法的参数candidate必须同时满足Money,GoodLook协议. 2:之前说过,协议本身也是一种类型,所以可以被存储,被转换.如: var candidatsList:[Money] = [LifeWinner(),LiGang()];这里定义了一个Money协议的数组,里面存放着该协议的遵守类型. 协议可以像其他普通类型一样使用,使用场景: 作为函数、方法或构造器中的参数类型或返回值类型 作为常量、变量或属性的类型 作为数组、字典或其他容器中的元素类型 注意 协议是一种类型,因此协议类型的名称应与其他类型(Int,Double,String)的写法相同,使用大写字母开头的驼峰式写法,例如(FullyNamed和RandomNumberGenerator) 3:使用is判断一个类型是否遵守了某个协议. 4:使用as!和as?来实现协议的转换.其区别在前面文章提到过,as!属于强制转换,如果转换失败会报运行时错误,而as?则会返回一个可选类型,如果转换失败,其返回值为 nil,转换成功,则表示转换后的值. */ }