Swift 协议(Protocols译文)

2,820 阅读7分钟
**协议是**为类、结构和枚举定义的方法、属性和其它需求的**蓝图,**可以**通过扩展**实现协议或是**通过扩展**添加额外的功能。

you can extend a protocol to implement some of these requirements or to implement additional functionality that conforming types can take advantage of.

语法

protocol SomeProtocol {
    // protocol definition goes here
}
struct SomeStructure: FirstProtocol, AnotherProtocol {
    // structure definition goes here
}
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
    // class definition goes here
}

协议属性

任何实现协议的类型,都需要为协议提供带有名称和类型的实例属性或类型属性,并限定读写权限,协议本身不会限定属性是存储属性还是计算属性。

var poperty_name: type { get or set }

实例属性

协议 SomeProtocol 定义了两个实例属性,读写属性和只读属性,用 var 声明为变量,: 

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

let 在属性限定中不可用

协议 FullyNamed 定义了读写 fullName 属性,结构 Person 实现了该协议,系统会自动为期生产 getter 方法和 setter 方法:

protocol FullyNamed {
    var fullName: String { get set }
}struct Person: FullyNamed {
    var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName is "John Appleseed"

类型属性

在实例属性限定的基础上加上 static 修饰符,定义类型属性:

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

协议方法

协议中可定义实例方法和类型方法,支持可变参数。

但是无法在协议方法中指定参数的默认值

类型方法

同类型属性,使用 static 定义类型方法

protocol SomeProtocol {
    static func someTypeMethod()
}

实例方法

protocol RandomNumberGenerator {
    func random() -> Double
}
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)
            .truncatingRemainder(dividingBy:m))
        return lastRandom / m
    }
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// Prints "Here's a random number: 0.3746499199817101"
print("And another one: \(generator.random())")
// Prints "And another one: 0.729023776863283"

值类型的可变实例方法

有时一个方法需要修改它隶属的值类型的实例,可以在 func 之前放置 mutating 关键字,允许方法**修改其隶属的实例和实例的属性,**具体的修改过程请参考Modifying Value Types from Within Instance Methods

mutating + 方法声明语法

可变实例方法

protocol Togglable {
    mutating func toggle()
}

 枚举 OnOffSwitch 实现协议 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 is now equal to .on

medium.com/@agrawalsun…

初始化方法

协议可以限定需要实现的初始化方法,形式如下:

protocol SomeProtocol {
    init(someParameter: Int)
}

使用关键字 required ,把协议限定的初始化方法,指定为指定初始化方法或便捷初始化方法

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // initializer implementation goes here
    }
}

对于使用 final 修饰的类,可以省去初始化方法实现中的 required 修饰符,可参考 Required Initializers 和 Preventing Overrides

对于 final 修饰的类,协议初始化方法不需要使用 required 修饰符如果父类和子类自己实现的协议中有相同初始化方法,需要同时保留 **required 和 override **两个关键字。

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
    }
}

可以使用 final 限定方法、属性和下标禁止被子类覆盖

协议可以定义可失败初始化方法,即返回 nil 的方法。可失败和非可失败初始化方法,都可以满足可失败初始化方法非可失败和隐式未打包可失败初始化方法,都可以满足非可失败初始化方法

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}

docs.swift.org/swift-book/…

协议的使用

协议作为类型

协议是类型,可以作为参数、返回值、常量、变量、属性等元素的类型。可以通过 AnyObject 协议,限定自定义协议只能由类实现,排除枚举和结构体。

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

协议的组合

协议可以继承多个协议,类可以继承多个协议。

类只能继承一个父类,并且要放置在继承列表的第一个位置,其后可以跟随多个协议。

检测与强转

Type Casting 中的is 操作符检测类型是否实现了某个协议,as 操作符进行协议类型强转。

  • is 操作符 返回 true 如果实例实现了某协议,否则返回false

  • as? 向下转换操作符号的 + ? 会返回一个类型的可选值,如果转换失败返回nil

  • as! 向下转换操作符号的 + ! 会返回一个强转后的协议类型,如果强转失败会触发一个运行时错误(很危险,会引起对象内存模型的混乱表达)

    protocol HasArea { var area: Double { get } }

    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 } }

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

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

    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

可选协议

**Swift 引入通过 objc 关键字,实现与 **Objective-C 混编;通过引用 optional 关键字定义可选协议,引入 Objective-C 中的可选方法,由实现的类型决定是否实现其中声明的限制。optional  关键字不可独立使用。

@objc protocol ProtocolName {
     @objc optional ...}

非可选协议中的所有限制必须由类型实现。可选协议只能由 Objective-C 子类实现,枚举和结构体不能实现。

class ThreeSource: NSObject, ProtocolName {}

可选协议中的属性或是方法声明会自动变为 optional,如 (Int) -> String 会变为((Int) -> String)?

Note that the entire function type is wrapped in the optional, not the method’s return value.

可选链 optional chaining

可以在可选链中使用可选协议。属性名称+?或是方法名称+?+ 参数,如果属性值为 nil或是方法没有实现调用链终止,详情可参考  Optional Chaining 

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

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
        }
    }
}

协议扩展

可以对协议进行扩展,为实现协议的类型提供同一的方法、初始化方法、下标和计算属性的实现。如下面 RandomNumberGenerator 的扩展,为所有实现 RandomNumberGenerator 协议的类型提供了 randomBool() -> Bool 方法。

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// Prints "Here's a random number: 0.3746499199817101"
print("And here's a random Boolean: \(generator.randomBool())")
// Prints "And here's a random Boolean: true"

默认实现

可以通过扩展,为方法和计算属性提供默认实现。

extension PrettyTextRepresentable  {
    var prettyTextualDescription: String {
        return textualDescription
    }
}

添加限制

在协议扩展中,可以在协议名称后面,使用范型中的 where 语句,指定类型必须满足的限制,更多信息可参考 Generic Where Clauses 

extension ProtocolName where Element1: Protocol1, Element2: Protocol2, ... {
}
or 
extension Moveable where Element1: protocol<Protocol1, Protocol2>, ... {
}

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.

下面例子限定集合中的元素要实现 Equatable 协议,因此可以使用== !=操作符判断两个元素是否相等:

extension Collection where Element: Equatable {
    func allEqual() -> Bool {
        for element in self {
            if element != self.first {
                return false
            }
        }
        return true
    }
}

小结

协议是程序设计的蓝图,多种类型实现的抽象同一表达,可以通过扩展为协议提供默认的实现,从而简化统一多种类型对协议的实现。Swift 的协议中默认的方法、属性等是类型中必须实现的限定,没有可选限定。为了与 OC 混编,可以通过 objc 和 optional 关键字声明 Swift 协议中的某些限定为可选限定。

Swift 协议遵循多继承原则,只能继承一个父类,可以通过 AnyObject 协议,限定自定义协议只能由类实现,关闭枚举和结构体实现协议的权限。

资料

官方文档

[https://docs.swift.org/swift-book/LanguageGuide/Protocols.html](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html)  协议

docs.swift.org/swift-book/… 范型

docs.swift.org/swift-book/… 选择值列表

docs.swift.org/swift-book/… 类型转换 

docs.swift.org/swift-book/… 初始化方法

三方资料

medium.com/@agrawalsun… 值类型的可变实例方法