MVC一直以来是代码组织架构中苹果公司所推崇的开发模式,但因为工程类文件的日益增多,MVC中C层(Controller层)的日益臃肿,产品需求复杂化,迭代速度愈来愈快,老架构设计已经渐渐跟不上高强度的组件化团队化开发了。最近一直在寻求一种开发模式,能让多个团队成员能够同时开发且逻辑清晰。期间阅读了不少文章,好比VIPER架构、UBer公司未开源的Riblets架构、MVVM架构等,最终决定本身针对MVVM进行一次架构改造,并加入VIPER的特色。其中MVVM的ViewModel的轻实现,当下被列为攻坚环节。git
MVVM的ViewModel中采用KVO的观察者模式监听,调用ViewController来进行整个架构的解耦设计。在Objective-C当中得益于强大的Runtime机制能够实现对任意类型的观察者监听。虽然Objective-C中能够任意定义KVO,可是经历过大项目的朋友必定首先会想到Objective-C中的KVO在使用的轻便型上差强人意,须要addObserver和removeObserver,且若是Context上下文弄错了,会有必定的崩溃风险,这是须要深入了解Objective-C的释放避免指针的循环引用等。github
Swift做为一个静态编译型语言,它摒弃了Objective-C中的Runtime机制。想要开启动态Property须要再Swift的Property前面增长声明:dynamic,且使用dynamic必须是基于NSObject基类所构造的类型,这样作必然会丧失对Swift原始数据类型的支持,可见其是很差的。并且预计没有多少朋友记得给变量打上dynamic的标记吧,起码我不会编程
很庆幸的是Swift语言在本身的Property中增长了getter/ setter的属性观察器,并对setter的属性观察器提供了willSet / didSet的两个观察器来详细监听值的变化。这让咱们看到Swift自己是汲取了Objective-C在Runtime中创造的经验和灵感,并将观察者模式轻量化,以至关优雅的方式去表示一个值的变化过程。swift
class valueDemo { var value:String = "" { willSet { print("newValue:", newValue) } didSet { print("done:", value) } } }
但是咱们在开发中不只仅是这样的简单环境,咱们须要针对MVVM中ViewModel开放一个被观察者链接给ViewController,二者产生联动。此时有人想到:"我提供一个闭包(block)设置给didSet就行了呀"。确实你能够这样作。为每个Property提供一个block虽然可行,但没有重用好这一机制是则会让代码变得重复。那咱们就要寻找一个好一点的方法来能让Property变成一个被观察者,当它发生变动的时候,触发一批block回调。api
ReactiveCocoa和RxSwift的第三方库来实现是能够很好地实现观察者模式(笔者更喜欢后者RxSwift的书写风格)。确实,如今MVVM中采用RxSwift解耦做为中间件确实是产品开发潮流,这就像某种服装搭配趋势同样的流行。那问题随之而来,采用ReactiveCocoa和RxSwift都哪些共同缺点呢?咱们开发实战的时候确定会遇到下面的问题:数组
基于以上几点缺点,我在这里不赞同采用这样的第三方组件的开发方式开发,虽然它们很酷炫、显得高大上!xcode
那难道没有一个又轻又容易维护的观察者模式吗?答案是有的!
那咱们就从零开始一步步实现一个基于Swift 3~4的低调奢华有内涵的观察者模式(题外话因为我所书写的日期是2017-6-6,正好是Swift 4发布当日,个人工程文件又一次被Swift4的升级所摧毁,被摧毁的是第三方库,那我仍是本身造一个轮子吧!)闭包
先来描述一下基本原理:架构
先来看一下基础代码:app
// 须要持有一批blocks,则必须建立一个类做为空间 class Observable<T> { typealias ObservableBlock = (T) -> () private var blocks: [ObserverBlock] = [] // 持有blocks init(_ t:T) { self.value = t } // 初始化value var value:T { didSet { // 实现didSet来遍历block,触发回调 for block in blocks { block(self.value) } } } // 订阅 func subscribe(block:@escaping ObserverBlock) { blocks.append(block) } }
run exmple:
let example = Observable<String>("") example.subscribe { (newValue:String) in print("newValue:", newValue) } example.value = "a" example.value = "b"
代码的运行结果:
newValue: a newValue: b
看到运行结果,很不错!基于简单blocks持有,基于didSet就能够完成对于一个变量设置的变动监听。
仔细打量了代码,中间缺乏几个能力:
第一步咱们先来加入高级运算符重载,片断代码:
infix operator <-: ObservableChange precedencegroup ObservableChange { associativity: left // 表示左结合 } public func <- <T> (left: Observable<T>, right: T) { left.value = right }
完整代码:[纯block,可自动释放内存]
// 高级运算符重载必须声明在final顶级访问级别的类中 public final class Observable<T> { typealias ObserverBlock = (T) -> () private var blocks: Array<ObserverBlock> = Array() init(_ t:T) { self.value = t } var value:T { didSet { for block in blocks { block(self.value) } } } func subscribe(block:@escaping ObserverBlock) { blocks.append(block) } deinit { print("Observable", #function) } } /* 定义 <- 运算符 运算符定义必须放在文件级别当中 */ infix operator <-: ObservableChange precedencegroup ObservableChange { associativity: left // 表示左结合 } public func <- <T> (left: Observable<T>, right: T) { left.value = right }
run exmple :
let example = Observable<String>("") example.subscribe { (newValue:String) in print("newValue:", newValue) } example.value = "a" example.value = "b" example <- "a"
代码的运行结果:
newValue: a newValue: b newValue: a
重载看上去还不错,很精简!那继续完善,填补后续的功能
第二步添加unSubscribe方法
起初我想直接经过block闭包的相等性检查,经过block闭包相等,来移除blocks中的指定闭包,可是失败了。好比代码:
public final class Observable<T> { typealias ObserverBlock = (T) -> () private var blocks: Array<ObserverBlock> = Array() init(_ t:T) { self.value = t } var value:T { didSet { for block in blocks { block(self.value) } } } func subscribe(block:@escaping ObserverBlock) { blocks.append(block) } // 移除订阅 func unSubscript(block:@escaping ObservableBlock) { var blocksFiltered = blocks.filter { (blockInArray:ObservableBlock) -> Bool in return blockInArray !== block // !!!!!!!没法编译,编译报错!!!!!!! //报错信息: Cannot check reference equality of functions;operands here have type '(T)->()' and '(T)->()' } self.blocks = blocksFiltered } }
看到//报错信息: Cannot check reference equality of functions;operands here have type '(T)->()' and '(T)->()'
发现Swift中是不容许将两个闭包进行的比较的。虽然遗留的C API中是有unsafeBitCast能够对两个闭包进行比较,但我仍是放弃这样的写法。
unsafeBitCast 相关使用:https://stackoverflow.com/questions/24111984/how-do-you-test-functions-and-closures-for-equality
那既然block没法比较相等,就只能讲上下文与blocks进行绑定关系,来实现订阅和删除订阅。
// 定义高级运算符重载,必须为final访问权限的声明 public final class Observable<T> { typealias ObserverBlock = (_ oldValue:T, _ newValue:T) -> () // 订阅block,增长old和new的传值 typealias ObserverEntry = (observer: AnyObject, block: ObserverBlock) // 观察者元组 private var observers: [ObserverEntry] // 观察者Array init(_ value:T) { self.value = value observers = [] } var value:T { didSet { observers.forEach { (entry: ObserverEntry) in let (_, block) = entry block(oldValue, value) } } } // 订阅,建立观察者元组 func subscribe(observer:AnyObject, block:@escaping ObserverBlock) { observers.append(ObserverEntry(observer:observer, block:block)) } // 解除订阅,根据元组中的观察者移除 func unSubscribe(observer:AnyObject) { let filtered = observers.filter { (entry: ObserverEntry) in let (owner, _) = entry return owner !== observer } observers = filtered } } infix operator <-: ObservableChange precedencegroup ObservableChange { associativity: left // 表示左结合 } // 运算符重载 public func <- <T> (left: Observable<T>, right: T) { left.value = right }
run example:
let example = Observable<String>("") example.subscribe(observer: self) { (oldValue:String, newValue:String) in print("oldValue:", oldValue, "newValue:", newValue) } example.value = "a" example.value = "b" example <- "a" example.unSubscribe(observer: self) example <- "c" // 取消订阅,则不会看到"c"的打印
代码的运行结果:
oldValue: newValue: a oldValue: a newValue: b oldValue: b newValue: a // 这里没有看到“c”
好了,通过细细打磨的Observable已经初步具有了观察者能力了,而且能够轻巧的应用于变量的观察
所有代码:
https://github.com/slazyk/Observable-Swift
我在编写期间试用了google的一个开发者开发的Observable-Swift的,但这个只针对于Swift 3且功能略显复杂,最后放弃。
不过本观察者订阅模式和其余的第三方组件其实都有弊端:
而内存循环应用,须要将被保存在entry当中的Observer在必要的时候unSubscribe掉才能够解决循环引用的问题。因此当以为Observer没有必要的话,仍是直接使用只有一个Block的版本吧![纯block,可自动释放内存]