RAC 中的 RACObserve 监听流程分析

我们都知道 ReactiveObjC 的编程思想主要是 响应式编程 的概念 , 而 RACObserve 是使用了原生的 KVO响应式编程 相结合的产物。

现在开始对 RACObserve 的监听流程进行分析,现在VC声明一下属性,如下:

//ViewController.m
@property (nonatomic, strong) Dog *dog;
@property (nonatomic, copy) NSString *name;

我们使用的 RACObserve 是一个宏定义,里面传入的参数为:

RACObserve(TARGET, KEYPATH)

[RACObserve(self, name) subscribeNext:^(id  _Nullable x) {      
    // 这里会得到被监听值的最终结果
  }];

那么这个宏定义 RACObserve 实际又是一个什么东西呢?我们看一下源码:


#define _RACObserve(TARGET, KEYPATH) \
({ \
//   把传入的被观察对象进行了 weak 引用
    __weak id target_ = (TARGET); \
    [target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) 
        observer:self]; \
})

#if __clang__ && (__clang_major__ >= 8)
#define RACObserve(TARGET, KEYPATH) _RACObserve(TARGET, KEYPATH)
#else
#define RACObserve(TARGET, KEYPATH) \
({ \
    _Pragma("clang diagnostic push") \
    _Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
    _RACObserve(TARGET, KEYPATH) \
    _Pragma("clang diagnostic pop") \
})
#endif

RACObserve 实质是继续调用了 _RACObserve(TARGET, KEYPATH)

#define _RACObserve(TARGET, KEYPATH) \
({ \
    __weak id target_ = (TARGET); \
    [target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
})
// 是  NSObject+RACPropertySubscribing.h 里的两个方法
[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self];

NSObject+RACPropertySubscribing

看第一个方法即可 OS_OBJECT_HAVE_OBJC_SUPPORT

//  NSObject+RACPropertySubscribing.h
#if OS_OBJECT_HAVE_OBJC_SUPPORT
- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath 
                           observer:(__weak NSObject *)observer;
#else
// Swift builds with OS_OBJECT_HAVE_OBJC_SUPPORT=0 for Playgrounds and LLDB :(
- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath 
                           observer:(NSObject *)observer;
#endif

这里实际是封装了一个 RACSignal 对象,外部调用的 RACObserve(TARGET, KEYPATH) 是返回一个(RACSignal *)对象

// NSObject+RACPropertySubscribing.m
- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath 
                          observer:(__weak NSObject *)observer {
/*
  这里实际是封装了一个 RACSignal 对象, 
  RACObserve() 的宏返回的是 (RACSignal *)
*/
    return [[[self  rac_valuesAndChangesForKeyPath:keyPath                                   options:NSKeyValueObservingOptionInitial
                    observer:observer]
        map:^(RACTuple *value) {
            return value[0];
        }]
setNameWithFormat:@"RACObserve(%@, %@)", RACDescription(self), keyPath];
}


- (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;

// 创建了一个 销毁信号量, 假如:vc 调用了 RACObserve(TARGET, KEYPATH),但vc被销毁了,这个信号就会发出一个销毁信号
    RACSignal *deallocSignal = [[RACSignal
        zip:@[
            self.rac_willDeallocSignal,
            strongObserver.rac_willDeallocSignal ?: [RACSignal never]
        ]]
        doCompleted:^{
            [objectLock lock];
            @onExit {
                [objectLock unlock];
            };
        }];

    //  takeUntil:deallocSignal  判断当前观察者是否销毁了,如果销毁了就不会对进入以下监听工作
    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) {
                /*
                    将得到的新值通过block回调到这里,
                    并使用 sendNext 发送出去外部订阅的 subscribeNext
                 */
                [subscriber sendNext:RACTuplePack(value, change)];
            }];
        }]
        takeUntil:deallocSignal]
        setNameWithFormat:@"%@ -rac_valueAndChangesForKeyPath: %@ options: %lu observer: %@", 
RACDescription(self), keyPath, (unsigned long)options, RACDescription(strongObserver)];
}

// 点击此方法,跳转到 NSObject+RACKVOWrappe.m
[self rac_observeKeyPath:keyPath options:options observer:observer 
 block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
                [subscriber sendNext:RACTuplePack(value, change)];
            }];

NSObject+RACKVOWrapper

