ReactiveCocoa学习笔记六-RACDisposable源码分析

RACDisposable算是三个核心组件里面最简单的了, 除了自身外, 还有4个子类:
RACKVOTrampoline, RACScopedDisposable 以及在Signal中已经介绍过的RACCompoundDisposable和RACSerialDisposable. 这一部分的代码还是比较好理解的, 就是有很多底层一些的代码, 所以需要多查查文档.

RACDisposable

头文件对这个类的描述就是对订阅关系的取消和相关资源的清理, 我们就看看它是怎么清理的.

// RACDisposable.h
// 标记是否已经清除的状态
@property (atomic, assign, getter = isDisposed, readonly) BOOL disposed;
+ (instancetype)disposableWithBlock:(void (^)(void))block;
- (void)dispose;
- (RACScopedDisposable *)asScopedDisposable; // 转换为RACScopedDisposable

属性和方法的意义还是比较明显的, 然后看实现文件也比较"言简意赅", 大致意思是存储起来dispose需要的block, 在调用时执行这个block(如果block不为空)[注意, 如果不是所有的RACDisposable在dealloc时都会自动调用dispose哟]. 但是里面的实现用到了很多C语言和一些非ARC的东西, 所以看起来比较高深.

先看生命周期函数:

// 一个实际上没有什么可清除的disposable, 基本上可以看做一个标记是否已dispose的值
- (id)init {
    self = [super init];
    if (self == nil) return nil;

    // 需要注意: 如果没有block, _disposeBlock会指向自己, 所以后续操作都会进行判断是否等于自己, 因此_disposeBlock的类型也是void *. 之所以不是id类型, 是因为会引用自己, 这样就循环引用了. 
    _disposeBlock = (__bridge void *)self;
    // OSMemoryBarrier是确保前面的代码执行完了之后再执行后面的, 
    // 这个在多核CPU中可以保证安全
    OSMemoryBarrier();

    return self;
}

- (id)initWithBlock:(void (^)(void))block {
    NSCParameterAssert(block != nil);

    self = [super init];
    if (self == nil) return nil;
    
    // 这里其实不是真正的retain, 只是一个桥接, 把Objc类型转换为CoreFoundation类型(也就是C类型), 因为_disposeBlock是void *类型
    _disposeBlock = (void *)CFBridgingRetain([block copy]); 
    OSMemoryBarrier();

    return self;
}

- (void)dealloc {
    // 为空和等于自身都不需要额外释放或者设置NULL
    if (_disposeBlock == NULL || _disposeBlock == (__bridge void *)self) return;

    CFRelease(_disposeBlock);
    _disposeBlock = NULL;
}

核心的dispose也是一个看起来有点难的函数, 并不像我们想的直接判断_disposeBlock后调用.

- (void)dispose {
    void (^disposeBlock)(void) = NULL;
    

    while (YES) {
        void *blockPtr = _disposeBlock;
        // 应该又是多核作祟, 所以又要while YES, 又要刚赋值又compare
        // 这里blockPtr和_disposeBlock对比匹配后, 会把_disposeBlock赋值为NULL
        // 所以isDisposed只需要判断_disposeBlock == NULL即可
        if (OSAtomicCompareAndSwapPtrBarrier(blockPtr, NULL, &_disposeBlock)) {
            // 如果不等于self, 说明外面有block传进来
            if (blockPtr != (__bridge void *)self) {
                //CoreFoundation类型转换为Objc类型
                disposeBlock = CFBridgingRelease(blockPtr);
            }

            break;
        }
    }
    // 执行block(如果有)
    if (disposeBlock != nil) disposeBlock();
}

RACScopedDisposable

RACDisposable最后有个asScopedDisposable方法, 就是直接返回了一个RACScopedDisposable对象, 与父类的区别就是, 这个类的实例在dealloc时会调用dispose.

下面就是全部代码, 还是比较明显的

