Swift面向协议编程简介

Swift面向协议编程简介

/**
 * 谨献给Yoyo
 *
 * 原文出处:https://www.toptal.com/swift/introduction-protocol-oriented-programming-swift
 * @author dogstar.huang <chanzonghuang@gmail.com> 2016-12-06
 */

协议是Swift编程语言中一个非常强大的特性。

协议用于定义“符合某个指定任务或者功能片的方法蓝图,属性,以及其他要求”。

Swift在编译时检查协议一致性问题,使得开发者可以在运行程序前发现代码中的一些致命错误。协议使得开发者可以在Swift编写灵活和可扩展的代码而不用妥协该语言的表现力。

Swfit通过提供一些最常见奇怪问题的解决方案以及许多其他编程语言的接口限制,进一步获得了使用协议的便利性。

通过面向协议编程,编写灵活、可扩展的Swfit代码。

在早期的Swfit版本中,只能扩展类、结构以及枚举类型,在很多现代编程语言里也是这样的。然而,自从Swift 2 开始,也能对协议进行扩展了。

此文章会考察在Swfit中协议如何用于编写可重用、可维护的代码,以及通过使用协议扩展如何修改可让单个小模块合并成一个大型面向协议的代码库。

协议

什么是协议?

在其最简单的定义里,协议是指描述某些属性和方法的接口。任何符合协议的类型,都应该使用合适的值填充协议中定义的特定属性,并且实现其必要的方法。例如:

protocol Queue {  
    var count: Int { get }
    mutating func push(_ element: Int) 
    mutating func pop() -> Int
}

此Queue协议描述了一个队列,它包含了整型的元素。此语法相当明了。

在协议块里面,当描述某个属性时,我们必须指定该属性是只读{ get }还是既可读又可写{ get set }。这里,变量Count(类型为Int)是只读的。

如果某个协议要求有一个读写的属性,那就不能用一个存放常量的属性或者一个只读的计算值来填充。

如果协议只要求属性是可读的,那么可以是任意类型的属性,并且该属性也可以是可写的,如果这对于你的代码有用的话。

对于在协议里定义的方法,使用关键词mutating指明该方法将会改变上下文是非常重要的。除此之外,方法的签名足以作为定义。

为了符合协议,类型必须提供全部实例属性以及实现在协议中描述的全部方法。例如下面,是一个符合我们Queue协议的Container结构。此结构本质上保存压入的Int到一个私有的items数组。

struct Container: Queue {  
    private var items: [Int] = []

    var count: Int {
        return items.count
    }

    mutating func push(_ element: Int) {
        items.append(element)
    }

    mutating func pop() -> Int {
        return items.removeFirst()
    }
}

然而,我们当前的Queue协议有一堆缺点。

仅有处理Int的容器才能符合此协议。

我们可以通过使用“关联类型”特性来去掉这个限制。关联类型的工作方式类似泛型。为了演示,让我们修改Queue协议以采用关联类型:

protocol Queue {  
    associatedtype ItemType
    var count: Int { get }
    func push(_ element: ItemType) 
    func pop() -> ItemType
}

现在此Queue协议允许储存任意类型的元素了。

在这个Container结构的实现里,编译器会根据上下文(例如方法返回类型和参数类型)决定此关联类型。这种方式使得我们可以创建一个带有泛型元素的Container结构。例如:

class Container<Item>: Queue {  
    private var items: [Item] = []

    var count: Int {
        return items.count
    }

    func push(_ element: Item) {
        items.append(element)
    }

    func pop() -> Item {
        return items.removeFirst()
    }
}

在很多情况下,使用协议都可以简化代码的编写。

例如,任何表示错误的对象都会符合Error(或者LocalizedError,以防我们想提供本地化的描述)协议。

然后在你的代码里,处理错误的相同逻辑就可以应用到任意这些错误对象上。因此,你不需要使用任何指定的对象来表示错误,用任何符合ErrorLocalizedError协议的就可以了。

你甚至可以扩展String类型,让它符合LocalizedError协议并且把字符串当作错误抛出。

extension String: LocalizedError {  
    public var errorDescription: String? {
          Return NSLocalizedString(self, comment:””)
    }
}


