Swift系列三十三 - 面向协议编程

面向协议编程(Protocol Oriented Programming,简称POP)是Swift的一种编程范式,Apple于2015年WWDC提出。在Swift的标准库中,能见到大量POP的影子。

一、POP和OOP

1.1. 回顾OOP

Swift也是一门面向对象的编程语言(Object Oriented Programming,简称OOP)。

OOP的三大特性:封装、继承、多态。

继承的经典使用场景:当多个类(比如A、B、C类)具有很多共性时,可以将这些共性抽取到一个父类中(比如D类),最后A、B、C类继承D类。

有些问题,使用OOP并不能很好解决,比如:如何将BVC、DVC的公共方法run抽取出来?

class BVC: UIViewController {
    func run() {
        print("run")
    }
}

class DVC: UITableViewController {
    func run() {
        print("run")
    }
}

1.2. OOP解决方案

  1. run方法放到另一个对象A中,然后BVC、DVC拥有对象A属性;

  2. run方法添加到UIViewController分类中(UITableViewController继承自UIViewController);

  3. run方法抽取到新的父类,采用多继承(C++支持多继承)。

虽然可以解决问题,但是第一种方法多了一些额外的依赖关系。第二种方法会导致UIViewController越来越臃肿,而且会影响它的其他所有子类。第三中虽然在iOS开发中用不到,但在C++中使用也会增加程序设计的复杂度,产生菱形继承等问题,需要开发者额外解决。

1.3. POP解决方案

定义一个协议,同时定义公共方法run。扩展协议并实现抽象方法run,类遵守协议即可。

protocol Runnable {
    func run()
}

extension Runnable {
    func run() {
        print("run")
    }
}

class BVC: UIViewController, Runnable { }
class DVC: UITableViewController, Runnable { }

相比较OOP,POP的解决方案更加简洁。因为协议支持扩展实现,使得Swift使用POP非常便捷。

在Swift开发中,OOP和POP是相辅相成的,任何一方并不能取代另一方。POP能弥补OOP一些设计上的不足。