+ (instancetype)scopedDisposableWithDisposable:(RACDisposable *)disposable {
    // 自身没有什么好释放的, 直接调用参数的dispose
    return [self disposableWithBlock:^{
        [disposable dispose];
    }];
}

- (void)dealloc {
    [self dispose];
}

#pragma mark RACDisposable
// 重写父类的方法返回自己, 不然再次调用就又换一个实例
- (RACScopedDisposable *)asScopedDisposable {
    // totally already are
    return self;
}

RACKVOTrampoline

头文件中介绍这类对象用于KVO观察模式中. 所以最好还是和KVO相关的信号一起看, 但是代码也并不复杂, 所以可以先行探讨一下.

子类头文件只有一个方法:

- (id)initWithTarget:(__weak NSObject *)target observer:(__weak NSObject *)observer keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block;

私有属性:

@interface RACKVOTrampoline ()

// 正在观察的keyPath
@property (nonatomic, readonly, copy) NSString *keyPath;

// 回调block
@property (nonatomic, readonly, copy) RACKVOBlock block;
// unsafe_unretained意味着这个属性要纯粹自己管理, 编译器不会对这个属性做任何ARC操作
@property (nonatomic, readonly, unsafe_unretained) NSObject *unsafeTarget;
@property (nonatomic, readonly, weak) NSObject *weakTarget;
@property (nonatomic, readonly, weak) NSObject *observer;

@end

方法实现:

- (id)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];
    if (self == nil) return nil;

    _keyPath = [keyPath copy];

    _block = [block copy];
    _weakTarget = target;
    _unsafeTarget = strongTarget;
    _observer = observer;

    // 在RACKVOProxy.sharedProxy注册观察者 传入context是过滤掉不属于自己范围的回调, 里面涉及到RACKVOProxy.sharedProxy的实现, 这个之后再看
    [RACKVOProxy.sharedProxy addObserver:self forContext:(__bridge void *)self];
    // 真正监听的是RACKVOProxy.sharedProxy
    [strongTarget addObserver:RACKVOProxy.sharedProxy forKeyPath:self.keyPath options:options context:(__bridge void *)self];

    // 在strongTarget和observer被释放后, 要执行自己的dispose
    [strongTarget.rac_deallocDisposable addDisposable:self];
    [self.observer.rac_deallocDisposable addDisposable:self];

    return self;
}

// 和scoped一样, 在dealloc时会调用dispose
- (void)dealloc {
    [self dispose];
}

本身也在KVOProxy中注册了观察者, 所以肯定还有回调方法:


- (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(target, observer, change);
}

看看作为一个Disposable的关键方法:

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

    // 主要是清理持有的对象
    @synchronized (self) {
        _block = nil;
        // target虽然是unsafe的, 但是这个时候target应该还未释放, 因为在init方法中, 对target的rac_deallocDisposable进行了add self的操作, 如果要释放了, 则会自动调用这方法
        target = self.unsafeTarget;
        observer = self.observer;

        _unsafeTarget = nil;
        _observer = nil;
    }

    // 移除添加的disposable
    [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];
}

RACCompoundDisposable

本质上可以看做一个Disposable数组, 在dispose的时候会把组合进来的Disposable一个个调用dispose.

内部的实现用了C数组和CoreFoundation的数组而不是一个Objc的Array, 原因应该一是性能方面的考虑, 因为CompoundDisposable用的很频繁, 二是会持有这个Disposable, 有一些类型的Disposable是在dealloc的时候要dispose的, 如果持有则会干扰这个行为.

同时, 实现还用了一个魔数长度的固定大小数组, 更多的disposable会额外存储在一个CoreFoundation创建的数组中. 有2个问题:

  1. 为什么要分为2个部分?
  2. 为什么这个数字是2?

第一个问题的答案是性能, 用CoreFoundation是比较耗时的, 而直接用一个固定大小的C数组会更快.
第二个问题的答案还是性能, 只不过这个是作者综合测算出来的.

