小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
1.什么是面向协议编程
Apple于2015年WWDC提出,Swift是世界上第一门面向协议编程的语言。它是swift2.0引入的一种新的编程范式,通过协议扩展,协议继承和协议组合的方式来设计编写代码。
2.面向协议编程和面向对象编程
面向协议编程POP,和面向对象编程OOP,这2个是相辅相成的,没有说一定只能用某个,更多的时候,POP能补足OOP一些设计的不足。
OOP的三大特性是封装,继承,多态。
很多时候,我们都会把一些共性的东西,提供到父类,让子类去继承父类。好处是子类可以直接使用父类的方法,不好的地方是父类的东西有可能越来越多,变得臃肿。这时候,POP就可以解决这种问题了。
我们可以通过protocol协议,对协议继续extension扩展,在扩展里面实现协议的方法,让我们的类遵循protocol协议,就可以调用协议扩展方法了。
protocol JJProtocol {
func toast()
}
extension JJProtocol {
func toast() {
print("toast")
}
}
class Cat: JJProtocol {
}
class Dog: JJProtocol {
}
do {
let cat = Cat()
cat.toast()
let dog = Dog()
dog.toast()
}
这样的好处是,你想要哪个类实现toast功能,就哪个类遵循这个协议就好。
3.总结POP的好处
- 结构体,枚举等值类型不可以继承,所以可以使用这种方式
- 增强的代码的可扩展性,在协议扩展中实现,减少代码的冗余
- 协议可以看做是一个组件,增强可测性
4.利用协议实现前缀效果
RxSwift的核心就是面向协议编程。特别是RxCocoa里面,封装了大量关于iOS控件的使用。如按钮的button.rx.tap。点击进去可以发现,rx是一个Reactive的结构体。实现如下
public struct Reactive<Base> {
/// Base object to extend.
public let base: Base
/// Creates extensions with base object.
///
/// - parameter base: Base object.
public init(_ base: Base) {
self.base = base
}
}
/// A type that has reactive extensions.
public protocol ReactiveCompatible {
/// Extended type
associatedtype ReactiveBase
@available(*, deprecated, renamed: "ReactiveBase")
typealias CompatibleType = ReactiveBase
/// Reactive extensions.
static var rx: Reactive<ReactiveBase>.Type { get set }
/// Reactive extensions.
var rx: Reactive<ReactiveBase> { get set }
}
extension ReactiveCompatible {
/// Reactive extensions.
public static var rx: Reactive<Self>.Type {
get {
return Reactive<Self>.self
}
// swiftlint:disable:next unused_setter_value
set {
// this enables using Reactive to "mutate" base type
}
}
/// Reactive extensions.
public var rx: Reactive<Self> {
get {
return Reactive(self)
}
// swiftlint:disable:next unused_setter_value
set {
// this enables using Reactive to "mutate" base object
}
}
}
import class Foundation.NSObject
/// Extend NSObject with `rx` proxy.
extension NSObject: ReactiveCompatible { }
如果我们也想扩展自己的功能,又担心和系统的命名冲突,这时候我们可以模拟这种写法,这里我用自己的简写jj,来实现类似rx的效果。下面我们一步一步推演。
先写一个String的扩展计算属性。
extension String {
var numberCount: Int {
var count = 0
for c in self where ("0"..."9").contains(c) {
count += 1
}
return count
}
}
这时候,我们就可以写"123abc".numberCount,但这个不是我们想要的效果。参考rx,我们实现自己的结构体JJStruct,里面初始化str,让结构体的扩展实现numberCount方法。
而要做到string.jj,只需要在String的扩展中添加计算属性jj即可。
struct JJStruct {
var str: String
init(_ string: String) {
self.str = string
}
}
extension JJStruct {
var numberCount: Int {
var count = 0
for c in str where ("0"..."9").contains(c) {
count += 1
}
return count
}
}
extension String {
var jj: JJStruct { JJStruct(self) }
}
"123abc".jj.numberCount
上面的确可以实现.jj效果,但是我们又发现,如果我们想要扩展Int,或者其他的,这时候难道又要在结构体JJStruct中,添加int的变量吗,所以这里我们要把这些改成泛型。
struct JJStruct<Base> {
var base: Base
init(_ base: Base) {
self.base = base
}
}
extension JJStruct where Base == String {
var numberCount: Int {
var count = 0
for c in base where ("0"..."9").contains(c) {
count += 1
}
return count
}
}
extension String {
var jj: JJStruct<Self> { JJStruct(self) }
}
这样的话,我们其它的扩展,只要Base传相应的类型就可以了,但我们又发现,这里只支持实现对象调用,而且方法还不能加mutating修改,所以我们得优化一下。
再添加多一个静态计算属性,允许set和get。
struct JJStruct<Base> {
var base: Base
init(_ base: Base) {
self.base = base
}
}
extension JJStruct 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() {
}
mutating func test2() {
}
}
extension String {
var jj: JJStruct<Self> {
set {}
get {JJStruct(self)}
}
static var jj: JJStruct<Self>.Type {
set{}
get {JJStruct.self}
}
}
"123abc".jj.numberCount
var str = "123abc"
str.jj.test2()
String.jj.test()
这时候,问题又来了,我要是还要扩展Int,还是需要var jj,这时候,我们就可以用到协议了。在协议扩展中定义计算属性,要扩展的String ,Int, Class都遵循这个协议。
struct JJStruct<Base> {
var base: Base
init(_ base: Base) {
self.base = base
}
}
protocol JJProtocol {
}
extension JJProtocol {
var jj: JJStruct<Self> {
set {}
get {JJStruct(self)}
}
static var jj: JJStruct<Self>.Type {
set{}
get {JJStruct.self}
}
}
extension JJStruct 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() {
}
mutating func test2() {
}
}
extension String : JJProtocol {}
也就是说,后面只要extension Type: JJProtocol {},该Type就是Base类型,然后我们extension JJStruct where Base == Type里面,实现想要扩展的计算属性,方法等,就可以了。看到最后的实现,对比一下rx,你会发现,和我们自己封装的是差不多的。