(轉(zhuǎn)載注明來(lái)自:http://hparis.github.io/blog/2015/07/25/rache-nei-cun-guan-li/
最近在用RAC的時(shí)候發(fā)現(xiàn)自己對(duì)內(nèi)存管理還是有些困惑,于是自己寫(xiě)了一些代碼來(lái)驗(yàn)證自己的一些理解蝙昙。
在一開(kāi)始接觸RAC的時(shí)候逊脯,我們知道RAC對(duì)于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:"];
}
在創(chuàng)建RACSingal的時(shí)候會(huì)調(diào)用其子類(lèi)RACDynamicSignal去創(chuàng)建,我們也看到RACDynamicSignal對(duì)didSuscribe這個(gè)block是進(jìn)行了copy。所以大家可能會(huì)被要求注意循環(huán)引用的問(wèn)題,于是大家都用@weakify(target)和@strongify(target)來(lái)避免循環(huán)引用的問(wèn)題狭莱。那是不是所有用到RAC的地方都需要使用這些宏來(lái)避免循環(huán)引用的問(wèn)題,不盡然概作。比如下面這個(gè):
// 場(chǎng)景1
[RACObserve(self, title) subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
接下來(lái)腋妙,我們來(lái)對(duì)比一下幾種用法
@interface ViewController()
@property (strong, nonatomic) ViewModel * viewModel;
@end
@implementation ViewController
- (void)viewDidiLoad {
[super viewDidLoad];
self.viewModel = [ViewModel new];
// 場(chǎng)景2
dispatch_async(dispatch_get_main_queue(), ^{
self.title = @"你好";
});
// 場(chǎng)景3
[self.viewModel.titleSignal subscribeNext:^(NSString * title) {
self.title = title;
}];
// 場(chǎng)景4
[RACObserve(self.viewModel, title) subscribeNext:^(NSString * title) {
self.title = title;
}];
}
@end
場(chǎng)景2是我們平常都會(huì)用到的,而且我們也沒(méi)有在這種場(chǎng)景下去考慮循環(huán)引用的問(wèn)題讯榕,這是因?yàn)閐ispatch的block不是屬于self的(至于這個(gè)block是屬于誰(shuí)的辉阶,回頭我再查點(diǎn)資料或者請(qǐng)各位指教),所以即使你在block使用了self也不會(huì)有循環(huán)應(yīng)用的問(wèn)題瘩扼。
場(chǎng)景3很明顯是有循環(huán)引用的問(wèn)題:self->viewModel->titleSignal->block->self,這個(gè)時(shí)候如果我們不做處理的話垃僚,那么self就永遠(yuǎn)不會(huì)被釋放集绰。正確的做法應(yīng)該是使用@weakify(self)和@strongify(self):
// 場(chǎng)景3
@weakify(self);
[self.viewModel.titleSignal subscribeNext:^(NSString * title) {
@strongify(self);
self.title = title;
}];
場(chǎng)景4在我們看來(lái)是沒(méi)有問(wèn)題的,因?yàn)檫@里看起來(lái)只有singal->block->self的引用谆棺,它們之間并沒(méi)有造成循環(huán)引用的問(wèn)題栽燕。我們來(lái)看看RACObserve的實(shí)現(xiàn)先:
#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;
其實(shí),看到這里你會(huì)認(rèn)為這里只是調(diào)用了一個(gè)方法創(chuàng)建了一個(gè)Signal改淑,而且這個(gè)Signal也并不屬于任何對(duì)象碍岔。我們?cè)賮?lái)看看具體的實(shí)現(xiàn)是怎么樣的?
- (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];
}
重點(diǎn)觀察deallocSignal和[signal takeUntile:deallocSignal]朵夏,我們把deallocSignal單獨(dú)拿出來(lái)看看:
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都將要發(fā)生dealloc的時(shí)候才會(huì)觸發(fā)的蔼啦。即用RACObserve創(chuàng)建的信號(hào)只有在其target和observe都發(fā)生dealloc的時(shí)候才會(huì)被disposable(這個(gè)好像是RAC用來(lái)銷(xiāo)毀自己資源的東西)。不明白的童鞋仰猖,我們回頭來(lái)分析一下場(chǎng)景4的代碼:
// 場(chǎng)景4
[RACObserve(self.viewModel, title) subscribeNext:^(NSString * title) {
self.title = title;
}];
用RACObserve創(chuàng)建的信號(hào)看起來(lái)只要出了函數(shù)體其資源應(yīng)該就會(huì)被回收捏肢,但是這個(gè)信號(hào)其實(shí)是只有在self.viewModel.rac_willDeallocSignal和self.rac_willDeallocSignal都發(fā)生的情況下才會(huì)被釋放奈籽。所以場(chǎng)景4的引用關(guān)系看起來(lái)只有signal->block->self,但是這個(gè)signal只有在self.rac_willDeallocSignal的時(shí)候才會(huì)被釋放鸵赫。所以這里如果不打斷這種關(guān)系的話就會(huì)造成循環(huán)引用的問(wèn)題衣屏,正確做法應(yīng)該是:
// 場(chǎng)景4
@weakify(self);
[RACObserve(self.viewModel, title) subscribeNext:^(NSString * title) {
@strongify(self);
self.title = title;
}];
最后,在說(shuō)一個(gè)特別需要注意的辩棒,就是UITableViewCell和UICollectionViewCell復(fù)用和RAC的問(wèn)題狼忱。
- (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創(chuàng)建的Signal和self之間已經(jīng)去掉了循環(huán)引用的問(wèn)題,所以應(yīng)該是沒(méi)有什么問(wèn)題的一睁。但是結(jié)合之前我們對(duì)RACObserve的理解再仔細(xì)分析一下钻弄,這里的Signal只要self沒(méi)有被dealloc的話就不會(huì)被釋放。雖然每次UITableViewCell都會(huì)被重用卖局,但是每次重用過(guò)程中創(chuàng)建的信號(hào)確實(shí)無(wú)法被disposable斧蜕。那我們?cè)撛趺醋瞿兀?/p>
- (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;
}
注意,我們?cè)赾ell里面創(chuàng)建的信號(hào)加上takeUntil:cell.rac_prepareForReuseSignal砚偶,這個(gè)是讓cell在每次重用的時(shí)候都去disposable創(chuàng)建的信號(hào)批销。
以上所說(shuō)的關(guān)于內(nèi)存的東西我都用Instrument的Allocations驗(yàn)證過(guò)了,但是依舊建議大家自己也去試試染坯。
(PS:第一次這么認(rèn)真寫(xiě)東西均芽,如果有什么問(wèn)題,歡迎指出5ヂ埂)