轉載:探究ReactiveCocoa 底層KVO封裝流程

原文作者:溪浣雙鯉
原文地址:https://www.jianshu.com/p/51758229b4a5

一、對比原生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使用分爲三個步驟:

1、添加監聽
2、實現監聽的代理方法
3、移除監聽

但是RACKVO只是用了非常簡單的一段代碼就實現了以上的這三個步驟,去掉了膠水代碼,真正的做到了面向業務開發,那它是怎麼實現的呢,接下來我們來一層層分析

二、深入RAC底層逐層探究KVO實現

1、點擊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使用了信號量來處理監聽,結合之前信號量生命週期(傳送門),此處創建了信號,然後把這個信號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設計思路總結圖,調來調去,花了我整整一下午時間(=@__@=)

iOSSir公衆號技術交流微信羣!
需要進羣可以添加公衆號助理“kele22558!”

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章