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

1、對比原生KVO,初識ReactiveCocoa的KVO


咱們先來看一段代碼,經過觸屏來動態修改視圖背景色react

@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只是用了很是簡單的一段代碼就實現了以上的這三個步驟,去掉了膠水代碼,真正的作到了面向業務開發,那它是怎麼實現的呢,接下來咱們來一層層分析bash

做爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個個人iOS交流羣:763164022,無論你是小白仍是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 你們一塊兒交流學習成長!架構

2、深刻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封裝思路中,最後一步的釋放時機應該是在這裏。dom

好,咱們接着分析中間部分的代碼,能夠看出的是,萬物皆信號---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的調用部分, 重點來了: 點擊進去咱們可以看到一段很長的代碼,前面的一大堆處理略過,來看重點部分,ui

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實現監聽的部分,下面一部分是處理銷燬的邏輯。this

咱們先分析監聽上部分這段代碼的邏輯,上面這段代碼塊仍是隻作中間層傳值,RAC又封裝了一箇中間層對象RACKVOTrampoline,而且由這個對象實現了KVO的監聽。點擊就進入了RACKVOTrampoline對象的.m實現文件,下面是這個.m的所有代碼,這部分代碼的解析我直接寫在代碼中便於分析:atom

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

//二、此處是系統原生的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設計思路總結圖,調來調去,花了我整整一下午時間(=@__@=)

原文地址:www.jianshu.com/p/51758229b…

相關文章
相關標籤/搜索