ReactiveCocoa之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创建的信号。
声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。
- 上一篇: ReactiveCocoa之RAC合并(九)
- 下一篇:没有了