RAC(ReactiveCocoa)介绍(七)——信号销毁

2019-04-02 14:59:15 浏览数 (1)

这一篇讲主要针对RACSignal信号销毁进行探究

在RACSignal信号发送命令执行之后,本着谁创建谁销毁的原则,最后一步必须要进行销毁操作。而销毁操作的执行则由RACDisposable类来完成。 RACDisposable类在RAC中作为一个父类,由三种子类继承自它。RACCompoundDisposableRACSerialDisposable以及RACKVOTrampoline。 首先来看下RACDisposable类在执行销毁disposableWithBlock方法时的操作。

代码语言:javascript复制
  (instancetype)disposableWithBlock:(void (^)(void))block {
    return [[self alloc] initWithBlock:block];
}

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

    self = [super init];

    _disposeBlock = (void *)CFBridgingRetain([block copy]); 
    OSMemoryBarrier();

    return self;
}

可以看到,将销毁信号中的代码块进行了保存操作,赋值给了_disposeBlock。 而_dispostBlock的声明方式为:void * volatile _disposeBlock volatile的作用是,每次取得数值的方式都是直接从内存中读取

(void *)CFBridgingRetain( )代码是Objective-C与C语言进行桥接的方法,使用__bridge_retained方法自行管理内存。 桥接方法有三种:__bridge、__bridge_retained、__bridge_transfer __bridge是将Objective-C转换成C语言,OC对象交给CF对象同时其所有权不变化 __bridge_retained将Objective-C转换成C语言,OC对象将所有权交给CF对象,但会解除自动管理内存机制ARC的所有权,意味着要自行进行内存管理。当管理对象需要释放时,必须要执行CFBridgingRelease方法来手动释放。

__bridge_retained内部方法

CFBridgingRelease方法的内部实现,是为第三种方法__bridge_transfer的执行,将CF对象的所有权交给OC对象,给予管理对象自动管理内存机制ARC的所有权。

__bridge_transfer内部方法

此处为什么将该block转换成C函数?将Objective-C对象转换成C函数的,而C函数可以直接拿到相应的函数指针,拿到函数指针之后就可以指向任意类型,即重定向指针。此处重定向指针之后,会在dispose方法进行指针处理。

OSMemoryBarrier();被称为内存屏障,为了保证相应的对象按顺序依次执行。 类似的,在dispose方法中使用到了OSAtomicCompareAndSwapPtrBarrier( )方法

OSAtomicCompareAndSwapPtrBarrier( )方法内部实现

对比第一个oldValue与 & value是否相等,若相等则返回BOOL值YES,并把第二个newValue赋值给 & value。

代码语言:javascript复制
- (void)dispose {
    void (^disposeBlock)(void) = NULL;

    while (YES) {
        void *blockPtr = _disposeBlock;
        if (OSAtomicCompareAndSwapPtrBarrier(blockPtr, NULL, &_disposeBlock)) {
            if (blockPtr != (__bridge void *)self) {
                disposeBlock = CFBridgingRelease(blockPtr);
            }

            break;
        }
    }

    if (disposeBlock != nil) disposeBlock();
}

OSAtomicCompareAndSwapPtrBarrier( )方法将_disposeBlock赋值给的blockPtr_disposeBlock进行对比,如果相等就将_disposeBlock赋值为NULL,同时将blockPtr释放销毁,此处写法作用是将_disposeBlock置为NULL的操作,同时进入下一步判断blockPtr是否与self相同,若不同则将blockPtr的OC对象赋值给disposeBlock。 那么,判断局部变量disposeBlock不为nil,意味着还存在销毁者,还不需要执行销毁操作,则继续执行disposeBlock( ),即销毁信号block中的代码块。

在dispose方法中,当OSAtomicCompareAndSwapPtrBarrier( )方法判断_disposeBlockblockPtr不相同时,_disposeBlock无法赋值为NULL,就无法执行下一步操作。那么就在dealloc方法中执行置为NULL以及释放操作。

代码语言:javascript复制
- (void)dealloc {
    if (_disposeBlock == NULL || _disposeBlock == (__bridge void *)self) return;

    CFRelease(_disposeBlock);
    _disposeBlock = NULL;
}

释放CF对象_disposeBlock,同时将其置为NULL。 销毁信号的整个操作,并不需要外部进行管理,全部由内部执行操作完成,让开发更专注于业务逻辑。 销毁过程中,是通过手动 自动释放来共同进行内存释放管理。

在发送信号的三种执行方法实现中,sendNext方法没有实现[self.disposable dispose],而sendErrorsendCompleted方法却实现了。

发送信号方法实现区别

在dispose方法中,会有while(YES)的死循环,用于不断寻找销毁对象,直到找到为止,并将其销毁置空掉。 而sendNext方法并不意味着创建信号的代码块已执行结束完成,当创建信号的代码块中所有代码都已执行完成,但未实现[self.disposable dispose]方法,那么就会去执行dealloc方法。

扩展一下: 在控制器创建销毁信号时,若创建了一个RACDisposable类的成员变量,将其放入销毁信号return中。因为持有该销毁信号对象的是当前类,在RAC信号销毁过程中内部方法无法对其进行销毁操作,最终会导致内存泄漏问题

销毁信号使用成员变量而非临时变量,导致的内存泄漏

0 人点赞