RAC和內(nèi)存管理

(轉(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ヂ埂)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末掀宋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子仲锄,更是在濱河造成了極大的恐慌劲妙,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件儒喊,死亡現(xiàn)場(chǎng)離奇詭異镣奋,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)怀愧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)侨颈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人芯义,你說(shuō)我怎么就攤上這事哈垢。” “怎么了扛拨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵耘分,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)陶贼,這世上最難降的妖魔是什么啤贩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮拜秧,結(jié)果婚禮上痹屹,老公的妹妹穿的比我還像新娘。我一直安慰自己枉氮,他們只是感情好志衍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著聊替,像睡著了一般楼肪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惹悄,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天春叫,我揣著相機(jī)與錄音,去河邊找鬼泣港。 笑死暂殖,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的当纱。 我是一名探鬼主播呛每,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼坡氯!你這毒婦竟也來(lái)了晨横?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤箫柳,失蹤者是張志新(化名)和其女友劉穎手形,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體悯恍,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡库糠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坪稽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鳞骤,死狀恐怖窒百,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情豫尽,我是刑警寧澤篙梢,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站美旧,受9級(jí)特大地震影響渤滞,放射性物質(zhì)發(fā)生泄漏贬墩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一妄呕、第九天 我趴在偏房一處隱蔽的房頂上張望陶舞。 院中可真熱鬧,春花似錦绪励、人聲如沸肿孵。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)停做。三九已至,卻和暖如春大莫,著一層夾襖步出監(jiān)牢的瞬間蛉腌,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工只厘, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留烙丛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓懈凹,卻偏偏與公主長(zhǎng)得像蜀变,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子介评,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 打算在項(xiàng)目中大面積使用RAC來(lái)開(kāi)發(fā)库北,所以整理一些常用的實(shí)踐范例和比較完整的api說(shuō)明方便開(kāi)發(fā)時(shí)隨時(shí)查閱 聲明式編程...
    星光社的戴銘閱讀 5,350評(píng)論 5 49
  • 前言 很多blog都說(shuō)ReactiveCocoa好用,然后各種秀自己如何靈活運(yùn)用ReactiveCocoa们陆,但是感...
    RainyGY閱讀 1,337評(píng)論 0 1
  • RAC使用測(cè)試Demo下載:github.com/FuWees/WPRACTestDemo 1.ReactiveC...
    FuWees閱讀 6,372評(píng)論 3 10
  • 一 導(dǎo)入ReactiveCocoa框架 通常都會(huì)使用CocoaPods(用于管理第三方框架的插件)幫助我們導(dǎo)入po...
    CharType閱讀 822評(píng)論 0 1
  • 2017.02.22 可以練習(xí)寒瓦,每當(dāng)這個(gè)時(shí)候,腦袋就犯困坪仇,我這腦袋真是神奇呀杂腰,一說(shuō)讓你做事情,你就犯困椅文,你可不要太...
    Carden閱讀 1,346評(píng)論 0 1