1.4. POP的注意点

  • 优先考虑创建协议,而不是父类(基类)
  • 优先考虑值类型(struct、enum),而不是引用类型(class
  • 巧用协议的扩展功能
  • 不要为了面向协议而使用协议(有时候使用类更合理)

二、添加前缀

场景:为字符串增加计算纯数字的功能。

示例代码:

var str = "123ttt456"
func numberCount(_ str: String) -> Int {
    var count = 0
    for c in str where ("0"..."9").contains(c) {
        count += 1
    }
    return count
}
print(numberCount(str)) // 输出:6

上面的示例代码已经完成了基本的功能,但是还有很多需要优化的地方。

优化代码一(协议扩展):

extension String {
    func numberCount() -> Int {
        var count = 0
        for c in self where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
}
print(str.numberCount()) // 输出:6

使用协议后,所有字符串都可以使用该功能,并且也限定了只有字符串类型可以使用。但是计算属性比函数调用更加优雅。

优化代码二(计算属性):

extension String {
    var numberCount: Int {
        var count = 0
        for c in self where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
}
print(str.numberCount) // 输出:6
print("45579test12".numberCount) // 输出:7

上面优化过后看似已经很完善了,但隐藏一个风险:属性名有可能和系统有冲突。这时候我们可以为属性添加一个前缀加以区分。

优化代码三(OC风格加前缀):

extension String {
    var db_numberCount: Int {
        var count = 0
        for c in self where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
}
print(str.db_numberCount) // 输出:6

类似OC的前缀做法虽热能够满足条件,但总感觉有点OC的风格。其实我们可以使用Swift风格为属性/函数添加前缀。

优化代码四(Swift风格加前缀):

struct DB {
    var string: String
    init(_ string: String) {
        self.string = string
    }
    var numberCount: Int {
        var count = 0
        for c in string where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
}

extension String {
    var db: DB {
        return DB(self)
    }
}
print(str.db.numberCount) // 输出:6

为字符串扩展一个DB(自定义)属性,DB是自定义结构体,内部实现了字符串数字计数功能。这样不仅拥有自己的命名空间,后续有任何字符串相关自定义功能都可以放到结构体中。

一般情况下,我们不会只为字符串扩展自定义功能,数组、字典、自定义类等都有可能扩展新的自定义功能。但总不能为在DB结构体中为每一个类型都新增一个类型和初始化方法,这样DB结构体就会变得很臃肿。可以使用泛型解决该问题。

优化代码五(通用):

struct DB<Base> {
    var base: Base
    init(_ base: Base) {
        self.base = base
    }
}

extension String {
    var db: DB<String> { DB(self) }
}

class Person {
    var name: String
    init(_ name: String) {
        self.name = name
    }
}
extension Person {
    var db: DB<Person> { DB(self) }
}

extension DB where Base == String {
    var numberCount: Int {
        var count = 0
        for c in base where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
}
print(str.db.numberCount) // 输出:6

extension DB where Base: Person {
    func run() {
        print(base.name, "running")
    }
}
let p = Person("大奔")
print(p.db.run()) // 输出:大奔 running

使用泛型后,再为结构体DB扩展对应类型的函数/属性,不仅类型隔离,还做到了统一DB管理。

注意:为类扩展时要注意扩展方法是否要求子类也必须拥有,Base: PersonBase == Person是有区别的。

如果要为类扩展属性/方法,就需要再增加一个类型属性db

示例代码:

extension String {
    var db: DB<String> { DB(self) }
    static var db: DB<String>.Type { DB<String>.self }
}

extension DB where Base == String {
    var numberCount: Int {
        var count = 0
        for c in base where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
    static func test() {
        print("String test")
    }
}
String.db.test() // 输出:String test

如果为不同类型扩展方法,就需要重复定义实例属性和类型属性。这时候可以使用协议实现这些属性,那个类型需要扩展方法,只需要遵守该协议即可。

最终优化代码:

// 1. 前缀类型(中介)
struct DB<Base> {
    var base: Base
    init(_ base: Base) {
        self.base = base
    }
}

// 2. 利用协议扩展前缀属性
protocol DBCompatible { }
extension DBCompatible {
    var db: DB<Self> { 
      set { }
      get { DB(self) }
    }
    static var db: DB<Self>.Type { 
      set { }
      get { DB<Self>.self }
    }
}

// 3. 让String拥有db前缀属性
extension String: DBCompatible { }

// 4. 给db前缀(实例/类型)扩展功能
extension DB where Base == String {
    var numberCount: Int {
        var count = 0
        for c in base where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
    static func test() {
        print("String test")
    }
}
print(str.db.numberCount) // 输出:6
String.db.test() // 输出:String test

// 为自定义类扩展方法
class Person {
    var name: String
    init(_ name: String) {
        self.name = name
    }
}

extension Person: DBCompatible { }

extension DB where Base: Person {
    func run() {
        print(base.name, "running")
    }
}
let p = Person("大奔")
print(p.db.run()) // 输出:大奔 running

注意:如果扩展函数使用mutating,在利用协议扩展前缀属性时,属性必须是可读可写的。

String增加扩展功能后,NSStringNSMutableString也能使用么?

需要为NSStringNSMutableString扩展db属性,但其实只需要为NSString扩展就行了,因为NSMutableString继承自NSString

extension NSString: DBCompatible { }

找不到对应方法,但是如果为其扩展一个和String方法就会出现代码重复。我们知道StringNSStringNSMutableString都遵守了一个ExpressibleByStringLiteral协议,只需要把扩展条件修改一下就可以了。

extension DB where Base: ExpressibleByStringLiteral {
    var numberCount: Int {
        var count = 0
        for c in (base as! String) where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
    static func test() {
        print("String test")
    }
    
}

注意base需要强转为String

三、利用协议实现类型判断

场景:判断传入的实例参数是不是数组。

示例代码:

func isArray(_ value: Any) -> Bool {
    value is [Any]
}
print(isArray([1, 2])) // 输出:true
print(isArray(["1", 2])) // 输出:true
print(isArray(NSArray())) // 输出:true
print(isArray(NSMutableArray())) // 输出:true
print(isArray("123")) // 输出:false

场景:判断传入的类型参数是不是数组。

示例代码:

protocol ArrayType { }
extension Array: ArrayType { }
extension NSArray: ArrayType { }
func isArrayType(_ type: Any.Type) -> Bool {
    type is ArrayType.Type
}
print(isArrayType([Int].self)) // 输出:true
print(isArrayType([Any].self)) // 输出:true
print(isArrayType(NSArray.self)) // 输出:true
print(isArrayType(NSMutableArray.self)) // 输出:true

注意:[Int].Type[Any].Type没有关系。协议最终是一个类型,是有自己的meta的。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值