protocols swift_swift-protocols - CSDN
  • Swift3.0 protocol

    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 {
        //声明函数
    }


    结构体可以实现一个或多个protocol(即接口):

    struct SomeStructure: FirstProtocol, AnotherProtocol {
        // 结构体实现protocol
    }

    类同样可以实现一个或多个protocol, 跟Java的extends、implements关键字不同的是, Swift在继承类实现接口时只用逗号分隔,语法如下:

    class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
        // 类定义
    }
    
        SomeClass继承于SomeSuperclass,并实现了FirstProtocol和AnotherProtocol。


    成员属性:

           protocol可以声明成员变量或者静态成员变量,但不能对成员变量赋值, 只能声明其get或set方法(注意仅仅声明,没有实现)。 成员变量可以声明set/get方法或者get方法, 但不能只声明set方法;即成员变量是可读写或者只读变量。PS:Xcode会给出提示。
    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
    }
    注意: 使用结构体实现protocol时,Xcode会自动加入其属性; 使用类型实现protocol时,Xcode会自动加入该属性并赋初值。
    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)


    成员方法: Swift同Java一样对方法的访问权限设置了关键字: public/internal/private,  作用类似于Java修饰方法的public/protected/private。在Swift语法中,函数访问级别默认为internal, 声明函数时可以省略internal关键字。
    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



    修改成员属性的接口函数:
    在《Swift3.0学习笔记-Functions》中提到Swift的函数体默认不能修改成员属性, 声明函数时必须添加前缀mutating关键字才可以, 该特性同样适用于protocol。  如果想在接口函数中修改成员属性, 那么该接口函数一定要添加mutating前缀
        注意: 如果类实现protocol, 那么函数不用带mutating前缀, 默认可以修改成员属性; 结构体和枚举类型如果要在接口函数中修改成员属性,那么必须添加mutating前缀。基本语法如下:
    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前缀。




    展开全文
  • Swift协议(Protocols)

    2015-11-02 15:27:30
    协议主要为一个特定的任务和功能定义一个方法、属性和其他要求,你也可以理解协议就是一种要遵循的规范。学过设计模式的,都知道工厂模式,如果你不知道可以查阅我的博文《23设计模式之工厂方法(FactoryMethod)》,...

    协议主要为一个特定的任务和功能定义一个方法、属性和其他要求,你也可以理解协议就是一种要遵循的规范。

    学过设计模式的,都知道工厂模式,如果你不知道可以查阅我的博文《23设计模式之工厂方法(FactoryMethod)》,工厂模式就是一种协议的体现。在Java中,是用接口定义协议的;在OC中,主要用于代理。

    除了已有的协议,你还可以像扩展类一样扩展协议。这些扩展的协议可以实现也可以直接使用。

    语法

    协议语法使用了关键字protocol,和其他类型不同的是,它是规范的指定者,无须去实现。下面是一个空得协议。

    protocol SomeProtocol {
        // protocol definition goes here
    }

    协议可以被结构体继承,

    struct SomeStructure: FirstProtocol, AnotherProtocol {
        // structure definition goes here
    }

    协议也可以被类继承,当然这是它最主要的功能。

    class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
        // class definition goes here
    }

    协议

    这里我们设计一个协议,并实现一个简单方法。我们在这里使用了一个简单方法。

    protocol YJSomeProtocol {
    
        func test()
    
    }

    编写一个类去实现这个协议。

    class YJSomeClass: YJSomeProtocol {
    
        func test() {
            print(__FUNCTION__)
        }
    
    }
    
    let yjs = YJSomeClass()
    yjs.test()

    如果你想在类型中使用类型方法,可以在func前加static。

    static func test()

    你还可以让该协议只能由class去实现,只需让协议去继承class

    protocol YJSomeProtocol: class

     

    代理

    在Swift中有各种各样的代理,这些代理都是通过协议来规范的,想知道更多关于代理模式的实现详见我的博文《23设计模式之代理模式(Proxy)》。
     

    继承

    协议也支持继承。

    protocol YJAnotherProtocol: YJSomeProtocol {
    
        // 协议可继承
    
    }

    继承的好处就是我们可以在一些公共的协议上,添加我们的协议,使之具有延伸性。
     

    可选

    在swift中又可选链,也就是‘?’和’!’。在这里我们考虑到这种情况,有一个协议里面有5个方法,其中两个方法是必须的,其他3个方法,继承的类不必强制去实现。

    在OC中就有这种机制@optional,在swift也可以这样使用,但是@optional是OC
    的特性。因此,我们想使用这种特性的时候,就需要借助@objc。@objc可以理解为swift和oc的桥梁。

    @objc protocol YJSomeProtocol:class {
    
        // class代表只用类才能实现这个协议
        func test()
    
        // @objc:OC特性,代表可以使用optional特性。optional可选的方法
        optional func testOptional()
    
    }

    但是这样对实现类有一定的影响,因为这个特性是OC的。而OC中所有的类都是继承NSObject,故实现这个协议的类也要继承NSObject。

    class YJSomeClass:NSObject, YJSomeProtocol {
    
        func test() {
            print(__FUNCTION__)
        }
    
    }

     

    扩展

    协议也支持扩展extension,这也相当于可选。只是在扩展的协议中,需要实现方法。

    extension YJSomeProtocol {
    
        func testExtension() {
            print(__FUNCTION__)
        }
    
    }

    实现类可以实现这个方法,也可以不实现这个方法。

     


    其他

    参考资料

    The Swift Programming Language (Swift 2.1)

    文档修改记录

    时间 描述
    2015-11-2 根据 The Swift Programming Language (Swift 2.1)中的Protocols总结

     


    版权所有:http://blog.csdn.net/y550918116j

    展开全文
  • Swift 协议(Protocols

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

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

    协议的语法

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

    protocol SomeProtocol {
        // 协议内容
    }
    

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

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

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

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

    对属性的规定

    协议可以规定其遵循者提供特定名称和类型的实例属性(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关键字作为前缀。当协议的遵循者是类时,可以使用classstatic关键字来声明类属性,但是在协议的定义中,仍然要使用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结构体的每一个实例都有一个叫做fullNameString类型的存储型属性。这正好满足了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构建fullNameprefix不存在时,则将直接用name构建fullName

    对方法的规定

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

    正如对属性的规定中所说的,在协议中定义类方法的时候,总是使用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"
    

    对Mutating方法的规定

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

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

    注意:
    用类实现协议中的mutating方法时,不用写mutating关键字;用结构体,枚举实现协议中的mutating方法时,必须写mutating关键字。

    如下所示,Togglable协议含有名为toggle的实例方法。根据名称推测,toggle()方法将通过改变实例属性,来切换遵循该协议的实例的状态。

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

    protocol Togglable {
        mutating func toggle()
    }
    

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

    下面定义了一个名为OnOffSwitch的枚举类型。这个枚举类型在两种状态之间进行切换,用枚举成员OnOff表示。枚举类型的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
    

    对构造器的规定

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

    protocol SomeProtocol {
        init(someParameter: Int)
    }
    

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

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

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

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

    关于required构造器的更多内容,请参考必要构造器

    注意
    如果类已经被标记为final,那么不需要在协议构造器的实现中使用required修饰符。因为final类不能有子类。关于final修饰符的更多内容,请参见防止重写

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

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

    可失败构造器的规定

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

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

    协议类型

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

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

    • 作为函数、方法或构造器中的参数类型或返回值类型
    • 作为常量、变量或属性的类型
    • 作为数组、字典或其他容器中的元素类型

    注意
    协议是一种类型,因此协议类型的名称应与其他类型(Int,Double,String)的写法相同,使用大写字母开头的驼峰式写法,例如(FullyNamedRandomNumberGenerator)

    如下所示,这个示例中将协议当做类型来使用

    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的实例含有sidesgenerator两个属性,前者是整型,用来表示骰子有几个面,后者为骰子提供一个随机数生成器。

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

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

    Dice类也提供了一个名为roll的实例方法用来模拟骰子的面值。它先使用generatorrandom()方法来创建一个[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
    

    委托(代理)模式

    委托是一种设计模式,它允许结构体将一些需要它们负责的功能交由(委托)给其他的类型的实例。委托模式的实现很简单: 定义协议来封装那些需要被委托的函数和方法, 使其遵循者拥有这些被委托的函数和方法。委托模式可以用来响应特定的动作或接收外部数据源提供的数据,而无需要知道外部数据源的类型信息。

    下面的例子是两个基于骰子游戏的协议:

    protocol DiceGame {
        var dice: Dice { get }
        func play()
    }
    
    protocol DiceGameDelegate {
        func gameDidStart(game: DiceGame)
        func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll:Int)
        func gameDidEnd(game: DiceGame)
    }
    

    DiceGame协议可以在任意含有骰子的游戏中实现。DiceGameDelegate协议可以用来追踪DiceGame的游戏过程

    如下所示,SnakesAndLaddersSnakes and Ladders(Control Flow章节有该游戏的详细介绍)游戏的新版本。新版本使用Dice作为骰子,并且实现了DiceGameDiceGameDelegate协议,后者用来记录游戏的过程:

    class SnakesAndLadders: DiceGame {
        let finalSquare = 25
        let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
        var square = 0
        var board: [Int]
        init() {
            board = [Int](count: finalSquare + 1, repeatedValue: 0)
            board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
            board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
        }
         var delegate: DiceGameDelegate?
         func play() {
             square = 0
             delegate?.gameDidStart(self)
             gameLoop: while square != finalSquare {
                 let diceRoll = dice.roll()
                 delegate?.game(self,didStartNewTurnWithDiceRoll: diceRoll)
                 switch square + diceRoll {
                 case finalSquare:
                     break gameLoop
                 case let newSquare where newSquare > finalSquare:
                     continue gameLoop
                 default:
                 square += diceRoll
                 square += board[square]
                 }
             }
             delegate?.gameDidEnd(self)
         }
    }
    

    这个版本的游戏封装到了SnakesAndLadders类中,该类遵循了DiceGame协议,并且提供了相应的可读的dice属性和play实例方法。(dice属性在构造之后就不再改变,且协议只要求dice为只读的,因此将dice声明为常量属性。)

    游戏使用SnakesAndLadders类的构造器(initializer)初始化游戏。所有的游戏逻辑被转移到了协议中的play方法,play方法使用协议规定的dice属性提供骰子摇出的值。

    注意:delegate并不是游戏的必备条件,因此delegate被定义为遵循DiceGameDelegate协议的可选属性。因为delegate是可选值,因此在初始化的时候被自动赋值为nil。随后,可以在游戏中为delegate设置适当的值。

    DicegameDelegate协议提供了三个方法用来追踪游戏过程。被放置于游戏的逻辑中,即play()方法内。分别在游戏开始时,新一轮开始时,游戏结束时被调用。

    因为delegate是一个遵循DiceGameDelegate的可选属性,因此在play()方法中使用了可选链来调用委托方法。 若delegate属性为nil, 则delegate所调用的方法失效,并不会产生错误。若delegate不为nil,则方法能够被调用

    如下所示,DiceGameTracker遵循了DiceGameDelegate协议

    class DiceGameTracker: DiceGameDelegate {
        var numberOfTurns = 0
        func gameDidStart(game: DiceGame) {
            numberOfTurns = 0
            if game is SnakesAndLadders {
                print("Started a new game of Snakes and Ladders")
            }
            print("The game is using a \(game.dice.sides)-sided dice")
        }
        func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
            ++numberOfTurns
            print("Rolled a \(diceRoll)")
        }
        func gameDidEnd(game: DiceGame) {
            print("The game lasted for \(numberOfTurns) turns")
        }
    }
    

    DiceGameTracker实现了DiceGameDelegate协议规定的三个方法,用来记录游戏已经进行的轮数。 当游戏开始时,numberOfTurns属性被赋值为0; 在每新一轮中递增; 游戏结束后,输出打印游戏的总轮数。

    gameDidStart方法从game参数获取游戏信息并输出。game在方法中被当做DiceGame类型而不是SnakeAndLadders类型,所以方法中只能访问DiceGame协议中的成员。当然了,这些方法也可以在类型转换之后调用。在上例代码中,通过is操作符检查game是否为 SnakesAndLadders类型的实例,如果是,则打印出相应的内容。

    无论当前进行的是何种游戏,game都遵循DiceGame协议以确保game含有dice属性,因此在gameDidStart(_:)方法中可以通过传入的game参数来访问dice属性,进而打印出dicesides属性的值。

    DiceGameTracker的运行情况,如下所示:

    let tracker = DiceGameTracker()
    let game = SnakesAndLadders()
    game.delegate = tracker
    game.play()
    // Started a new game of Snakes and Ladders
    // The game is using a 6-sided dice
    // Rolled a 3
    // Rolled a 5
    // Rolled a 4
    // Rolled a 5
    // The game lasted for 4 turns
    

    在扩展中添加协议成员

    即便无法修改源代码,依然可以通过扩展(Extension)来扩充已存在类型(译者注: 类,结构体,枚举等)。扩展可以为已存在的类型添加属性,方法,下标脚本,协议等成员。详情请在扩展章节中查看。

    注意
    通过扩展为已存在的类型遵循协议时,该类型的所有实例也会随之添加协议中的方法

    例如TextRepresentable协议,任何想要表示一些文本内容的类型都可以遵循该协议。这些想要表示的内容可以是类型本身的描述,也可以是当前内容的版本:

    protocol TextRepresentable {
        func asText() -> String
    }
    

    可以通过扩展,为上一节中提到的Dice增加类遵循TextRepresentable协议的功能

    extension Dice: TextRepresentable {
        func asText() -> String {
            return "A \(sides)-sided dice"
        }
    }
    

    现在,通过扩展使得Dice类型遵循了一个新的协议,这和Dice类型在定义的时候声明为遵循TextRepresentable协议的效果相同。在扩展的时候,协议名称写在类型名之后,以冒号隔开,在大括号内写明新添加的协议内容。

    现在所有Dice的实例都遵循了TextRepresentable协议:

    let d12 = Dice(sides: 12,generator: LinearCongruentialGenerator())
    print(d12.asText())
    // 输出 "A 12-sided dice"
    

    同样SnakesAndLadders类也可以通过扩展的方式来遵循TextRepresentable协议:

    extension SnakesAndLadders: TextRepresentable {
        func asText() -> String {
            return "A game of Snakes and Ladders with \(finalSquare) squares"
        }
    }
    print(game.asText())
    // 输出 "A game of Snakes and Ladders with 25 squares"
    

    通过扩展补充协议声明

    当一个类型已经实现了协议中的所有要求,却没有声明为遵循该协议时,可以通过扩展(空的扩展体)来补充协议声明:

    struct Hamster {
        var name: String
        func asText() -> String {
            return "A hamster named \(name)"
        }
    }
    extension Hamster: TextRepresentable {}
    

    从现在起,Hamster的实例可以作为TextRepresentable类型使用

    let simonTheHamster = Hamster(name: "Simon")
    let somethingTextRepresentable: TextRepresentable = simonTheHamster
    print(somethingTextRepresentable.asText())
    // 输出 "A hamster named Simon"
    

    注意
    即使满足了协议的所有要求,类型也不会自动转变,因此你必须为它做出显式的协议声明

    集合中的协议类型

    协议类型可以在集合使用,表示集合中的元素均为协议类型,下面的例子创建了一个类型为TextRepresentable的数组:

    let things: [TextRepresentable] = [game,d12,simonTheHamster]
    

    如下所示,things数组可以被直接遍历,并打印每个元素的文本表示:

    for thing in things {
        print(thing.asText())
    }
    // A game of Snakes and Ladders with 25 squares
    // A 12-sided dice
    // A hamster named Simon
    

    thing被当做是TextRepresentable类型而不是DiceDiceGameHamster等类型。因此能且仅能调用asText方法

    协议的继承

    协议能够继承一个或多个其他协议,可以在继承的协议基础上增加新的内容要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:

    protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
        // 协议定义
    }
    

    如下所示,PrettyTextRepresentable协议继承了TextRepresentable协议

    protocol PrettyTextRepresentable: TextRepresentable {
        func asPrettyText() -> String
    }
    

    例子中定义了一个新的协议PrettyTextRepresentable,它继承自TextRepresentable协议。任何遵循PrettyTextRepresentable协议的类型在满足该协议的要求时,也必须满足TextRepresentable协议的要求。在这个例子中,PrettyTextRepresentable协议要求其遵循者提供一个返回值为String类型的asPrettyText方法。

    如下所示,扩展SnakesAndLadders,让其遵循PrettyTextRepresentable协议:

    extension SnakesAndLadders: PrettyTextRepresentable {
        func asPrettyText() -> String {
            var output = asText() + ":\n"
            for index in 1...finalSquare {
                switch board[index] {
                    case let ladder where ladder > 0:
                    output += "▲ "
                case let snake where snake < 0:
                    output += "▼ "
                default:
                    output += "○ "
                }
            }
            return output
        }
    }
    

    上述扩展使得SnakesAndLadders遵循了PrettyTextRepresentable协议,并为每个SnakesAndLadders类型提供了了协议要求的asPrettyText()方法。每个PrettyTextRepresentable类型同时也是TextRepresentable类型,所以在asPrettyText的实现中,可以调用asText()方法。之后在每一行加上换行符,作为输出的开始。然后遍历数组中的元素,输出一个几何图形来表示遍历的结果:

    • 当从数组中取出的元素的值大于0时,用表示
    • 当从数组中取出的元素的值小于0时,用表示
    • 当从数组中取出的元素的值等于0时,用表示

    任意SankesAndLadders的实例都可以使用asPrettyText()方法。

    print(game.asPrettyText())
    // A game of Snakes and Ladders with 25 squares:
    // ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
    

    类专属协议

    你可以在协议的继承列表中,通过添加class关键字,限制协议只能适配到类(class)类型。(结构体或枚举不能遵循该协议)。该class关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。

    protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
        // class-only protocol definition goes here
    }
    

    在以上例子中,协议SomeClassOnlyProtocol只能被类(class)类型适配。如果尝试让结构体或枚举类型适配该协议,则会出现编译错误。

    注意
    当协议想要定义的行为,要求(或假设)它的遵循类型必须是引用语义而非值语义时,应该采用类专属协议。关于引用语义,值语义的更多内容,请查看结构体和枚举是值类型类是引用类型

    协议合成

    有时候需要同时遵循多个协议。你可以将多个协议采用protocol<SomeProtocol, AnotherProtocol>这样的格式进行组合,称为协议合成(protocol composition)。你可以在<>中罗列任意多个你想要遵循的协议,以逗号分隔。

    下面的例子中,将NamedAged两个协议按照上述的语法组合成一个协议:

    protocol Named {
        var name: String { get }
    }
    protocol Aged {
        var age: Int { get }
    }
    struct Person: Named, Aged {
        var name: String
        var age: Int
    }
    func wishHappyBirthday(celebrator: protocol<Named, Aged>) {
        print("Happy birthday \(celebrator.name) - you're \(celebrator.age)!")
    }
    let birthdayPerson = Person(name: "Malcolm", age: 21)
    wishHappyBirthday(birthdayPerson)
    // 输出 "Happy birthday Malcolm - you're 21!
    

    Named协议包含String类型的name属性;Aged协议包含Int类型的age属性。Person结构体遵循了这两个协议。

    wishHappyBirthday函数的形参celebrator的类型为protocol<Named,Aged>。可以传入任意遵循这两个协议的类型的实例。

    上面的例子创建了一个名为birthdayPersonPerson实例,作为参数传递给了wishHappyBirthday(_:)函数。因为Person同时遵循这两个协议,所以这个参数合法,函数将输出生日问候语。

    注意
    协议合成并不会生成一个新协议类型,而是将多个协议合成为一个临时的协议,超出范围后立即失效。

    检验协议的一致性

    你可以使用isas操作符来检查是否遵循某一协议或强制转化为某一类型。检查和转化的语法和之前相同(详情查看类型转换):

    • is操作符用来检查实例是否遵循了某个协议
    • as?返回一个可选值,当实例遵循协议时,返回该协议类型;否则返回nil
    • as用以强制向下转型,如果强转失败,会引起运行时错误。

    下面的例子定义了一个HasArea的协议,要求有一个Double类型可读的area:

    protocol HasArea {
        var area: Double { get }
    }
    

    如下所示,定义了CircleCountry类,它们都遵循了HasArea协议

    class Circle: HasArea {
        let pi = 3.1415927
        var radius: Double
        var area: Double { return pi * radius * radius }
        init(radius: Double) { self.radius = radius }
    }
    class Country: HasArea {
        var area: Double
        init(area: Double) { self.area = area }
    }
    

    Circle类把area实现为基于存储型属性radius的计算型属性Country类则把area实现为存储型属性。这两个类都遵循HasArea协议。

    如下所示,Animal是一个没有实现HasArea协议的类

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

    CircleCountryAnimal并没有一个相同的基类,然而,它们都是类,它们的实例都可以作为AnyObject类型的变量,存储在同一个数组中:

    let objects: [AnyObject] = [
        Circle(radius: 2.0),
        Country(area: 243_610),
        Animal(legs: 4)
    ]
    

    objects数组使用字面量初始化,数组包含一个radius为2的Circle的实例,一个保存了英国面积的Country实例和一个legs为4的Animal实例。

    如下所示,objects数组可以被迭代,对迭代出的每一个元素进行检查,看它是否遵循了HasArea协议:

    for object in objects {
        if let objectWithArea = object as? HasArea {
            print("Area is \(objectWithArea.area)")
        } else {
            print("Something that doesn't have an area")
        }
    }
    // Area is 12.5663708
    // Area is 243610.0
    // Something that doesn't have an area
    

    当迭代出的元素遵循HasArea协议时,通过as?操作符将其可选绑定(optional binding)objectWithArea常量上。objectWithAreaHasArea协议类型的实例,因此area属性是可以被访问和打印的。

    objects数组中元素的类型并不会因为强转而丢失类型信息,它们仍然是CircleCountryAnimal类型。然而,当它们被赋值给objectWithArea常量时,则只被视为HasArea类型,因此只有area属性能够被访问。

    对可选协议的规定

    协议可以含有可选成员,其遵循者可以选择是否实现这些成员。在协议中使用optional关键字作为前缀来定义可选成员。

    可选协议在调用时使用可选链,因为协议的遵循者可能没有实现可选内容,详细内容在可空链式调用章节中查看。

    someOptionalMethod?(someArgument)这样,你可以在可选方法名称后加上?来检查该方法是否被实现。可选方法和可选属性都会返回一个可选值(optional value),当其不可访问时,?之后语句不会执行,并整体返回nil

    注意
    可选协议只能在含有@objc前缀的协议中生效。且@objc的协议只能被遵循
    这个前缀表示协议将暴露给Objective-C代码,详情参见Using Swift with Cocoa and Objective-C。即使你不打算和Objective-C有什么交互,如果你想要指明协议包含可选属性,那么还是要加上@obj前缀

    下面的例子定义了一个叫Counter的整数加法类,它使用外部的数据源来实现每次的增量。数据源是两个可选属性,在CounterDataSource协议中定义:

    @objc protocol CounterDataSource {
        optional func incrementForCount(count: Int) -> Int
        optional var fixedIncrement: Int { get }
    }
    

    CounterDataSource含有incrementForCount可选方法和fiexdIncrement可选属性,它们使用了不同的方法来从数据源中获取合适的增量值。

    注意
    CounterDataSource中的属性和方法都是可选的,因此可以在类中声明都不实现这些成员,尽管技术上允许这样做,不过最好不要这样写。

    Counter类含有CounterDataSource?类型的可选属性dataSource,如下所示:

    @objc class Counter {
        var count = 0
        var dataSource: CounterDataSource?
        func increment() {
            if let amount = dataSource?.incrementForCount?(count) {
                count += amount
            } else if let amount = dataSource?.fixedIncrement? {
                count += amount
            }
        }
    }
    

    Counter使用count来存储当前的值。该类同时定义了一个increment方法,每次调用该方法的时候,将会增加count的值。

    increment()方法首先试图使用incrementForCount(_:)方法来得到每次的增量。increment()方法使用可选链来尝试调用incrementForCount(_:),并将当前的count值作为参数传入。

    这里使用了两种可选链方法。由于dataSource可能为nil,因此在dataSource后边加上了?标记来表明只在dataSource非空时才去调用incrementForCount方法。即使dataSource存在,但是也无法保证其是否实现了incrementForCount方法,因此在incrementForCount方法后边也加有?标记。

    调用incrementForCount方法在上述两种情形都有可能失败,所以返回值为可选Int类型。虽然在CounterDataSource中,incrementForCount被定义为一个非可选Int(non-optional),但是这里我们仍然需要返回可选Int类型。

    在调用incrementForCount方法后,Int可选值通过可选绑定(optional binding)自动拆包并赋值给常量amount。如果可选值确实包含一个数值,这表示delegate和方法都存在,之后便将amount加到count上,增加操作完成。

    如果没有从incrementForCount(_:)获取到值,可能是dataSource为nil,或者它并没有实现incrementForCount方法——那么increment()方法将试图从数据源的fixedIncrement属性中获取增量。fixedIncrement也是一个可选型,所以在属性名的后面添加?来试图取回可选属性的值。和之前一样,返回值为可选型。

    ThreeSource实现了CounterDataSource协议,它实现来可选属性fixedIncrement,每次返回值3:

    @objc class ThreeSource: CounterDataSource {
        let fixedIncrement = 3
    }
    

    可以使用ThreeSource的实例作为Counter实例的数据源:

    var counter = Counter()
    counter.dataSource = ThreeSource()
    for _ in 1...4 {
        counter.increment()
        print(counter.count)
    }
    // 3
    // 6
    // 9
    // 12
    

    上述代码新建了一个Counter实例;将它的数据源设置为TreeSource实例;调用increment()4次。和你预想的一样,每次在调用的时候,count的值增加3.

    下面是一个更为复杂的数据源TowardsZeroSource,它将使得最后的值变为0:

    class TowardsZeroSource: CounterDataSource {
    func incrementForCount(count: Int) -> Int {
            if count == 0 {
                return 0
            } else if count < 0 {
                return 1
            } else {
                return -1
            }
        }
    }
    

    TowardsZeroSource实现了CounterDataSource协议中的incrementForCount(_:)方法,以count参数为依据,计算出每次的增量。如果count已经为0,方法返回0,这表示之后不会再有增量。

    你可以配合使用TowardsZeroSource实例和Counter实例来从-4增加到0.一旦增加到0,数值便不会再有变动。

    在下面的例子中,将从-4增加到0。一旦结果为0,便不在增加:

    counter.count = -4
    counter.dataSource = TowardsZeroSource()
    for _ in 1...5 {
        counter.increment()
        print(counter.count)
    }
    // -3
    // -2
    // -1
    // 0
    // 0
    

    协议扩展

    使用扩展协议的方式可以为遵循者提供方法或属性的实现。通过这种方式,可以让你无需在每个遵循者中都实现一次,无需使用全局函数,你可以通过扩展协议的方式进行定义。

    例如,可以扩展RandomNumberGenerator协议,让其提供randomBool()方法。该方法使用协议中要求的random()方法来实现:

    extension RandomNumberGenerator {
        func randomBool() -> Bool {
            return random() > 0.5
        }
    }
    

    通过扩展协议,所有协议的遵循者,在不用任何修改的情况下,都自动得到了这个扩展所增加的方法。

    let generator = LinearCongruentialGenerator()
    print("Here's a random number: \(generator.random())")
    // 输出 "Here's a random number: 0.37464991998171"
    print("And here's a random Boolean: \(generator.randomBool())")
    // 输出 "And here's a random Boolean: true"
    

    提供默认实现

    可以通过协议扩展的方式来为协议规定的属性和方法提供默认的实现。如果协议的遵循者对规定的属性和方法提供了自己的实现,那么遵循者提供的实现将被使用。

    注意
    通过扩展协议提供的协议实现和可选协议规定有区别。虽然协议遵循者无需自己实现,通过扩展提供的默认实现,可以不是用可选链调用。

    例如,PrettyTextRepresentable协议,继承了TextRepresentable协议,可以为其提供一个默认的asPrettyText()方法来简化返回值

    extension PrettyTextRepresentable  {
        func asPrettyText() -> String {
            return asText()
        }
    }
    

    为协议扩展添加限制条件

    在扩展协议的时候,可以指定一些限制,只有满足这些限制的协议遵循者,才能获得协议扩展提供的属性和方法。这些限制写在协议名之后,使用where关键字来描述限制情况。(Where语句)。:

    例如,你可以扩展CollectionType协议,但是只适用于元素遵循TextRepresentable的情况:

    extension CollectionType where Generator.Element : TextRepresentable {
        func asList() -> String {
            return "(" + ", ".join(map({$0.asText()})) + ")"
        }
    }
    

    asList()方法将每个元素以asText()的方式表示,最后以逗号分隔链接起来。

    现在我们来看Hamster,它遵循TextRepresentable:

    let murrayTheHamster = Hamster(name: "Murray")
    let morganTheHamster = Hamster(name: "Morgan")
    let mauriceTheHamster = Hamster(name: "Maurice")
    let hamsters = [murrayTheHamster, morganTheHamster, mauriceTheHamster]
    

    因为Array遵循CollectionType协议,数组的元素又遵循TextRepresentable协议,所以数组可以使用asList()方法得到数组内容的文本表示:

    print(hamsters.asList())
    // 输出 "(A hamster named Murray, A hamster named Morgan, A hamster named Maurice)"
    

    注意
    如果有多个协议扩展,而一个协议的遵循者又同时满足它们的限制,那么将会使用所满足限制最多的那个扩展。

    展开全文
  • Introducing Swift - Protocols协议语法// 定义 protocol SomeProtocol { // something ... }// 使用 class SomeClass: SomeSuperClass, SomeProtocol { // protocol implementation ... }属性要求协议可以用于...

    Introducing Swift - Protocols

    本文主要记录在学习Swift中【协议】时的笔记。


    协议语法

    // 定义
    protocol SomeProtocol {
        // something ... 
    }
    
    // 使用
    class SomeClass: SomeSuperClass, SomeProtocol {
        // protocol implementation ...
    }

    属性要求

    协议可以用于要求其遵循者必须具有某些属性,可以要求属性的读写权限,但是不会要求属性是存储型还是计算型。

    通过class关键字要求一个类必须具有的实体,通过static关键字要求一个结构体或者枚举必须具有的实体。


    方法要求

    协议可以用于要求其遵循者必须实现某些实例方法和类方法。和类中定义方法不同的是,协议方法支持变长参数,不支持默认参数。

    通过class关键字要求一个类必须具有的实体,通过static关键字要求一个结构体或者枚举必须具有的实体。

    如果协议中声明的一个方法需要去改变类或者结构体或者枚举中的某一个属性,那么需要将其声明为mutating方法。用class实现协议中的mutating方法时,不用写mutating关键字;用结构体,枚举实现协议中的mutating方法时,必须写mutating关键字。


    委托(代理)模式

    委托是一种设计模式,它允许一个对象将某些特定的操作交由其代理来实现。我们通过协议来声明这些操作,然后由代理对象去实现这些操作。和OC不同的是,我们有了更优雅的写法:

    delegate?.doSomething(param)

    当delegate为空时,后边的语句会优雅的忽略~


    在扩展中使用协议

    extension SomeClass: SomeProtocol {
        // protocol implementation ...
    }

    如果一个类已经实现了某一个协议的方法,但是没有声明,我们也可以通过扩展来声明:

    extension SomeClass: SomeProtocol {}

    之所以要这样做,是因为即使类已经满足了协议所要求的属性和方法,但是类型也不会自动转变,如果不声明,这个类依旧不是一个遵循了特定协议的类。


    协议的继承

    协议支持多继承,类仅支持单继承。


    协议一致性检验

    1. 使用is检查实例是否遵循了某个协议;
    2. 使用as?返回一个可选值,如果实例遵循了协议,返回协议类型,否则返回nil;
    3. 使用as用于强制向下换型。
    4. @objc用来表示协议是可选的(同时在相关方法和属性前面加上@optional关键字),也可以用于表示暴露给OC的代码,此外,@objc型协议只对类有效,因此只能在类中检查协议的一致性。
    展开全文
  • 简要协议是苹果给它自己设计的语言的一种类型,Swift和OC都叫协议,它的功能更Java中的接口非常类似,其实就是Java中的接口功能,就是名称不一样而已,我们可以类比来学,但是有一点,Java中没有扩展,所以Java中的...

    简要

    协议是苹果给它自己设计的语言的一种类型,Swift和OC都叫协议,它的功能更Java中的接口非常类似,其实就是Java中的接口功能,就是名称不一样而已,我们可以类比来学,但是有一点,Java中没有扩展,所以Java中的接口也是不可以扩展的,但是Swift可以。

    协议语法

    协议使用protocol关键字来定义:

    protocol SomeProtocol {
        // 这里是协议的定义部分
    }

    Java中的接口使用interface关键字来定义:

    public interface SomeInterface {
    
    }

    Swift中类、结构体或枚举都可以采纳协议,需要在类型名称后加上协议名称,中间以冒号(:)分隔,采纳多个协议时,各协议之间用逗号(,)分隔。Java中,只有类能够实现接口,类型名称后加上接口名称,中间以implements关键字分隔,我们看下例子:
    Swift:

    class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
        // 这里是类的定义部分
    }

    Java:

    public class SomeObject implements SomeInterface,SomeInterface1 {
        // 这里是类的定义部分
    }

    属性要求

    协议要求采纳协议的类型提供特定名称和类型的实例属性或类型属性。协议本身不指定属性是存储型属性还是计算型属性,它只指定属性的名称和类型,具体是哪种类型由采纳协议的类型自己去确定,此外,协议还指定属性是可读的还是可读可写的。

    如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的。
    协议总是用 var 关键字来声明变量属性,在类型声明后加上 { set get } 来表示属性是可读可写的,可读属性则用 { get } 来表示:

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

    在协议中定义类型属性时,总是使用 static 关键字作为前缀。当类类型采纳协议时,除了 static 关键字,还可以使用 class 关键字来声明类型属性:

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

    Java中没有属性这个概念,类似于Swift的属性有个字段的概念,一般来说字段是private的,接口中不允许使用私有的字段,不过可以在接口中定义一些静态公共的常量,即用public static final修饰的字段。

    方法要求

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

    正如属性要求中所述,在协议中定义类方法的时候,总是使用 static 关键字作为前缀。当类类型采纳协议时,除了 static 关键字,还可以使用 class 关键字作为前缀:

    protocol SomeProtocol {
        static func someTypeMethod()
    }

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

    protocol RandomNumberGenerator {
        func random() -> Double
    }

    Java8以下的版本中,接口中不允许有方法的实现,但是在Java8及其以上的版本中,接口可以有方法的实现,Swift不允许在协议中有方法的实现,必须为空方法,但是可以在扩展中提供默认方法的实现,在采纳协议的类型中,可以直接调用。
    协议可以当做像Swift中其他类型来使用了,有Java开发经验的同学应该知道怎么用了,其他方面都是一些语法糖,像协议的合成,类类型专属协议,可选的协议要求,我们看一下就知道,协议的继承跟类的继承是一样的,用冒号分开,很简单。

    展开全文
  • Swift5 14.Protocols

    2020-07-19 16:58:54
    目录Protocols Protocols
  • Swift 4 is an incremental release of Swift: a tock in Apple’s usual tick-tock release cycle, where the tick is a major release and the tock is a follow-up release with bug fixes and some additions ...
  • 一、协议 | Protocols  协议用于定义完成某些功能所需要的方法和属性,协议本身并不提供这些功能的具体实现,只是用来描述这些实现。类、结构体、枚举通过提供协议所要求的方法、属性的具体实现来采用协议。能够...
  • Learn Swift by Building Applications: Explore Swift programming through iOS app development by Emil Atanasov Packt Publishing English 2018-05-25 366 pages 5.0/5.0 1 reviews Details Title: Learn Swift...
  • swift enum高级用法

    2019-07-20 13:09:17
    本文是一篇详细且具有实战意义的教程,涵盖几乎所有枚举(Enum)知识点,为你解答Swift中枚举的应用场合以及使用方法。 和switch语句类似,Swift中的枚举乍看之下更像是C语言中枚举的进阶版本,即允许你定义一种类型...
  • Swift3.0 类型检查

    2016-10-09 10:47:08
    类型检查在 Swift 中使用is 和 as操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。你也可以用来检查一个类是否实现了某个协议,就像在 Protocols Checking for
  • Swift学习笔记20——协议(Protocols
  • 1.0 翻译:geek5nan校对:dabing1022 2.0 翻译:futantan校对:小铁匠Linus定稿:shanksyang 本页包含内容: 协议的语法(Protocol Syntax) ...对属性的规定(Property Requirements) ...对方法的规定(Method ...
  • 本篇主要介绍UIView里的delegate 和 protocol的一个简单的example和如何去使用 首先我们先做一个简单的UIView,在ViewController里有一个去到DetailViewController的button,在DetailView里我们有两个Button,一个是...
  • weak references don't seem to work in Swift unless a protocol is declared as @objc , which I don't
  • Swift 4.2是Swift 4的第二次小更新,随之带来了很多很棒的改进-这使得今年将成为Swift的重要一年,并且更加确认这个社区驱动的Swift演变进程正在让一个伟大的语言变得更好。这次我们获得了一些特性比如enum case ...
  • Swift 版本历史记录

    2017-04-20 16:24:30
    XCode6.4 Beta Swift语法文档更新XCode6.3正式版 Swift语法文档更新XCode6.2正式版 Swift语法文档更新XCode6.2 Beta3 Swift语法文档更新XCode6.2 Beta2 Swift语法文档更新XCode6.2 Beta1 Swift语法文档更新XCode...
  • 【ios】swift教程

    2019-06-13 20:38:23
    学习swift地址 https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html 在wwdc2019上看到了苹果公司关于swiftui的介绍,感觉这就是未来。 Swift编程语言简介 Swift命令行操作 Swift defines away large ...
  • Embrace the Protocol-Oriented Programming paradigm, for better code maintainability and increased performance, with Swift programming. Protocol-oriented programming is an incredibly powerful concept ...
1 2 3 4 5 ... 20
收藏数 1,366
精华内容 546
热门标签
关键字:

protocols swift