#define RACCompoundDisposableInlineCount 2

基于上面的缘故, 这里实现会看起来比较复杂一点, 但是实际上还是比较好懂的, 看成对数组的操作即可.

- (id)initWithDisposables:(NSArray *)otherDisposables {
    self = [self init];
    if (self == nil) return nil;

    #if RACCompoundDisposableInlineCount
    [otherDisposables enumerateObjectsUsingBlock:^(RACDisposable *disposable, NSUInteger index, BOOL *stop) {
        // 前2个存在inline数组中, 也就是C数组
        _inlineDisposables[index] = disposable;
        // 超过2个的先不管
        if (index == RACCompoundDisposableInlineCount - 1) *stop = YES;
    }];
    #endif

    // 大于2个的创建CoreFoundation数组来管理
    if (otherDisposables.count > RACCompoundDisposableInlineCount) {
        _disposables = RACCreateDisposablesArray();
        // 写入数据
        CFRange range = CFRangeMake(RACCompoundDisposableInlineCount, (CFIndex)otherDisposables.count - RACCompoundDisposableInlineCount);
        CFArrayAppendArray(_disposables, (__bridge CFArrayRef)otherDisposables, range);
    }

    return self;
}

// dealloc不会自动调用dispose
- (void)dealloc {
    #if RACCompoundDisposableInlineCount
    for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
        _inlineDisposables[i] = nil;
    }
    #endif

    if (_disposables != NULL) {
        CFRelease(_disposables);
        _disposables = NULL;
    }
}

添加/删除Disposable的操作:

- (void)addDisposable:(RACDisposable *)disposable {
    NSCParameterAssert(disposable != self);
    if (disposable == nil || disposable.disposed) return;

    BOOL shouldDispose = NO;

    // 保证线程安全
    OSSpinLockLock(&_spinLock);
    {
        // 如果此时已经dispose掉了, 那么也要对该disposable调用dispose
        if (_disposed) {
            shouldDispose = YES;
        } else {
            // 主体代码:
            // 1. 先找找inline数组里面有没有空位
            #if RACCompoundDisposableInlineCount
            for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
                if (_inlineDisposables[i] == nil) {
                    // 有就赋值, 然后跳到后面去执行
                    _inlineDisposables[i] = disposable;
                    goto foundSlot;
                }
            }
            #endif
            // 2. 如果已经满额, 就加入到CoreFoundation中
            if (_disposables == NULL) _disposables = RACCreateDisposablesArray();
            CFArrayAppendValue(_disposables, (__bridge void *)disposable);

            // 下面是加入了DTrace探针的代码? 
            if (RACCOMPOUNDDISPOSABLE_ADDED_ENABLED()) {
                RACCOMPOUNDDISPOSABLE_ADDED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount);
            }

        #if RACCompoundDisposableInlineCount
        foundSlot:;
        #endif
        }
    }
    OSSpinLockUnlock(&_spinLock);
    // 在锁外面调用, 防止递归使用CompoundDisposable
    if (shouldDispose) [disposable dispose];
}

// remove的操作基本差不多, 主要是一些C函数的使用
- (void)removeDisposable:(RACDisposable *)disposable {
    if (disposable == nil) return;

    OSSpinLockLock(&_spinLock);
    {
        if (!_disposed) {
            #if RACCompoundDisposableInlineCount
            for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
                               // 这里既然已经找到了, 为什么不跳到最后呢? 难道就是为了少写一对大括号么,[手动抠鼻]
                if (_inlineDisposables[i] == disposable) _inlineDisposables[i] = nil;
            }
            #endif

            if (_disposables != NULL) {
                CFIndex count = CFArrayGetCount(_disposables);
                for (CFIndex i = count - 1; i >= 0; i--) {
                    const void *item = CFArrayGetValueAtIndex(_disposables, i);
                    if (item == (__bridge void *)disposable) {
                        CFArrayRemoveValueAtIndex(_disposables, i);
                    }
                }

                if (RACCOMPOUNDDISPOSABLE_REMOVED_ENABLED()) {
                    RACCOMPOUNDDISPOSABLE_REMOVED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount);
                }
            }
        }
    }
    OSSpinLockUnlock(&_spinLock);
}

