探究ReactiveCocoa 底层KVO封装流程
一、对比原生KVO,初识ReactiveCocoa的KVO
-
- *
我们先来看一段代码,通过触屏来动态修改视图背景色
@interface ViewController () @property (nonatomic, strong)UIColor * bgColor; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. //1/Normal KVO [self normalKVO]; //2/RACKVO [self racObserver]; } #pragma mark normalKVO - (void)normalKVO { [self addObserver:self forKeyPath:@"bgColor" options:(NSKeyValueObservingOptionNew) context:nil]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { self.view.backgroundColor = [change objectForKey:NSKeyValueChangeNewKey];; } - (void)dealloc { [self removeObserver:self forKeyPath:@"bgColor"]; } #pragma mark racKVO - (void)racObserver { [RACObserve(self, bgColor) subscribeNext:^(id _Nullable x) { self.view.backgroundColor = (UIColor *)x; }]; } #pragma mark touch change - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { CGFloat red = arc4random() % 256 / 255.0; CGFloat blue = arc4random() % 256 / 255.0; CGFloat green = arc4random() % 256 / 255.0; UIColor * randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1]; self.bgColor = randomColor; } @end
从上面步骤我们可以看出原生的KVO使用分为三个步骤:
- 添加监听
- 实现监听的代理方法
- 移除监听
但是RACKVO只是用了非常简单的一段代码就实现了以上的这三个步骤,去掉了胶水代码,真正的做到了面向业务开发,那它是怎么实现的呢,接下来我们来一层层分析
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:763164022,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
二、深入RAC底层逐层探究KVO实现
-
- *
- 点击RACObserver找到这个宏
#define _RACObserve(TARGET, KEYPATH) \ ({ \ __weak id target_ = (TARGET); \ [target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \ })
继续点进去,
我们会进入NSObject+RACPropertySubscribing.m文件下的
- (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver { NSObject *strongObserver = weakObserver; keyPath = [keyPath copy]; NSRecursiveLock *objectLock = [[NSRecursiveLock alloc] init]; objectLock.name = @"org.reactivecocoa.ReactiveObjC.NSObjectRACPropertySubscribing"; __weak NSObject *weakSelf = self; RACSignal *deallocSignal = [[RACSignal zip:@[ self.rac_willDeallocSignal, strongObserver.rac_willDeallocSignal ?: [RACSignal never] ]] doCompleted:^{ [objectLock lock]; @onExit { [objectLock unlock]; }; }]; return [[[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { [objectLock lock]; @onExit { [objectLock unlock]; }; __strong NSObject *observer __attribute__((objc_precise_lifetime)) = weakObserver; __strong NSObject *self __attribute__((objc_precise_lifetime)) = weakSelf; if (self == nil) { [subscriber sendCompleted]; return nil; } return [self rac_observeKeyPath:keyPath options:options observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { [subscriber sendNext:RACTuplePack(value, change)]; }]; }] takeUntil:deallocSignal] setNameWithFormat:@"%@ -rac_valueAndChangesForKeyPath: %@ options: %lu observer: %@", RACDescription(self), keyPath, (unsigned long)options, RACDescription(strongObserver)]; }
我们会发现其中有一个deallocSignal,见名知意,我们先猜这个信号大概是在delloc的时候调用的,至于怎么调用的我们搁在一边;重点来了,return这段代码是重点,我们能够从中发现return的是一个信号RACSignal对象,并且这个signal有一个依赖前提:takeUntil:deallocSignal,KVO取值会一直取到VC释放,当这个VC释放之后,也就没有必要去取值了,也就是说deallocSignal这个信号在VC释放之前会一直执行,VC释放之后功能也会跟着失效,这里我们可以猜出,RACKVO封装思路中,最后一步的释放时机应该是在这里。
好,我们接着分析中间部分的代码,可以看出的是,万物皆信号---RACKVO使用了信号量来处理监听,结合之前信号量生命周期(传送门https://www.jianshu.com/p/bd4fff21d9b7),此处创建了信号,然后把这个信号return了出去,在外面subscribeNext订阅信号,外面订阅信号并同时调用了初始化保存的这个block代码块,代码块里进行completed操作取消订阅,取消订阅之前,在一个这样的代码块中做了订阅者的sendNext操作,这样信号量的生命周期是完整的,但是我们的KVO操作到现在还没有看见,那么只可能在这步操作隐藏了封装的内容
[self rac_observeKeyPath:keyPath options:options observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { [subscriber sendNext:RACTuplePack(value, change)]; }];
也就是return的这部分代码,我们接下来继续分析这部分代码:通过订阅信号时保存的sendNext代码块,把监听到的change值传出去,也就是我们在VC那一个block的调用部分,
重点来了:
点击进去我们能够看到一段很长的代码,前面的一大堆处理略过,来看重点部分,
RACKVOTrampoline *trampoline = [[RACKVOTrampoline alloc] initWithTarget:self observer:strongObserver keyPath:keyPathHead options:trampolineOptions block:^(id trampolineTarget, id trampolineObserver, NSDictionary *change) { if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { [firstComponentDisposable() dispose]; if ((options & NSKeyValueObservingOptionPrior) != 0) { block([trampolineTarget valueForKeyPath:keyPath], change, NO, keyPathHasOneComponent); } return; } if (value == nil) { block(nil, change, NO, keyPathHasOneComponent); return; } RACDisposable *oldFirstComponentDisposable = [firstComponentSerialDisposable swapInDisposable:[RACCompoundDisposable compoundDisposable]]; [oldFirstComponentDisposable dispose]; addDeallocObserverToPropertyValue(value); if (keyPathHasOneComponent) { block(value, change, NO, keyPathHasOneComponent); return; } addObserverToValue(value); block([value valueForKeyPath:keyPathTail], change, NO, keyPathHasOneComponent); }]; --------------------------------------------------------------- --------------------------------------------------------------- NSObject *value = [self valueForKey:keyPathHead]; if (value != nil) { addDeallocObserverToPropertyValue(value); if (!keyPathHasOneComponent) { addObserverToValue(value); } } if ((options & NSKeyValueObservingOptionInitial) != 0) { id initialValue = [self valueForKeyPath:keyPath]; NSDictionary *initialChange = @{ NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting), NSKeyValueChangeNewKey: initialValue ?: NSNull.null, }; block(initialValue, initialChange, NO, keyPathHasOneComponent); } RACCompoundDisposable *observerDisposable = strongObserver.rac_deallocDisposable; RACCompoundDisposable *selfDisposable = self.rac_deallocDisposable; [observerDisposable addDisposable:disposable]; [selfDisposable addDisposable:disposable]; return [RACDisposable disposableWithBlock:^{ [disposable dispose]; [observerDisposable removeDisposable:disposable]; [selfDisposable removeDisposable:disposable]; }];
上面一部分代码可以按分割线分成上下两部,可以看出上部分是KVO实现监听的部分,下面一部分是处理销毁的逻辑。
我们先分析监听上部分这段代码的逻辑,上面这段代码块还是只做中间层传值,RAC又封装了一个中间层对象RACKVOTrampoline,并且由这个对象实现了KVO的监听。点击就进入了RACKVOTrampoline对象的.m实现文件,下面是这个.m的全部代码,这部分代码的解析我直接写在代码中便于分析:
#import "RACKVOTrampoline.h" #import "NSObject+RACDeallocating.h" #import "RACCompoundDisposable.h" #import "RACKVOProxy.h" @interface RACKVOTrampoline () @property (nonatomic, readonly, copy) NSString *keyPath; @property (nonatomic, readonly, copy) RACKVOBlock block; @property (nonatomic, readonly, unsafe_unretained) NSObject *unsafeTarget; @property (nonatomic, readonly, weak) NSObject *weakTarget; @property (nonatomic, readonly, weak) NSObject *observer; @end @implementation RACKVOTrampoline #pragma mark Lifecycle - (instancetype)initWithTarget:(__weak NSObject *)target observer:(__weak NSObject *)observer keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block { NSCParameterAssert(keyPath != nil); NSCParameterAssert(block != nil); NSObject *strongTarget = target; if (strongTarget == nil) return nil; self = [super init]; _keyPath = [keyPath copy]; _block = [block copy]; _weakTarget = target; _unsafeTarget = strongTarget; _observer = observer; ////1.此处是系统原生的的KVO方法,添加监听,RAC又做了额外的处理,又封装了一个单例中间层对象RACKVOProxy,把当前的vc和keypath,并由RACKVOProxy来监听RACKVOTrampoline的keyPath属性,相当于把代理移交给了这个RACKVOProxy单例中间层对象 [RACKVOProxy.sharedProxy addObserver:self forContext:(__bridge void *)self]; [strongTarget addObserver:RACKVOProxy.sharedProxy forKeyPath:self.keyPath options:options context:(__bridge void *)self]; [strongTarget.rac_deallocDisposable addDisposable:self]; [self.observer.rac_deallocDisposable addDisposable:self]; return self; } - (void)dealloc { [self dispose]; } #pragma mark Observation //3/释放代码,当前RACKVOTrampoline对象在销毁的时候,会进行移除单例中间层监听对象RACKVOProxy,这里通过信号量生命周期分析得出,信号在销毁的时候,会调用这个dispose,然后取消信号的调用同时取消监听移除RACKVOProxy代理者 - (void)dispose { NSObject *target; NSObject *observer; @synchronized (self) { _block = nil; // The target should still exist at this point, because we still need to // tear down its KVO observation. Therefore, we can use the unsafe // reference (and need to, because the weak one will have been zeroed by // now). target = self.unsafeTarget; observer = self.observer; _unsafeTarget = nil; _observer = nil; } [target.rac_deallocDisposable removeDisposable:self]; [observer.rac_deallocDisposable removeDisposable:self]; [target removeObserver:RACKVOProxy.sharedProxy forKeyPath:self.keyPath context:(__bridge void *)self]; [RACKVOProxy.sharedProxy removeObserver:self forContext:(__bridge void *)self]; } //2、此处是系统原生的KVO代理实现,并且通过Block把KVO监听到的值传出去- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context != (__bridge void *)self) { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; return; } RACKVOBlock block; id observer; id target; @synchronized (self) { block = self.block; observer = self.observer; target = self.weakTarget; } //在传出值得做了判断,target不存在的时候,就不传值出去了。否则就把改变的值传出去,通过三次的block代码块回传,传到VC的subscribeNext订阅保存的代码块里,供开发者使用! if (block == nil || target == nil) return; block(target, observer, change); } @end
这样一来,整个流程就很清楚了,RACKVO的设计,首先是集成RACDisposable的子类RACKVOTrampoline,把要监听的对象和keyPath传入封装的信号的子类,实现原生KVO监听,并且考虑到了整体架构的灵活度,又实现了RACKVOProxy类来移交监听,在RACKVOTrampoline系统KVO代理中,利用代码块把改变的值,通过订阅信号时保存的block传出去,在开发者层面上,我们只能看到逻辑紧凑并且简单易用的使用部分。
设计者设计的时候,实现了很多NSObject的分类,但是并不是提供给所有对象使用的,这就是中间层变量的好处了,通过中间层对象单独实现这些分类,整个框架和思路灵活度非常高,代码没有耦合部分,这也是我们需要学习的细节,以后我们在架构项目和设计项目的时候,可以利用这种中间层变量的思想,既能解耦代码,灵活度又非常高,这也是一个好的架构师必备的技能思想。
最后再来顺便瞅瞅RACProxy:
下面是对RACProxy代码部分的分析,主要是初始化了一个表,把observer和context以keyValue的形式存在表里,然后添加的时候设置到表里,移除的时候用key移除,这样PACProxy这个中间层的使用就很灵活,能用于RAC的任何类,可以做到多重自由使用并且利用中间层设计完全可以避免循环引用问题
#import "RACKVOProxy.h" @interface RACKVOProxy() @property (strong, nonatomic, readonly) NSMapTable *trampolines; @property (strong, nonatomic, readonly) dispatch_queue_t queue; @end @implementation RACKVOProxy + (instancetype)sharedProxy { static RACKVOProxy *proxy; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ proxy = [[self alloc] init]; }); return proxy; } - (instancetype)init { self = [super init]; _queue = dispatch_queue_create("org.reactivecocoa.ReactiveObjC.RACKVOProxy", DISPATCH_QUEUE_SERIAL); _trampolines = [NSMapTable strongToWeakObjectsMapTable]; return self; } - (void)addObserver:(__weak NSObject *)observer forContext:(void *)context { NSValue *valueContext = [NSValue valueWithPointer:context]; dispatch_sync(self.queue, ^{ [self.trampolines setObject:observer forKey:valueContext]; }); } - (void)removeObserver:(NSObject *)observer forContext:(void *)context { NSValue *valueContext = [NSValue valueWithPointer:context]; dispatch_sync(self.queue, ^{ [self.trampolines removeObjectForKey:valueContext]; }); } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSValue *valueContext = [NSValue valueWithPointer:context]; __block NSObject *trueObserver; dispatch_sync(self.queue, ^{ trueObserver = [self.trampolines objectForKey:valueContext]; }); if (trueObserver != nil) { [trueObserver observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } @end
下面是整个RACKVO设计思路总结图,调来调去,花了我整整一下午时间(=@__@=)

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
CoordinatorLayout滑动抖动问题
目录介绍 01.CoordinatorLayout滑动抖动问题描述 02.滑动抖动问题分析 03.自定义AppBarLayout.Behavior说明 04.CoordinatorLayout滑动抖动解决方案 05.案例测试是否根本问题 好消息 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢! 链接地址:https://github.com/yangchong211/YCBlogs 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变! 01.CoordinatorLayout滑动抖动问题描述 先看下布局 <android.support.design.widget.CoordinatorLayout xmlns:android="http://s...
- 下一篇
6年iOS开发被裁员,是行业的饱和还是经验根本不值钱?
前言: 最近看到很多iOS开发由于公司裁员而需要重新求职的。他们普遍具有4年甚至更长的工作经验。但求职结果往往都不太理想。 我在与部分iOS开发者交谈的过程中发现,很多人的工作思路不清晰,技能不扎实,没有持续学习的习惯,但对于未来的预期都很高。 由于工作年限较长,他们普遍认为工资就是应该随着工作年限增长而不断提升的。但事实却是:你的工资不是和你的工作年限成正比,而是和你的不可替代性成正比。 作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要这是一个我的iOS交流群:638302184,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长! 一, 在公司,你是无可替代还是可有可无 我的一个iOS开发朋友,大学毕业后进入一家企业做iOS开发。 1,6年前,他刚入职那会儿,公司效益非常不错。 刚入职时他对工作充满好奇,他本身就是科班出身,为了能尽快熟悉公司业务,他白天跟着 Team Leader 学习工作流程,晚上回家拼命看书,恶补 iOS底层相关知识。 他人很机灵,项目组的前辈们也愿意点拨他,所以,他手很快。, 2,工作的第二年,他对自身项...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7,8上快速安装Gitea,搭建Git服务器