throw “Unfortunately something went wrong”


func handle(error: Error) {  
    print(error.localizedDescription)
}

协议扩展

协议扩展基于协议本身的威力。这使得我们可以:

  • 1、提供协议方法的默认实现和协议属性的默认值,从而使得它们“可选”。符合协议的类型可以提供他们自己的实现或者使用默认提供的。
  • 2、添加不在协议描述里的额外方法并且使用这些额外的方法“装饰”任意符合此协议的类型。这个特性允使我们添加指定方法到已经符合协议的混合类型上而不需要单独修改每一个类型。

默认的方法实现

让我们再来创建一个协议:

rotocol ErrorHandler {  
    func handle(error: Error)
}

这个协议描述了负责处理应用中出现的错误的对象。例如:

struct Handler: ErrorHandler {  
    func handle(error: Error) {
        print(error.localizedDescription)
    }
}

这里我们只是打印了错误的本地化描述。使用协议扩展我们可以让这个实现成为默认的实现。

extension ErrorHandler {  
    func handle(error: Error) {
        print(error.localizedDescription)
    }
}

通过提供一个默认的实现,这样使得handle方法变成可选。

使用默认的行为来扩展已经存在的协议的能力相当强大,这使得协议可以发展和扩展,而不需要担心破坏既有代码的兼容性。

条件扩展

我们已经提供了handle方法的默认实现,但打印到控制台对于终端用户一点用都没有。

当错误处理器是一个视图控制器时,我们可能更倾向通过本地化描述来显示某些排序好的警告视图给他们看。为了做到这一点,可以扩展ErrorHandle协议,但限制此扩展只用于既定的场景(例如,当类型是视图控制器时)。

Swift允使我们使用where关键字添加这样的条件到协议扩展里。

extension ErrorHandler where Self: UIViewController {  
    func handle(error: Error) {
        let alert = UIAlertController(title: nil, message: error.localizedDescription, preferredStyle: .alert)
        let action = UIAlertAction(title: "OK", style: .cancel, handler: nil)
        alert.addAction(action)
        present(alert, animated: true, completion: nil)
    }
}

在上面代码片段里的Self(“S”大写)是指类型(结构、类或枚举)。通过这样指定后我们只能为继承于UIViewController的类型扩展此协议,我们可以使用UIViewController指定方法(例如present(viewControllerToPresnt: animated: completion))。

现在, 任何符合ErrorHandler协议的视图控制器都拥有了handle方法的默认实现,即显示带有本地化描述的警告视图。

不明确的方法实现

假设这里有两个协议,两个都有一个方法,并且签名一样。

protocol P1 {  
    func method()
    //some other methods
}




protocol P2 {  
    func method()
    //some other methods
}

这两个协议都扩展了这个方法的默认实现。

extension P1 {  
    func method() {
        print("Method P1")
    }
}




extension P2 {  
    func method() {
        print("Method P2")
    }
}

假设这里有一个类型,符合这两个协议。

struct S: P1, P2 {

}

在这里,我们遇到了一个问题:不明确的方法实现。此类型没有清楚指明它应该使用哪个方法的实现。结果,我们得到了一个编译错误。为了修复这点,我们需要添加这个方法的实现到此类型里。

struct S: P1, P2 {  
    func method() {
        print("Method S")
    }
}

许多面向对象编程语言困扰于围绕歧义扩展定义解决方案的限制。通过允使程序员在编译器快速失败时取得控制权,Swift相当优雅地处理了这一点。

添加新方法

再来看多一眼Queue这个协议。

protocol Queue {  
    associatedtype ItemType
    var count: Int { get }
    func push(_ element: ItemType) 
    func pop() -> ItemType
}

每个符合此Queue协议的类型都有一个定义所存放元素数量的count实例属性。这让我们,除其他事项外,可以比较这样的类型以决定哪个更大。可以通过协议扩展来添加这样的方法。

extension Queue {  
    func compare<Q>(queue: Q) -> ComparisonResult where Q: Queue  {
        if count < queue.count { return .orderedDescending }
        if count > queue.count { return .orderedAscending }
        return .orderedSame
    }
}

