Protocols (协议下)

作者: 金旭峰 | 来源:发表于2017-01-21 18:09 被阅读19次

    Declaring Protocol Adoption with an Extension (通过扩展遵循协议)

    If a type already conforms to all of the requirements of a protocol, but has not yet stated that it adopts that protocol, you can make it adopt the protocol with an empty extension:

    当一个类型已经符合了某个协议中的所有要求,却还没有声明遵循该协议时,可以通过空扩展体的扩展来遵循该协议:

    struct Hamster{

    var name:String

    var textualDescription:String{

    return "A hamster named\(name)"

    }

    }

    extension Hamster:TextRepresentable{}

    Instances ofHamstercan now be used whereverTextRepresentableis the required type:

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

    let simonTheHamster=Hamster(name:"Simon")

    let somethingTextRepresentable:TextRepresentable=simonTheHamster

    print(somethingTextRepresentable.textualDescription)

    // Prints "A hamster named Simon"

    NOTE

    Types do not automatically adopt a protocol just by satisfying its requirements. They must always explicitly declare their adoption of the protocol.

    即使满足了协议的所有要求,类型也不会自动遵循协议,必须显式地遵循协议。

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

    A protocol can be used as the type to be stored in a collection such as an array or a dictionary, as mentioned inProtocols as Types. This example creates an array ofTextRepresentablethings:

    协议类型可以在数组或者字典这样的集合中使用,在协议类型提到了这样的用法。下面的例子创建了一个元素类型为TextRepresentable的数组:

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

    It is now possible to iterate over the items in the array, and print each item’s textual description:

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

    for thing in things{

    print(thing.textualDescription)

    }

    // A game of Snakes and Ladders with 25 squares

    // A 12-sided dice

    // A hamster named Simon

    Note that thethingconstant is of typeTextRepresentable. It is not of typeDice, orDiceGame, orHamster, even if the actual instance behind the scenes is of one of those types. Nonetheless, because it is of typeTextRepresentable, and anything that isTextRepresentableis known to have atextualDescriptionproperty, it is safe to accessthing.textualDescriptioneach time through the loop.

    thing是TextRepresentable类型而不是Dice,DiceGame,Hamster等类型,即使实例在幕后确实是这些类型中的一种。由于thing是TextRepresentable类型,任何TextRepresentable的实例都有一个textualDescription属性,所以在每次循环中可以安全地访问thing.textualDescription。

    Protocol Inheritance (协议的继承)

    A protocol caninheritone or more other protocols and can add further requirements on top of the requirements it inherits. The syntax for protocol inheritance is similar to the syntax for class inheritance, but with the option to list multiple inherited protocols, separated by commas:

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

    protocol InheritingProtocol: SomeProtocol, AnotherProtocol{

    // protocol definition goes here

    }

    Here’s an example of a protocol that inherits theTextRepresentableprotocol from above:

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

    protocol PrettyTextRepresentable:TextRepresentable{

    var prettyTextualDescription:String{get}

    }

    This example defines a new protocol,PrettyTextRepresentable, which inherits fromTextRepresentable. Anything that adoptsPrettyTextRepresentablemust satisfy all of the requirements enforced byTextRepresentable,plusthe additional requirements enforced byPrettyTextRepresentable. In this example,PrettyTextRepresentableadds a single requirement to provide a gettable property calledprettyTextualDescriptionthat returns aString.

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

    TheSnakesAndLaddersclass can be extended to adopt and conform toPrettyTextRepresentable:

    如下所示,扩展SnakesAndLadders,使其遵循并符合PrettyTextRepresentable协议:

    extension SnakesAndLadders:PrettyTextRepresentable{

    var prettyTextualDescription:String{

    var output=textualDescription+":\n"

    for index in1...finalSquare{

    switch board[index] {

    case let ladder where ladder>0:

    output+="▲ "

    case let snake where snake<0:

    output+="▼ "

    default:

    output+="○ "

    }

    }

    return output

    }

    }

    This extension states that it adopts thePrettyTextRepresentableprotocol and provides an implementation of theprettyTextualDescriptionproperty for theSnakesAndLadderstype. Anything that isPrettyTextRepresentablemust also beTextRepresentable, and so the implementation ofprettyTextualDescriptionstarts by accessing thetextualDescriptionproperty from theTextRepresentableprotocol to begin an output string. It appends a colon and a line break, and uses this as the start of its pretty text representation. It then iterates through the array of board squares, and appends a geometric shape to represent the contents of each square:

    上述扩展令SnakesAndLadders遵循了PrettyTextRepresentable协议,并提供了协议要求的prettyTextualDescription属性。每个PrettyTextRepresentable类型同时也是TextRepresentable类型,所以在prettyTextualDescription的实现中,可以访问textualDescription属性。然后,拼接上了冒号和换行符。接着,遍历数组中的元素,拼接一个几何图形来表示每个棋盘方格的内容:

    1. If the square’s value is greater than0, it is the base of a ladder, and is represented by▲.

    当从数组中取出的元素的值大于0时,用▲表示。

    2. If the square’s value is less than0, it is the head of a snake, and is represented by▼.

    当从数组中取出的元素的值小于0时,用▼表示。

    3. Otherwise, the square’s value is0, and it is a “free” square, represented by○.

    当从数组中取出的元素的值等于0时,用○表示。

    The prettyTextualDescriptionproperty can now be used to print a pretty text description of anySnakesAndLaddersinstance:

    任意SankesAndLadders的实例都可以使用prettyTextualDescription属性来打印一个漂亮的文本描述:

    print(game.prettyTextualDescription)

    // A game of Snakes and Ladders with 25 squares:

    // ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○

    Class-Only Protocols (类类型专属协议)

    You can limit protocol adoption to class types (and not structures or enumerations) by adding theclasskeyword to a protocol’s inheritance list. Theclasskeyword must always appear first in a protocol’s inheritance list, before any inherited protocols:

    你可以在协议的继承列表中,通过添加class关键字来限制协议只能被类类型遵循,而结构体或枚举不能遵循该协议。class关键字必须第一个出现在协议的继承列表中,在其他继承的协议之前:

    protocol SomeClassOnlyProtocol:class,SomeInheritedProtocol{

    // class-only protocol definition goes here

    }

    In the example above,SomeClassOnlyProtocolcan only be adopted by class types. It is a compile-time error to write a structure or enumeration definition that tries to adoptSomeClassOnlyProtocol.

    在以上例子中,协议SomeClassOnlyProtocol只能被类类型遵循。如果尝试让结构体或枚举类型遵循该协议,则会导致编译错误。

    NOTE

    Use a class-only protocol when the behavior defined by that protocol’s requirements assumes or requires that a conforming type has reference semantics rather than value semantics. For more on reference and value semantics, seeStructures and Enumerations Are Value TypesandClasses Are Reference Types.

    当协议定义的要求需要遵循协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。关于引用语义和值语义的更多内容,请查看结构体和枚举是值类型类是引用类型

    Protocol Composition (协议合成)

    It can be useful to require a type to conform to multiple protocols at once. You can combine multiple protocols into a single requirement with aprotocol composition. Protocol compositions have the formSomeProtocol & AnotherProtocol. You can list as many protocols as you need to, separating them by ampersands (&).

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

    Here’s an example that combines two protocols calledNamedandAgedinto a single protocol composition requirement on a function parameter:

    下面的例子中,将Named和Aged两个协议按照上述语法组合成一个协议,作为函数参数的类型:

    protocol Named{

    var name:String{get}

    }

    protocol Aged{

    var age:Int{get}

    }

    struct Person:Named,Aged{

    var name:String

    var age:Int

    }

    func wishHappyBirthday(tocelebrator:Named&Aged) {

    print("Happy birthday,\(celebrator.name), you're\(celebrator.age)!")

    }

    let birthdayPerson=Person(name:"Malcolm",age:21)

    wishHappyBirthday(to:birthdayPerson)

    // Prints "Happy birthday, Malcolm, you're 21!"

    This example defines a protocol calledNamed, with a single requirement for a gettableStringproperty calledname. It also defines a protocol calledAged, with a single requirement for a gettableIntproperty calledage. Both of these protocols are adopted by a structure calledPerson.

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

    The example also defines awishHappyBirthday(to:)function, The type of thecelebratorparameter isNamed & Aged, which means “any type that conforms to both theNamedandAgedprotocols.” It doesn’t matter what specific type is passed to the function, as long as it conforms to both of the required protocols.

    wishHappyBirthday(to:)函数的参数celebrator的类型为Named & Aged。这意味着它不关心参数的具体类型,只要参数符合这两个协议即可。

    The example then creates a newPersoninstance calledbirthdayPersonand passes this new instance to thewishHappyBirthday(to:)function. BecausePersonconforms to both protocols, this is a valid call, and thewishHappyBirthday(to:)function is able to print its birthday greeting.

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

    NOTE

    Protocol compositions do not define a new, permanent protocol type. Rather, they define a temporary local protocol that has the combined requirements of all protocols in the composition.

    协议合成并不会生成新的、永久的协议类型,而是将多个协议中的要求合成到一个只在局部作用域有效的临时协议中。

    Checking for Protocol Conformance (检查协议一致性)

    You can use theisandasoperators described inType Castingto check for protocol conformance, and to cast to a specific protocol. Checking for and casting to a protocol follows exactly the same syntax as checking for and casting to a type:

    你可以使用类型转换中描述的is和as操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同:

    1. The is operator returnstrueif an instance conforms to a protocol and returnsfalseif it does not.

    is用来检查实例是否符合某个协议,若符合则返回true,否则返回false。

    2. The as?version of the downcast operator returns an optional value of the protocol’s type, and this value isnilif the instance does not conform to that protocol.

    as?返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回nil。

    3. The as!version of the downcast operator forces the downcast to the protocol type and triggers a runtime error if the downcast does not succeed.

    as!将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。

    This example defines a protocol calledHasArea, with a single property requirement of a gettableDoubleproperty calledarea:

    下面的例子定义了一个HasArea协议,该协议定义了一个Double类型的可读属性area:

    protocol HasArea{

    var area:Double{get}

    }

    Here are two classes,CircleandCountry, both of which conform to theHasAreaprotocol:

    如下所示,Circle类和Country类都遵循了HasArea协议:

    class Circle:HasArea{

    let pi=3.1415927

    var radius:Double

    var area:Double{returnpi*radius*radius}

    init(radius:Double) {self.radius=radius}

    }

    class Country:HasArea{

    var area:Double

    init(area:Double) {self.area=area}

    }

    TheCircleclass implements theareaproperty requirement as a computed property, based on a storedradiusproperty. TheCountryclass implements thearearequirement directly as a stored property. Both classes correctly conform to theHasAreaprotocol.

    Circle类把area属性实现为基于存储型属性radius的计算型属性。Country类则把area属性实现为存储型属性。这两个类都正确地符合了HasArea协议。

    Here’s a class calledAnimal, which does not conform to theHasAreaprotocol:

    如下所示,Animal是一个未遵循HasArea协议的类:

    class Animal{

    var legs:Int

    init(legs:Int) {self.legs=legs}

    }

    TheCircle,CountryandAnimalclasses do not have a shared base class. Nonetheless, they are all classes, and so instances of all three types can be used to initialize an array that stores values of typeAnyObject:

    Circle,Country,Animal并没有一个共同的基类,尽管如此,它们都是类,它们的实例都可以作为AnyObject类型的值,存储在同一个数组中:

    let objects: [AnyObject] = [

    Circle(radius:2.0),

    Country(area:243_610),

    Animal(legs:4)

    ]

    Theobjectsarray is initialized with an array literal containing aCircleinstance with a radius of 2 units; aCountryinstance initialized with the surface area of the United Kingdom in square kilometers; and anAnimalinstance with four legs.

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

    Theobjectsarray can now be iterated, and each object in the array can be checked to see if it conforms to theHasAreaprotocol:

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

    for object in objects{

    if let objectWithArea=objectas?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

    Whenever an object in the array conforms to theHasAreaprotocol, the optional value returned by theas?operator is unwrapped with optional binding into a constant calledobjectWithArea. TheobjectWithAreaconstant is known to be of typeHasArea, and so itsareaproperty can be accessed and printed in a type-safe way.

    当迭代出的元素符合HasArea协议时,将as?操作符返回的可选值通过可选绑定,绑定到objectWithArea常量上。objectWithArea是HasArea协议类型的实例,因此area属性可以被访问和打印。

    Note that the underlying objects are not changed by the casting process. They continue to be aCircle, aCountryand anAnimal. However, at the point that they are stored in theobjectWithAreaconstant, they are only known to be of typeHasArea, and so only theirareaproperty can be accessed.

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

    Optional Protocol Requirements (可选的协议要求)

    You can defineoptional requirementsfor protocols, These requirements do not have to be implemented by types that conform to the protocol. Optional requirements are prefixed by theoptionalmodifier as part of the protocol’s definition. Optional requirements are available so that you can write code that interoperates with Objective-C. Both the protocol and the optional requirement must be marked with the@objcattribute. Note that@objcprotocols can be adopted only by classes that inherit from Objective-C classes or other@objcclasses. They can’t be adopted by structures or enumerations.

    协议可以定义可选要求,遵循协议的类型可以选择是否实现这些要求。在协议中使用optional关键字作为前缀来定义可选要求。可选要求用在你需要和 Objective-C 打交道的代码中。协议和可选要求都必须带上@objc属性。标记@objc特性的协议只能被继承自 Objective-C 类的类或者@objc类遵循,其他类以及结构体和枚举均不能遵循这种协议。

    When you use a method or property in an optional requirement, its type automatically becomes an optional. For example, a method of type(Int) -> Stringbecomes((Int) -> String)?. Note that the entire function type is wrapped in the optional, not the method’s return value.

    使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为(Int) -> String的方法会变成((Int) -> String)?。需要注意的是整个函数类型是可选的,而不是函数的返回值。

    An optional protocol requirement can be called with optional chaining, to account for the possibility that the requirement was not implemented by a type that conforms to the protocol. You check for an implementation of an optional method by writing a question mark after the name of the method when it is called, such assomeOptionalMethod?(someArgument). For information on optional chaining, seeOptional Chaining.

    协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。类似someOptionalMethod?(someArgument)这样,你可以在可选方法名称后加上?来调用可选方法。详细内容可在可选链式调用章节中查看。

    The following example defines an integer-counting class calledCounter, which uses an external data source to provide its increment amount. This data source is defined by theCounterDataSourceprotocol, which has two optional requirements:

    下面的例子定义了一个名为Counter的用于整数计数的类,它使用外部的数据源来提供每次的增量。数据源由CounterDataSource协议定义,包含两个可选要求:

    @objc protocol CounterDataSource{

    @objc optional func increment(forCountcount:Int) ->Int

    @objc optional var fixedIncrement:Int{get}

    }

    TheCounterDataSourceprotocol defines an optional method requirement calledincrement(forCount:)and an optional property requirement calledfixedIncrement. These requirements define two different ways for data sources to provide an appropriate increment amount for aCounterinstance.

    CounterDataSource协议定义了一个可选方法increment(forCount:)和一个可选属性fiexdIncrement,它们使用了不同的方法来从数据源中获取适当的增量值。

    NOTE

    Strictly speaking, you can write a custom class that conforms toCounterDataSourcewithout implementingeitherprotocol requirement. They are both optional, after all. Although technically allowed, this wouldn’t make for a very good data source.

    严格来讲,CounterDataSource协议中的方法和属性都是可选的,因此遵循协议的类可以不实现这些要求,尽管技术上允许这样做,不过最好不要这样写。

    TheCounterclass, defined below, has an optionaldataSourceproperty of typeCounterDataSource?:

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

    class Counter{

    var count=0

    var dataSource:CounterDataSource?

    func increment() {

    if let amount=dataSource?.increment?(forCount:count) {

    count+=amount

    } else if let amount=dataSource?.fixedIncrement{

    count+=amount

    }

    }

    }

    TheCounterclass stores its current value in a variable property calledcount. TheCounterclass also defines a method calledincrement, which increments thecountproperty every time the method is called.

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

    Theincrement()method first tries to retrieve an increment amount by looking for an implementation of theincrement(forCount:)method on its data source. Theincrement()method uses optional chaining to try to callincrement(forCount:), and passes the currentcountvalue as the method’s single argument.

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

    Note thattwolevels of optional chaining are at play here. First, it is possible thatdataSourcemay benil, and sodataSourcehas a question mark after its name to indicate thatincrement(forCount:)should be called only ifdataSourceisn’tnil. Second, even ifdataSourcedoesexist, there is no guarantee that it implementsincrement(forCount:), because it is an optional requirement. Here, the possibility thatincrement(forCount:)might not be implemented is also handled by optional chaining. The call toincrement(forCount:)happens only ifincrement(forCount:)exists—that is, if it isn’tnil. This is whyincrement(forCount:)is also written with a question mark after its name.

    这里使用了两层可选链式调用。首先,由于dataSource可能为nil,因此在dataSource后边加上了?,以此表明只在dataSource非空时才去调用increment(forCount:)方法。其次,即使dataSource存在,也无法保证其是否实现了increment(forCount:)方法,因为这个方法是可选的。因此,increment(forCount:)方法同样使用可选链式调用进行调用,只有在该方法被实现的情况下才能调用它,所以在increment(forCount:)方法后边也加上了?。

    Because the call toincrement(forCount:)can fail for either of these two reasons, the call returns anoptionalIntvalue. This is true even thoughincrement(forCount:)is defined as returning a nonoptionalIntvalue in the definition ofCounterDataSource. Even though there are two optional chaining operations, one after another, the result is still wrapped in a single optional. For more information about using multiple optional chaining operations, seeLinking Multiple Levels of Chaining.

    调用increment(forCount:)方法在上述两种情形下都有可能失败,所以返回值为Int?类型。虽然在CounterDataSource协议中,increment(forCount:)的返回值类型是非可选Int。另外,即使这里使用了两层可选链式调用,最后的返回结果依旧是单层的可选类型。关于这一点的更多信息,请查阅连接多层可选链式调用.

    After callingincrement(forCount:), the optionalIntthat it returns is unwrapped into a constant calledamount, using optional binding. If the optionalIntdoes contain a value—that is, if the delegate and method both exist, and the method returned a value—the unwrappedamountis added onto the storedcountproperty, and incrementation is complete.

    在调用increment(forCount:)方法后,Int?型的返回值通过可选绑定解包并赋值给常量amount。如果可选值确实包含一个数值,也就是说,数据源和方法都存在,数据源方法返回了一个有效值。之后便将解包后的amount加到count上,增量操作完成。

    If it isnotpossible to retrieve a value from theincrement(forCount:)method—either becausedataSourceis nil, or because the data source does not implementincrement(forCount:)—then theincrement()method tries to retrieve a value from the data source’sfixedIncrementproperty instead. ThefixedIncrementproperty is also an optional requirement, so its value is an optionalIntvalue, even thoughfixedIncrementis defined as a nonoptionalIntproperty as part of theCounterDataSourceprotocol definition.

    如果没有从increment(forCount:)方法获取到值,可能由于dataSource为nil,或者它并没有实现increment(forCount:)方法,那么increment()方法将试图从数据源的fixedIncrement属性中获取增量。fixedIncrement是一个可选属性,因此属性值是一个Int?值,即使该属性在CounterDataSource协议中的类型是非可选的Int。

    Here’s a simpleCounterDataSourceimplementation where the data source returns a constant value of3every time it is queried. It does this by implementing the optionalfixedIncrementproperty requirement:

    下面的例子展示了CounterDataSource的简单实现。ThreeSource类遵循了CounterDataSource协议,它实现了可选属性fixedIncrement,每次会返回3:

    class ThreeSource:NSObject,CounterDataSource{

    let fixedIncrement=3

    }

    You can use an instance ofThreeSourceas the data source for a newCounterinstance:

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

    var counter=Counter()

    counter.dataSource=ThreeSource()

    for_in1...4{

    counter.increment()

    print(counter.count)

    }

    // 3

    // 6

    // 9

    // 12

    The code above creates a newCounterinstance; sets its data source to be a newThreeSourceinstance; and calls the counter’sincrement()method four times. As expected, the counter’scountproperty increases by three each timeincrement()is called.

    上述代码新建了一个Counter实例,并将它的数据源设置为一个ThreeSource的实例,然后调用increment()方法四次。和预期一样,每次调用都会将count的值增加3.

    Here’s a more complex data source calledTowardsZeroSource, which makes aCounterinstance count up or down towards zero from its currentcountvalue:

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

    @objc classTowardsZeroSource:NSObject,CounterDataSource{

    func increment(forCountcount:Int) ->Int{

    if count==0{

    return 0

    }else if count<0{

    return 1

    }else{

    return-1

    }

    }

    }

    TheTowardsZeroSourceclass implements the optionalincrement(forCount:)method from theCounterDataSourceprotocol and uses thecountargument value to work out which direction to count in. Ifcountis already zero, the method returns0to indicate that no further counting should take place.

    TowardsZeroSource实现了CounterDataSource协议中的increment(forCount:)方法,以count参数为依据,计算出每次的增量。如果count已经为0,此方法返回0,以此表明之后不应再有增量操作发生。

    You can use an instance ofTowardsZeroSourcewith the existingCounterinstance to count from-4to zero. Once the counter reaches zero, no more counting takes place:

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

    counter.count=-4

    counter.dataSource=TowardsZeroSource()

    for_in1...5{

    counter.increment()

    print(counter.count)

    }

    // -3

    // -2

    // -1

    // 0

    // 0

    Protocol Extensions (协议扩展)

    Protocols can be extended to provide method and property implementations to conforming types. This allows you to define behavior on protocols themselves, rather than in each type’s individual conformance or in a global function.

    协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个遵循协议的类型中都重复同样的实现,也无需使用全局函数。

    For example, theRandomNumberGeneratorprotocol can be extended to provide arandomBool()method, which uses the result of the requiredrandom()method to return a randomBoolvalue:

    例如,可以扩展RandomNumberGenerator协议来提供randomBool()方法。该方法使用协议中定义的random()方法来返回一个随机的Bool值:

    extension RandomNumberGenerator{

    func randomBool() ->Bool{

    return random() >0.5

    }

    }

    By creating an extension on the protocol, all conforming types automatically gain this method implementation without any additional modification.

    通过协议扩展,所有遵循协议的类型,都能自动获得这个扩展所增加的方法实现,无需任何额外修改:

    let generator=LinearCongruentialGenerator()

    print("Here's a random number:\(generator.random())")

    // Prints "Here's a random number: 0.37464991998171"

    print("And here's a random Boolean:\(generator.randomBool())")

    // Prints "And here's a random Boolean: true"

    Providing Default Implementations (提供默认实现)

    You can use protocol extensions to provide a default implementation to any method or computed property requirement of that protocol. If a conforming type provides its own implementation of a required method or property, that implementation will be used instead of the one provided by the extension.

    可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。

    NOTE

    Protocol requirements with default implementations provided by extensions are distinct from optional protocol requirements. Although conforming types don’t have to provide their own implementation of either, requirements with default implementations can be called without optional chaining.

    通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然在这两种情况下,遵循协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用。

    For example, thePrettyTextRepresentableprotocol, which inherits theTextRepresentableprotocol can provide a default implementation of its requiredprettyTextualDescriptionproperty to simply return the result of accessing thetextualDescriptionproperty:

    例如,PrettyTextRepresentable协议继承自TextRepresentable协议,可以为其提供一个默认的prettyTextualDescription属性,只是简单地返回textualDescription属性的值:

    extension PrettyTextRepresentable{

    var prettyTextualDescription:String{

    return textualDescription

    }

    }

    Adding Constraints to Protocol Extensions (为协议扩展添加限制条件)

    When you define a protocol extension, you can specify constraints that conforming types must satisfy before the methods and properties of the extension are available. You write these constraints after the name of the protocol you’re extending using a genericwhereclause, as described inGeneric Where Clauses.

    在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用where子句来描述,正如Where子句中所描述的。

    For instance, you can define an extension to theCollectionprotocol that applies to any collection whose elements conform to theTextRepresentableprotocol from the example above.

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

    extension Collection where Iterator.Element:TextRepresentable{

    var textualDescription:String{

    let itemsAsText=self.map{$0.textualDescription}

    return "["+itemsAsText.joined(separator:", ") +"]"

    }

    }

    ThetextualDescriptionproperty returns the textual description of the entire collection by concatenating the textual representation of each element in the collection into a comma-separated list, enclosed in brackets.

    textualDescription属性返回整个集合的文本描述,它将集合中的每个元素的文本描述以逗号分隔的方式连接起来,包在一对方括号中。

    Consider theHamsterstructure from before, which conforms to theTextRepresentableprotocol, and an array ofHamstervalues:

    现在我们来看看先前的Hamster结构体,它符合TextRepresentable协议,同时这里还有个装有Hamster的实例的数组:

    let murrayTheHamster=Hamster(name:"Murray")

    let morganTheHamster=Hamster(name:"Morgan")

    let mauriceTheHamster=Hamster(name:"Maurice")

    let hamsters= [murrayTheHamster,morganTheHamster,mauriceTheHamster]

    BecauseArrayconforms toCollectionand the array’s elements conform to theTextRepresentableprotocol, the array can use thetextualDescriptionproperty to get a textual representation of its contents:

    因为Array符合CollectionType协议,而数组中的元素又符合TextRepresentable协议,所以数组可以使用textualDescription属性得到数组内容的文本表示:

    print(hamsters.textualDescription)

    // Prints "[A hamster named Murray, A hamster named Morgan, A hamster named Maurice]"

    NOTE

    If a conforming type satisfies the requirements for multiple constrained extensions that provide implementations for the same method or property, Swift will use the implementation corresponding to the most specialized constraints.

    如果多个协议扩展都为同一个协议要求提供了默认实现,而遵循协议的类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多的那个协议扩展提供的默认实现。

    相关文章

      网友评论

        本文标题:Protocols (协议下)

        本文链接:https://www.haomeiwen.com/subject/jnhnbttx.html