Swift(二十四)协议

2016-11-20  本文已影响25人  冰三尺
杭城下雨,路边随手拍的。

Protocol(协议)用于统一方法和属性的名称,而不实现任何功能。协议能够被类,枚举,结构体实现,满足协议要求的类,枚举,结构体被称为协议的遵循者。

1.协议语法
2.属性要求
3.方法要求
4.突变方法要求
5.初始化要求
6.协议类型
7.委托
8.给协议添加扩展
9.协议类型的集合
10.协议的继承
11.只有类才能使用的协议
12.协议组合
13.检查协议一致性
14.可选协议要求
15协议扩展

1.协议的语法

协议的定义与类,结构体,枚举的定义非常相似,如下所示:

protocol SomeProtocol {
    // protocol definition goes here
}

在类,结构体,枚举的名称后加上协议名称,中间以冒号:分隔即可实现协议;实现多个协议时,各协议之间用逗号,分隔,如下所示:

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

2.属性要求

协议能够要求其遵循者必须含有一些特定名称和类型的实例属性(instance property)或类属性 (type property),也能够要求属性的(设置权限)settable 和(访问权限)gettable,但它不要求属性是存储型属性(stored property)还是计算型属性(calculate property)。
当某个类含有父类的同时并实现了协议,应当把父类放在所有的协议之前,如下所示:

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

通常前置var关键字将属性声明为变量。在属性声明后写上{ get set }表示属性为可读写的。{ get }用来表示属性为可读的。即使你为可读的属性实现了setter方法,它也不会出错。

protocol SomeProtocol { 
    var musBeSettable : Int { get set } 
    var doesNotNeedToBeSettable: Int { get } 
}
protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
}

protocol FullyNamed {
    var fullName: String { get }
}


struct Person: FullyNamed {
    var fullName: String
}
let john = Person(fullName: "John Appleseed")

这个例子定义了一个名为结构Person,它代表一个特定的人。Person结构体含有一个名为fullName的存储型属性,完整的遵循了协议。(若协议未被完整遵循,编译时则会报错)。
下面是一个更复杂的类,它也采用了和符合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")
print(ncc1701.fullName)
// ncc1701.fullName is "USS Enterprise"

Starship类将fullName实现为可读的计算型属性。它的每一个实例都有一个名为name的必备属性和一个名为prefix的可选属性。 当prefix存在时,将prefix插入到name之前来为Starship构建fullName。

3.方法要求

协议能够要求其遵循者必备某些特定的实例方法和类方法。协议方法的声明与普通方法声明相似,但它不需要方法内容。

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.37464991998171"
print("And another one: \(generator.random())")
// Prints "And another one: 0.729023776863283"

4.突变方法要求

能在方法或函数内部改变实例类型的方法称为突变方法。在值类型(Value Type)(译者注:特指结构体和枚举)中的的函数前缀加上mutating关键字来表示该函数允许改变该实例和其属性的类型。

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

如下所示,Togglable协议含有toggle函数。根据函数名称推测,toggle可能用于切换或恢复某个属性的状态。mutating关键字表示它为突变方法:

protocol Togglable { 
    mutating func toggle() 
} 

当使用枚举或结构体来实现Togglabl协议时,必须在toggle方法前加上mutating关键字。

如下所示,OnOffSwitch枚举遵循了Togglable协议,On,Off两个成员用于表示当前状态

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()

5.初始化要求

协议可以要求通过符合类型实现特定的初始化。你写这些初始化在完全相同的方式为正常初始化协议定义的一部分,但没有花括号或初始化体:

protocol SomeProtocol {
        init(someParameter: Int)
}

类实现协议的初始化要求
如果该类接受了协议,你可以实现协议的初始化方法,但是此时需要使用关键字required来标记

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

注意: 你并不需要标记协议初始化实现与required上标有类修改final修改,因为最终的类不能被继承。

如果一个子类实现了父类的初始化方法,但同事也实现了协议的初始化方法,则使用关键字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
    }
}

6.协议类型

协议本身实际上没有实现任何功能。但是,您创建的任何协议将成为一个不折不扣的类型,在代码中使用。
因为它是一个类型,可以使用在许多地方使用。
使用场景:

  1. 作为函数,方法或构造器中的参数类型,返回值类型
  2. 作为常量,变量,属性的类型
  3. 作为数组,字典或其他容器中的元素类型
    这里是作为一个类型的协议的一个例子:
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
    }
}
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

