https://developer.apple.com/library/prerelease/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID267
Protocol在Swift中的作用就是接口, Protocol中可以声明若干个方法,但没有函数体; protocol跟Java interface关键字的作用是完全一样的。 Swift protocol跟Java interface的区别是interface可以给成员变量赋值、而protocol对成员变量只能声明set/get方法。
前文提到Swift只支持单继承, 但可以实现若干个protocol。
Swift声明protocol(即接口)的语法:
protocol SomeProtocol { //声明函数 }
struct SomeStructure: FirstProtocol, AnotherProtocol { // 结构体实现protocol }
类同样可以实现一个或多个protocol, 跟Java的extends、implements关键字不同的是, Swift在继承类实现接口时只用逗号分隔,语法如下:
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol { // 类定义 }SomeClass继承于SomeSuperclass,并实现了FirstProtocol和AnotherProtocol。
成员属性:
protocol SomeProtocol { var mustBeSettable: Int { get set } //可读写 var doesNotNeedToBeSettable: Int { get } //只读 }
对于静态变量,当类去实现protocol时可以用class或者static声明为静态变量。
protocol AnotherProtocol { static var someTypeProperty: Int { get set } //声明为可读写静态属性 }
protocol FullyNamed { var fullName: String { get } //声明为只读属性,属性类型为String }
struct Person: FullyNamed { var fullName: String } class PersonExt: FullyNamed { var fullName: String = "" }在实现protocol FullyNamed后, 结构体Person和类PersonExt必须包含protocol声明的成员属性。
class StarShip: FullyNamed { var prefix: String? //Optional类型 var name: String //字符串类 init(name: String, prefix: String? = nil) { //跟其它语言一样当后面参数有默认值时,在调用时可以省略 self.name = name self.prefix = prefix } var fullName: String { //protocol要求返回fullName的值 return (prefix != nil ? prefix! + " " : "") + name //三目运算符跟Java的用法一致! } } var objNc = StarShip(name: "Enterprise", prefix: "USS") var objNc1 = StarShip(name: "Company")类StarShip实现了FullyNamed, 即要对fullName返回String类型的值, 声明了成员变量prefix、name和构造函数init。 因为init对参数prefix赋了初值nil,所有在调用时可以省略。(注意:Swift的String肯定有值,String?可能为nil或者字符串; 所以String?类似于Java的String)
protocol SomeProtocol { static func someTypeMethod() }
protocol RandomNumberGenerator { func random() -> Double }跟Java interface一样, Swift声明函数但不带函数体, 在实现类中定义函数功能。
protocol FullyNamed { var fullName: String { get } func getName() -> String static func getFullName() -> String } class PersonExt: FullyNamed { static func getFullName() -> String { //函数访问权限都是internal,所有可以省略internal return "PersonExt full" } internal func getName() -> String { // 访问权限public/internal/private中默认的internal return "personExt name" } var fullName: String = "" //等号后是字符串,就是fullName的get方法实现。 } var person = PersonExt() print(person.getName()) //通过实例调用方法 print(PersonExt.getFullName()) //通过类调用输出:
personExt name
PersonExt full
protocol Togglable { mutating func toggle() //如果结构体和枚举要实现Togglable,那么必须添加mutating前缀 }
示例代码:Shop的buySomeThing方法没有mutating前缀,但可以修改DoShop类的amount属性;Togglable的toggle方法带有mutating前缀,可以修改name属性。
protocol Togglable { mutating func toggle() //测试修改成员属性 } struct TestStruct: Togglable { mutating internal func toggle() { name = "TestStruct modify success" } var name: String } protocol Shop { func buySomeThing() } class DoShop: Shop { func buySomeThing() { amount -= 50 //测试修改成员属性amount } var amount: Int = 100 } var objStruct = TestStruct(name: "TestStruct") var objShop = DoShop() objStruct.toggle() //修改成员属性name的值 print(objStruct.name) objShop.buySomeThing() //修改成员变量amount print("amount: \(objShop.amount)")输出:
TestStruct modify success
amount: 50
声明构造函数接口:在protocol声明若干个init方法, 在类实现Protocol时定义函数体。
protocol SomeProtocol { init(someParameter: Int) }
class SomeClass: SomeProtocol { required init(someParameter: Int) { // initializer implementation goes here } }在类实现构造函数时必须添加required关键字;仅仅在类被声明为final类型, 那么可以省略required关键字。
如果基类、Protocol声明了一模一样的构造函数, 派生类在继承基类并实现接口时会怎样? PS:同样场景在Java里寻址会找到接口函数。
protocol SomeProtocol { init() } class SomeSuperClass { init() { // initializer implementation goes here } } class SomeSubClass: SomeSuperClass, SomeProtocol { // "required" from SomeProtocol conformance; "override" from SomeSuperClass required override init() { // initializer implementation goes here } }说明:感觉Swift的这个语法就是和稀泥, 基类和protocol的init都要兼顾, 在派生类里构造函数添加前缀required override。
现在是重点了, 我们知道Java里的interface是将一个接口的引用作为参数传给一个对象。 在Swift里,protocol可以是类成员变量的类型。
class DoShop: Shop { required init(amount: Int) { self.amount = amount } func buySomeThing() { amount -= 50 //测试修改成员属性amount } var toggle: Togglable //protocol类型 var amount: Int = 100 }
扩展实现接口, 即Extension实现protocol。
protocol TextRepresentable { var textualDescription: String { get } } extension Dice: TextRepresentable { //删掉protocol名称并实现接口函数也可以! var textualDescription: String { return "A \(sides)-sided dice" } }
Protocol支持多继承, 类要实现所有protocol里的函数。语法和示例代码:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol { // protocol definition goes here }
protocol A { func methodA() } protocol B { func methodB() } protocol C: A,B { func methodC() }
class MulTest: C { internal func methodB() { ... } internal func methodA() { ... } internal func methodC() { ... } }
前面提到protocol适用于类、结构体和枚举, 那么如果只想给类用该怎么办呢?
Swift支持仅适用于类的Protocol, 语法如下:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol { // class-only protocol definition goes here }示例代码:
protocol A { func methodA() } protocol B: class { func methodB() } protocol C: class,A,B { func methodC() } class MulTest: C { internal func methodB() { ... } internal func methodA() { ... } internal func methodC() { ... } }
复合Protocol, 因为protocol可以继承若干个protocol, 那么如何判断它到底继承于哪些protocol呢? Swift提供了关键字&。
protocol Named { var name: String { get } } protocol Aged { var age: Int { get } } struct Person: Named, Aged { var name: String var age: Int } func wishHappyBirthday(to celebrator: Named & Aged) { //参数是 Named & Aged 类型 print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!") } let birthdayPerson = Person(name: "Malcolm", age: 21) //Person类实现了Named和Aged的方法 wishHappyBirthday(to: birthdayPerson) // 输出 "Happy birthday, Malcolm, you're 21!"说明:语法跟Java类似, 函数参数类型为接口, 但在调用函数时传递的是对象引用(实现了接口)。
类型判断: is、as?和as!关键字同样适用于Protocol,语法跟判断类是一样的。
小结:
Swift的protocol可以理解为接口类, 它主要作用就是声明它的能力,即声明若干个函数, 作用类似于Java的interface关键字。
1、 Protocol默认适用于类、结构体和枚举, 添加后缀class后只给类用;
2、类可以实现若干个protocol;
3、protocol可以作为类成员变量的参数类型(同Java的回调用法);
4、Swift支持多个protocol作为参数类型;
5、protocol声明init函数后,在类中实现该init函数时必须带required前缀。