至于核心的dispose, 显然就会是2个数组的遍历了:

- (void)dispose {
    // 1. 把2个数组copy出来到本地
    // 这么做一是可以保证线程安全(局部变量是线程安全的)
    // 而是可以尽早解开锁
    #if RACCompoundDisposableInlineCount
    RACDisposable *inlineCopy[RACCompoundDisposableInlineCount];
    #endif

    CFArrayRef remainingDisposables = NULL;

    OSSpinLockLock(&_spinLock);
    {
        _disposed = YES;

        #if RACCompoundDisposableInlineCount
        for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
            inlineCopy[i] = _inlineDisposables[i];
            _inlineDisposables[i] = nil;
        }
        #endif

        remainingDisposables = _disposables;
        _disposables = NULL;
    }
    OSSpinLockUnlock(&_spinLock);

    #if RACCompoundDisposableInlineCount
    // 遍历inline数组 调用dispose
    for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
        [inlineCopy[i] dispose];
    }
    #endif

    if (remainingDisposables == NULL) return;
    
    // 额外的Disposables要传入函数指针来遍历然后调用dispose
    CFIndex count = CFArrayGetCount(remainingDisposables);
    CFArrayApplyFunction(remainingDisposables, CFRangeMake(0, count), &disposeEach, NULL);
    CFRelease(remainingDisposables);
}

static void disposeEach(const void *value, void *context) {
    RACDisposable *disposable = (__bridge id)value;
    [disposable dispose];
}

整体来说上面的代码还是比较易懂的, 就是有很多C级别的代码, 很多API都比较底层.

RACSerialDisposable

Signal中说了对RACSerialDisposable调用setDisposable会有额外的一个效果, 旧的值如果已经被dispose了, 那么新的值也会立即dispose掉.

这个类的头文件也比较简单, 一个属性2个方法, 私有属性还有一些线程安全和状态以及辅助操作的声明(属性声明了disposable, 在拓展里面还要写RACDisposable * _disposable是因为实现方法重写了其getter方法--也就是disposable方法, 而在里面又要访问这个值, 为了避免造成递归, 所以需要直接访问, 而不能self.的形式访问).

直接看下这2个getter和setter

- (RACDisposable *)disposable {
    // 保证线程安全, 因为有可能会经常变化
    RACDisposable *result;

    OSSpinLockLock(&_spinLock);
    result = _disposable;
    OSSpinLockUnlock(&_spinLock);

    return result;
}

- (void)setDisposable:(RACDisposable *)disposable {
    [self swapInDisposable:disposable];
}

// 核心方法
- (RACDisposable *)swapInDisposable:(RACDisposable *)newDisposable {
    RACDisposable *existingDisposable;
    BOOL alreadyDisposed;

    OSSpinLockLock(&_spinLock);
    // 判断是否已经dispose过
    alreadyDisposed = _disposed;
    if (!alreadyDisposed) {
        // 如果没有, 则把新Disposable赋给属性, 局部变量存储老的Disposable
        existingDisposable = _disposable;
        _disposable = newDisposable;
    }
    OSSpinLockUnlock(&_spinLock);
    // 如果老的已经dispose过了, 那么新的也直接dispose掉, 返回空
    if (alreadyDisposed) {
        [newDisposable dispose];
        return nil;
    }
    // 返回老的Disposable
    return existingDisposable;
}

dispose方法则没什么特别:

- (void)dispose {
    RACDisposable *existingDisposable;

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

推荐阅读更多精彩内容