Queue协议没有描述这个方法是因为它和队列功能不相关。

所以它不是协议方法的默认实现,而是一个新的“装饰”全部符合Queue协议的类型方法实现。没有协议扩展我们将不得不分别把这个方法添加到每个类型上。

协议扩展 vs 基类

协议扩展和使用基类可能看起来相当相似,但使用协议扩展有几点好处。包括但不限于:

  • 1、因为类、结构和枚举可以符合多个协议,他们可以接收多种协议的默认实现。这在概念上和其他语言里的多继承相类似。
  • 2、协议可以被类、结构和枚举采用,而基类和继承只能用于类。

Swift标准库扩展

除了扩展自己的协议,还可以扩展来自Swift标准库的协议。例如,如果想找到队列集合里的平均值,可以通过扩展标准的Collection协议来做到这一点。

由Swfit标准库提供的序列化数据结构,其元素可以通过索引下标进行遍历和访问,通常符合Collection协议。通过协议扩展,可以扩展全部这些标准库数据结构或者有选择性地只扩展一部分。

注意:此协议在Swfit 2.x 以前叫CollectionType,而在Swfit 3 里已改名为Collection

extension Collection where Iterator.Element: Queue {  
    func avgSize() -> Int {
        let size = map { $0.count }.reduce(0, +)
        return Int(round(Double(size) / Double(count.toIntMax())))
    }
}

现在可以统计任何队列集合(Array, Set等)的平均值了。没有协议扩展的话,我们需要为每个集合类型分别添加这个方法。

在Swfit标准库里,协议扩展用于实现,例如,这样的方法诸如:map, filter, reduce等。

extension Collection {  
    public func map<T>(_ transform: (Self.Iterator.Element) throws -> T) rethrows -> [T] {




    }
}

协议扩展和多态

正如我曾经说过的,协议扩展使得我们可以为某些方法添加默认实现,也可以添加新的方法实现。但这两种特性有什么区别呢?让我们回到前面的错误处理器,找出答案。

protocol ErrorHandler {  
    func handle(error: Error)
}


extension ErrorHandler {  
    func handle(error: Error) {
        print(error.localizedDescription)
    }
}


struct Handler: ErrorHandler {  
    func handle(error: Error) {
        fatalError("Unexpected error occurred")
    }
}


enum ApplicationError: Error {  
    case other
}


let handler: Handler = Handler()  
handler.handle(error: ApplicationError.other)  

结果是一个致命错误。

现在删除声明在协议里的handle(error: Error)方法。

protocol ErrorHandler {

}

结果还是一样:一个致命错误。

这是不是意味着,为协议方法添加默认实现和为协议添加新的方法实现,没有什么不同?

不!还是有区别的,如果把handler变量的类型从Handler改成ErrorHandler,你就能出来了。

let handler: ErrorHandler = Handler()  

现在输出到控制台的是:The operation couldn’t be completed. (ApplicationError error 0.)

但如果把handle(error: Error)这个方法还原到协议的声明里的话,结果又会变回到致命错误。

protocol ErrorHandler {  
    func handle(error: Error)
}

一起来看下在各个场景中依次发生了什么。

当协议存在方法声明时:
协议声明了handle(error: Error)方法并且提供了一个默认的实现。此方法在Handler实现中被重载。所以,此方法的正确实现将会在运行时被调用,不管是什么类型的变量。

当协议不存在方法声明时:
因为这个方法没有声明在协议里,类型不能对它进行重载。那就是为什么一个被调用的方法的实现依赖于变量的类型。

如果变量的类型是Handler,来自该类型的方法实现将会被调用。如果变量的类型是ErrorHandler,来自该协议扩展的方法将会被调用。

面向协议的代码:安全而富有表现力

在这篇文章中,我们演示了一些在Swift里协议扩展的强大之处。

不像其他使用接口的编程语言,Swift没有用不必要的限制来约束协议。Swift通过允许开发人员根据需要解决歧义来解决这些编程语言常见的问题。

使用Swift协议和协议扩展,可以编写出像大部分动态编程语言富有表现力且在编译时类型安全的代码。这使得你可以确保代码的可重用性和可维护性,以及在对Swift应用代码库做出修改时更有自信。