// NSObject+RACKVOWrapper.m
- (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath 
                    options:(NSKeyValueObservingOptions)options 
                    observer:(__weak NSObject *)weakObserver 
                    block:(void (^)(id, NSDictionary *, BOOL, BOOL))block

此方法上半部分做了一大堆关于 Disposable 销毁信号准备工作 ,下半部分做了销毁操作等等,这里就不作过多的分析了;

    /* 
          获取keyPath 的路由,
          比如 VC 有一个 Dog 类并监听 dog.name, RACObserve(vc,dog.name)
    */
    NSArray *keyPathComponents = keyPath.rac_keyPathComponents;
  // 获取 被监听对象的 "属性列表"
    objc_property_t property = class_getProperty(object_getClass(self), keyPathHead.UTF8String);

我们重点看 传值监听 过程,以下代码:

/*
  KVO: 面向对象 , 谁调用谁被监听
  此处把所有的相关参数传入,如:观察者,被观察对象,keyPath 等,
  用 RACKVOTrampoline 进行了封装,此类是监听工作开始的起点,block的回调得到监听的最终结果
*/
    RACKVOTrampoline *trampoline = 
                      [[RACKVOTrampoline alloc] initWithTarget:self 
                                              observer:strongObserver 
                                               keyPath:keyPathHead 
                                               options:trampolineOptions 
                                              block:^(id trampolineTarget, id trampolineObserver, NSDictionary *change) {   
/* 
RACKVOTrampoline.m 里的响应监听方法 
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
 会把新值 Block 到这里来
  */
        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  返回到 (NSObject + RACPropertySubscribing.m) 的信号量定义 block里, 并 sendNext
        block([value valueForKeyPath:keyPathTail], change, NO, keyPathHasOneComponent);
    }];

点击以上 RACKVOTrampoline 的实例方法 进入到 RACKVOTrampoline.m

RACKVOTrampoline

这里的重点在于:RACKVOProxy.sharedProxy 这个单例做了一个神秘操作,比如 被监听对象 有可能需要 观察多个对象或其他属性(上述代码中,被监听对象是 VC ,所以被监听的对象会有:name,dog 等等)。
可理解为:每一个被监听的对象封装成一个 RACKVOTrampoline 对象,
并把这些被已封装的 trampoline 对象全部添加到 sharedProxy 单例里面统一监听。

//RACKVOTrampoline.m
- (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;

    /*
     移交代理 --- 观察对象
    被监听对象 有可能需要观察多个对象或其他属性(如:VC的属性有 name,dog等等),
    可理解为:每一个被监听的对象封装成一个 RACKVOTrampoline 对象,
    并把这些被已封装的 trampoline 对象全部添加到 sharedProxy 单例里面统一监听,如下一行代码:
     */
    [RACKVOProxy.sharedProxy addObserver:self forContext:(__bridge void *)self];
    
    /*
     然而 strongTarget(VC) 只需要观察 RACKVOProxy.sharedProxy  ,
        并把相应的 keyPath 传给此单例即可 ,统一用 shareProxy 处理,如下一行代码:
    */
    [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

- (void)dispose {
    NSObject *target;
    NSObject *observer;

    @synchronized (self) {
        _block = nil;

        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];
}

- (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;
    }

    if (block == nil || target == nil) return;
    /*
      数值发生改变,然后调用 block 返回到 NSObject + RACKVOWrapper.m  这个类里面
      即: RACKVOTrampoline *trampoline 实例化的 block 里面
     */
    block(target, observer, change);
}
@end

RACKVOProxy

我们再看一下 RACKVOProxy.sharedProxy 这个单例里面有做了些什么操作:
RACKVOProxy.m 声明了两个属性,分别是: NSMapTable *trampolines用于保存 RACKVOTrampoline对象;dispatch_queue_t queue用于确保线性安全

// RACKVOProxy.m

@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];
    // 把被观察的对象 放到 trampolines(表) 里去
    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) {
        /*
         使用对应的 RACKVOTrampoline 对象返回出去给 “RACKVOTrampoline” 这个类中的
          - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
         */
        [trueObserver observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

@end

发生回调时响应以下数据,分别是修改前、修改后的数据(为了不产生误会,说明一下以下两张图可以忽略掉其他控件的属性,我只抽出了 dog、name 来说明)

image.png
image.png

然后会通过

 [trueObserver observeValueForKeyPath:keyPath ofObject:object change:change context:context];

发送出去 RACKVOTrampoline.m

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 

以下附上一张流程图:


image.png

以上内容纯粹个人见解,仅用于分享交流;如有描述不当之处,欢迎指出。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335