7.委托

委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能交由(委托)给其他的类型。

委托模式的实现很简单: 定义协议来封装那些需要被委托的函数和方法, 使其遵循者拥有这些被委托的函数和方法。

具体使用官方例子比较长,请自行查阅

8.给协议添加扩展

protocol TextRepresentable {
    var textualDescription: String { get }
}

本着软件设计的开闭原则,对扩展开放,对修改关闭,我们可以对协议进行扩展,而添加新的方法

extension TextRepresentable {  
      //扩展的新内容
}

也可以扩展一个类,结构体使其遵循新的协议

extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

通过延展补充协议声明
当一个类型已经实现了协议中的所有要求,却没有声明时,可以通过扩展来补充协议声明:

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

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

9.协议类型的集合

协议类型可以被集合使用,表示集合中的元素均为协议类型:

protocol oneProtocol {
    func test()
}
class oneClass: oneProtocol {
    func test() {
        print("oneClass")
    }
}
let one = oneClass();

class twoClass: oneProtocol {
    func test() {
        print("twoClass")
    }
}
let two = twoClass();
//该集合里面的元素全部是遵从oneProtocol协议的对象
let things: [oneProtocol] = [one, two]
for thing in things {
    print(thing.test())
}

10.协议的继承

协议能够继承一到多个其他协议。语法与类的继承相似,多个协议间用逗号,分隔

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
      // protocol definition goes here
}

继承该协议,同时,也要实现该协议的方法

protocol inheritProtocol {
    func inherit()
}
//oneProtocol继承协议inheritProtocol, 接受oneProtocol协议的类同时也要接受inheritProtocol
protocol oneProtocol:inheritProtocol {
    func test()
}
class oneClass: oneProtocol {
//inheritProtocol
    func test() {
        print("oneClass")
    }
//inheritProtocol
    func inherit() {
        
    }
}

11.只有类才能使用的协议

就是指这个协议已经被特殊限制,只能有类来实现,结构体或者枚举都不能使用

protocol oneProtocol {
    
}
//使用关键字class来声明,如果该协议需要继承其他协议,需要写在其他协议最前面
protocol SomeClassOnlyProtocol: class, oneProtocol {
    // class-only protocol definition goes here
}
class classA: SomeClassOnlyProtocol {
    
}

struct structA: SomeClassOnlyProtocol {
        //    报错error: non-class type 'structA' cannot conform to class protocol 'SomeClassOnlyProtocol'
}

12.协议组合

当一个类,结构体需要同时符合多个协议时可以使用协议组合

protocol Named {
    var name: String { get }
}
protocol Aged {
    var age: Int { get }
}
struct Person: Named, Aged {
    var name: String
    var age: Int
}
//参数必须同时符合Named和Aged协议,中间用&链接
func wishHappyBirthday(celebrator: Named & Aged) {
    print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
//Person已经遵循了这两个协议
wishHappyBirthday(celebrator: birthdayPerson)
// Prints "Happy birthday, Malcolm, you're 21!"

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

13.检验协议的一致性

使用is检验协议一致性,使用as将协议类型向下转换(downcast)为的其他协议类型。检验与转换的语法和之前相同(详情查看类型检查):

  1. is操作符用来检查实例是否遵循了某个协议。
  2. as?返回一个可选值,当实例遵循协议时,返回该协议类型;否则返回nil
    3.as! 强制解析,如果可选值为nil,会产生运行时错误

14.可选协议要求

15协议扩展


//协议作为类型使用
protocol RandomGenerable {
    func randomNumber() -> Int
}

struct RandomNumber : RandomGenerable {
    func randomNumber() -> Int {
        return 100
    }
}

class TenRandomNumber : RandomGenerable {
    func randomNumber() -> Int {
        return 6
    }
}

struct Dice {
    var side : Int
    var randomPro : RandomGenerable
    
    func play() -> Int {
        return self.randomPro.randomNumber()
    }
}

let aDice = Dice(side: 6, randomPro: TenRandomNumber())
aDice.play()
上一篇下一篇

猜你喜欢

热点阅读