我们希望这篇文章对你有所帮助,同时也欢迎评论、留言、反馈或者进一步的见解。


------------------------

转载于:https://my.oschina.net/dogstar/blog/803777

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Swift面向协议编程(Protocol-Oriented Programming,简称POP)是Swift编程语言中的一个重要特性。使用POP的主要场景包括: 1. 代码复用:使用协议可以将某些功能抽象成一个独立的模块,并使多个不同的类或结构体共享该模块的功能。 2. 增强类型安全:Swift是一种类型安全的编程语言,POP进一步提高了类型安全性,因为它强制规定每个类型都必须遵循相应的协议。 3. 扩展性:使用协议可以轻松地向现有类型添加功能。例如,可以通过扩展一个实现了某个协议的类型来实现对该类型的功能扩展。 4. 网络编程协议可以用于定义网络通信协议,例如TCP、UDP、HTTP等,方便网络编程。 5. 泛型编程Swift中的泛型编程和POP密切相关。通过将协议作为类型参数传递,可以实现更灵活的泛型编程。 总之,使用POP可以提高代码的可读性、可维护性和可扩展性,同时也可以使代码更加清晰、简洁、易于测试和优化。 ### 回答2: Swift面向协议编程是一种软件开发方法论,它通过协议来定义一组需要实现的属性、方法和功能,实现了代码的模块化和复用,以及降低代码的耦合度。下面介绍一些Swift面向协议编程的使用场景。 1. 标准化和扩展:通过定义协议,可以规范一组对象需要实现的共同属性和方法。这样,不同的对象可以用相同的方进行处理和操作,提高代码的可读性和维护性。另外,如果需要为已有类型添加新的功能,可以通过扩展协议来实现,而不需要修改原有的类型。 2. 运行时多态:Swift协议支持多继承,一个类型可以遵循多个协议。通过协议,可以实现运行时的多态,将不同的实现绑定到相同的协议类型上。这可以使得代码更加灵活,能够适应不同的场景和需求。 3. 依赖注入和测试驱动开发:面向协议编程可以方便地进行依赖注入,通过将依赖关系抽象为协议,可以在不同的环境中进行替换。这对于测试驱动开发非常有用,可以提高代码的测试性和可测性。 4. UI 组件定制化:通过定义协议,可以将UI组件的定制化能力下放给使用者。例如,定义一个可定制的协议,使用者可以实现该协议,来定制按钮的外观、动画等。这种方可以提高代码的重用性,同时也增加了代码的灵活性。 总之,Swift面向协议编程提供了一种模块化、可扩展和可定制化的开发方,适用于各种需要代码复用和灵活性的场景,特别是在项目需要多态、依赖注入和定制化的情况下,面向协议编程可以发挥出其优势。 ### 回答3: Swift面向协议编程是一种重要的编程,它可以应用于许多不同的场景。以下是几个常见的使用场景: 1. 模块化和组件化:使用协议定义接口和方法,可以实现模块化和组件化的开发方。不同的组件可以通过实现相同的协议来进行交互,降低了代码的耦合度,增加了代码的可复用性和可维护性。 2. 单元测试:使用协议可以将被测试的类与测试类进行解耦。通过实现相同的协议,可以用一个模拟的类来替代真实的类进行测试,从而更容易编写和执行单元测试。 3. 多继承:Swift不支持多继承,但是可以通过协议来实现类似的功能。一个类可以同时遵循多个协议,从而获取不同协议的功能和属性。这种方可以在不引入类层次的情况下实现代码的复用。 4. 适配不同平台和框架:在跨平台开发和使用不同框架的情况下,使用协议可以更好地适应不同的平台和框架。通过定义统一的协议,不同的平台和框架可以通过实现这个协议来进行交互和兼容。 5. 插件系统:在一些应用程序中,可能希望用户可以编写自己的插件来扩展应用的功能。使用协议可以定义插件接口,插件通过实现这个协议来与应用进行交互和扩展。 总之,Swift面向协议编程广泛应用于软件开发中。它可以提高代码的可复用性、可扩展性和可维护性,同时也能够促进模块化开发和解耦。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值