RAC和内存管理

作者: Pariscode | 来源:发表于2015-07-25 23:24 被阅读8793次

    (转载注明来自:http://hparis.github.io/blog/2015/07/25/rache-nei-cun-guan-li/

    最近在用RAC的时候发现自己对内存管理还是有些困惑,于是自己写了一些代码来验证自己的一些理解。

    在一开始接触RAC的时候,我们知道RAC对于block都是copy赋值的。

    @implementation RACSignal
    
    #pragma mark Lifecycle
    
    + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
        return [RACDynamicSignal createSignal:didSubscribe];
    }
    
    
    
    @implementation RACDynamicSignal
    
    #pragma mark Lifecycle
    
    + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
        RACDynamicSignal *signal = [[self alloc] init];
        signal->_didSubscribe = [didSubscribe copy];
        return [signal setNameWithFormat:@"+createSignal:"];
    }
    

    在创建RACSingal的时候会调用其子类RACDynamicSignal去创建,我们也看到RACDynamicSignal对didSuscribe这个block是进行了copy。所以大家可能会被要求注意循环引用的问题,于是大家都用@weakify(target)和@strongify(target)来避免循环引用的问题。那是不是所有用到RAC的地方都需要使用这些宏来避免循环引用的问题,不尽然。比如下面这个:

    // 场景1
    [RACObserve(self, title) subscribeNext:^(id x) {
    NSLog(@"%@", x);
    }];
    

    接下来,我们来对比一下几种用法

    @interface ViewController()
    @property (strong, nonatomic) ViewModel * viewModel;
    @end
    
    @implementation ViewController
    
    - (void)viewDidiLoad {
        [super viewDidLoad];
    
        self.viewModel = [ViewModel new];
    
        // 场景2
        dispatch_async(dispatch_get_main_queue(), ^{
            self.title = @"你好";
        });
    
        // 场景3
        [self.viewModel.titleSignal subscribeNext:^(NSString * title) {
            self.title = title;
        }];
    
        // 场景4
        [RACObserve(self.viewModel, title) subscribeNext:^(NSString * title)     {
            self.title = title;
        }];
    }
    
    @end
    

    场景2是我们平常都会用到的,而且我们也没有在这种场景下去考虑循环引用的问题,这是因为dispatch的block不是属于self的(至于这个block是属于谁的,回头我再查点资料或者请各位指教),所以即使你在block使用了self也不会有循环应用的问题。

    场景3很明显是有循环引用的问题:self->viewModel->titleSignal->block->self,这个时候如果我们不做处理的话,那么self就永远不会被释放。正确的做法应该是使用@weakify(self)和@strongify(self):

    // 场景3
    @weakify(self);
    [self.viewModel.titleSignal subscribeNext:^(NSString * title) {
        @strongify(self);
        self.title = title;
    }];
    

    场景4在我们看来是没有问题的,因为这里看起来只有singal->block->self的引用,它们之间并没有造成循环引用的问题。我们来看看RACObserve的实现先:

    #define RACObserve(TARGET, KEYPATH) \\
    ({ \\
    _Pragma("clang diagnostic push") \\
    _Pragma("clang diagnostic ignored \\"-Wreceiver-is-weak\\"") \\
    __weak id target_ = (TARGET); \\
    [target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \\
    _Pragma("clang diagnostic pop") \\
    })
    
    - (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(__weak NSObject *)observer;
    

    其实,看到这里你会认为这里只是调用了一个方法创建了一个Signal,而且这个Signal也并不属于任何对象。我们再来看看具体的实现是怎么样的?

    - (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.ReactiveCocoa.NSObjectRACPropertySubscribing";
    
        __weak NSObject *weakSelf = self;
    
        RACSignal *deallocSignal = [[RACSignal zip:@[
                                self.rac_willDeallocSignal,
                                strongObserver.rac_willDeallocSignal ?: [RACSignal never]
        ]] doCompleted:^{
            // Forces deallocation to wait if the object variables are currently
            // being read on another thread.
            [objectLock lock];
            @onExit {
                [objectLock unlock];
            };
        }];
    
    return [[[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
        // Hold onto the lock the whole time we're setting up the KVO
        // observation, because any resurrection that might be caused by our
        // retaining below must be balanced out by the time -dealloc returns
        // (if another thread is waiting on the lock above).
        [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: %@", self.rac_description, keyPath, (unsigned long)options, strongObserver.rac_description];
    }
    

    重点观察deallocSignal和[signal takeUntile:deallocSignal],我们把deallocSignal单独拿出来看看:

    RACSignal *deallocSignal = [[RACSignal zip:@[
                            self.rac_willDeallocSignal,
                            strongObserver.rac_willDeallocSignal ?: [RACSignal never]
                            ]] doCompleted:^{
        // Forces deallocation to wait if the object variables are currently
        // being read on another thread.
        [objectLock lock];
        @onExit {
        [objectLock unlock];
        };
    }];
    

    这里的deallocSignal是只有在self和strongObserve都将要发生dealloc的时候才会触发的。即用RACObserve创建的信号只有在其target和observe都发生dealloc的时候才会被disposable(这个好像是RAC用来销毁自己资源的东西)。不明白的童鞋,我们回头来分析一下场景4的代码:

    // 场景4
    [RACObserve(self.viewModel, title) subscribeNext:^(NSString * title) {
        self.title = title;
    }];
    

    用RACObserve创建的信号看起来只要出了函数体其资源应该就会被回收,但是这个信号其实是只有在self.viewModel.rac_willDeallocSignal和self.rac_willDeallocSignal都发生的情况下才会被释放。所以场景4的引用关系看起来只有signal->block->self,但是这个signal只有在self.rac_willDeallocSignal的时候才会被释放。所以这里如果不打断这种关系的话就会造成循环引用的问题,正确做法应该是:

    // 场景4
    @weakify(self);
    [RACObserve(self.viewModel, title) subscribeNext:^(NSString * title) {
        @strongify(self);
        self.title = title;
    }];
    

    最后,在说一个特别需要注意的,就是UITableViewCell和UICollectionViewCell复用和RAC的问题。

    - (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return 1000;
    }
    
    - (UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
        UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"TableViewCell"];
    
        @weakify(self);
        [RACObserve(cell.textLabel, text) subscribeNext:^(id x) {
            @strongify(self);
            NSLog(@"%@", self);
        }];
    
        return cell;
    }
    

    我们看到这里的RACObserve创建的Signal和self之间已经去掉了循环引用的问题,所以应该是没有什么问题的。但是结合之前我们对RACObserve的理解再仔细分析一下,这里的Signal只要self没有被dealloc的话就不会被释放。虽然每次UITableViewCell都会被重用,但是每次重用过程中创建的信号确实无法被disposable。那我们该怎么做呢?

    - (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return 1000;
    }
    
    - (UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
        UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"TableViewCell"];
    
        @weakify(self);
        [[RACObserve(cell.textLabel, text) takeUntil:cell.rac_prepareForReuseSignal] subscribeNext:^(id x) {
            @strongify(self);
            NSLog(@"%@", self);
        }];
    
        return cell;
    }
    

    注意,我们在cell里面创建的信号加上takeUntil:cell.rac_prepareForReuseSignal,这个是让cell在每次重用的时候都去disposable创建的信号。

    以上所说的关于内存的东西我都用Instrument的Allocations验证过了,但是依旧建议大家自己也去试试。

    (PS:第一次这么认真写东西,如果有什么问题,欢迎指出!)

    相关文章

      网友评论

      • JoshuaC:你好,开头的链接失效了哦
        只是过路人:直接点可能不行,后面的字识别不全,需要复制粘贴可以看
        只是过路人:https://hparis.github.io/blog/2017/09/05/RAC和内存管理/
      • 举举手123:你好开头的链接失效了
        只是过路人:https://hparis.github.io/blog/2017/09/05/RAC和内存管理/
      • 洪哥:takeUntil:cell.rac_prepareForReuseSignal 这个方法受益匪浅
      • fake_roadster:感谢楼主
      • Link913:楼主你好,还是不是很理解,由于我水平的问题,看RAC框架源码也看不大懂,rac_willDeallocSignal这个信号什么时候回执行呢?
        Link913:@Touchs 你把它当作销毁的时候产生的一个信号就可以了,我也不算太懂,一直这么用的
        Touchs:你解决了吗? rac_willDeallocSignal什么时候能订阅到?
      • 没故事的卓同学:写的很棒,给我提供了不少帮助。

      本文标题:RAC和内存管理

      本文链接:https://www.haomeiwen.com/subject/ghrmqttx.html