RAC 中一个 retain cycle 问题

今天使用 RAC 注册通知时,遇到一个不是很明显的 retain cycle 问题,使用场景是在一个ViewController 中注册一个通知,代码如下:


1

2

3

4

5

6


- (void)viewDidLoad {

[super viewDidLoad];

[[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIApplicationDidBecomeActiveNotification object:nil] takeUntil:[self rac_willDeallocSignal]] subscribeNext:^(id x) {

self.title = @"";

}];

}

如果这样使用了,那么这个 ViewController 就释放不了了,为什么呢,翻了一下源码,看看 rac_addObserverForName是怎么运行的。

rac_addObserverForName 是怎么运行的呢,通常我们如果需要在一个 ViewController 中监听一个事件的话会把ViewController 自身作为一个监听者(observer),RAC 中并不是这样,RAC 中 rac_addObserverForName 方法的代码如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17


@implementation NSNotificationCenter (RACSupport)

- (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object {

@unsafeify(object);

return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {

@strongify(object);

id observer = [self addObserverForName:notificationName object:object queue:nil usingBlock:^(NSNotification *note) {

[subscriber sendNext:note];

}];

return [RACDisposable disposableWithBlock:^{

[self removeObserver:observer];

}];

}] setNameWithFormat:@"-rac_addObserverForName: %@ object: <%@: %p>", notificationName, [object class], object];

}

@end

这里创建了一个信号,并把这个信号返回,信号创建时需要一个 blockblock 中包含中有一些操作,如注册通知等,但这个时候 block 中的操作并没有执行。

看 RACSignal 的 createSignal 方法怎么实现的,如下:


1

2

3


+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {

return [RACDynamicSignal createSignal:didSubscribe];

}

其实是创建一个 RACDynamicSignal 对象,RACDynamicSignal 是 RACSignal 的子类,RACDynamicSignal 的createSignal 方法怎么实现的呢?


1

2

3

4

5


+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {

RACDynamicSignal *signal = [[self alloc] init];

signal->_didSubscribe = [didSubscribe copy];

return [signal setNameWithFormat:@"+createSignal:"];

}

我们看到传进来的 block 赋值给一个叫 didSubscribe 的属性,那么这个 block 在什么时候被调用?往下看。

一个信号在调用 subscribeNext 时会创建 subscriber


1

2

3

4

5

6


- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {

NSCParameterAssert(nextBlock != NULL);

RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];

return [self subscribe:o];

}

RACSubscriber 的初始化方法如下,这个 nextBlock 被赋值给 RACSubscriber 的一个属性


1

2

3

4

5

6

7

8

9


+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {

RACSubscriber *subscriber = [[self alloc] init];

subscriber->_next = [next copy];

subscriber->_error = [error copy];

subscriber->_completed = [completed copy];

return subscriber;

}

subscriber 创建后,会调用信号的 subscribe 方法,并将得到的返回值返回。

RACDynamicSignal 重写了父类 RACSignal 的 subscribe 方法


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17


- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {

NSCParameterAssert(subscriber != nil);

RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];

subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

if (self.didSubscribe != NULL) {

RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{

RACDisposable *innerDisposable = self.didSubscribe(subscriber);

[disposable addDisposable:innerDisposable];

}];

[disposable addDisposable:schedulingDisposable];

}

return disposable;

}

这里很重要的一个操作就是 self.didSubscribe(subscriber),在 createSignal 时传入的 block 被执行了,也就是说在一个信号调用 subscribeNext 方法时,createSignal 中传入的 block 中的一大坨操作被执行,这里主要就是监听指定的通知。

如果接收到注册的通知,subscriber 被执行一个方法


1


[subscriber sendNext:note];

这个时候我们就熟悉了,是封装信号的那一套东西。这样整个流程就走通了。

那么我们来看一下几个关键的地方的对象引用关系。

subscribeNext 中创建的 subscriber 持有 nextBlock,nextBlock 捕捉 self 并引用。

信号创建时的 block 中 创建了一个监听者(observer),[NSNotificationCenter defaultCenter] 实例引用了observerobserver 引用 usingBlockusingBlock 又捕捉到了 subscriber,由此两条引用链条串了起来,形成了如下的引用关系:

NSNotificationCenter 实例 -> observer -> usingBlock -> subscriber -> nextBlock -> self

信号创建时有一个操作


1

2

3


[RACDisposable disposableWithBlock:^{

[self removeObserver:observer];

}]

这个意思是在信号完成时移除 observer,信号在什么时候完成呢,takeUntil 的信号参数完成时rac_addObserverForName 中创建的信号完成,而 takeUntil 的参数是在 ViewController 释放时才可以完成,由此就可以看到:

  1. rac_addObserverForName 中创建的信号在 ViewController 对象释放前,在信号创建时添加的 observer 将一直存在
  2. 该 observer 间接引用 ViewController 对象,ViewController 对象引用计数始终不可能为 0,也就是释放不了

由此一个死循环产生,这个 ViewController 就释放不了了。

转了一大圈,产生了一个死循环的坑 :(

解决方法是挺简单的,使用 RAC 的方法注册通知时在 block 内使用 weak 对象来打破死死循环就好了,代码如下:


1

2

3

4

5

6

7

8


- (void)viewDidLoad {

[super viewDidLoad];

@weakify(self);

[[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIApplicationDidBecomeActiveNotification object:nil] takeUntil:[self rac_willDeallocSignal]] subscribeNext:^(id x) {

@strongify(self);

self.title = @"";

}];

}

由于 RAC 中大量使用 block,虽然可以写出更简单易读的代码,但是也隐藏了一些坑,使用时还是要小心。

最后,如有理解偏差,还望指正。

–EOF–

时间: 2024-11-01 23:46:14

RAC 中一个 retain cycle 问题的相关文章

aix-oracle asm 的rac 中,weblogic总无法连接其中一台,报ora-15025的错

问题描述 oracle asm 的rac 中,weblogic总无法连接其中一台,报ora-15025的错 在AIX上 oracle +asm的rac,2台服务器,一台是server1.2,另一台是server1.55. 1.2 这台server之前运行正常,但最近总是无法连接到服务器.查看了log,有ora-15025 could open a disk,和ORA-27041: unable to open file这两个错误. 求高手指点一二. 附: 另外1.2这台server上有一个 +A

RAC中的实例管理

  10月29日,由Oracle首席技术支持工程师高斌老师在"DBA+东北群"进行了一次关于"RAC中的实例管理"的线上主题分享.小编特别整理出其中精华内容,供大家学习交流.      嘉宾简介    <Oracle RAC核心技术解密>(即将出版)作者 Oracle首席技术支持工程师(Principal Technical Support Engineer),2007年加入Oracle 大连技术支持中心,对Oracle数据库产品有比较深刻的认识. 主要

【RAC】RAC中的负载均衡和故障切换--TAF配置

[RAC]RAC中的负载均衡和故障切换--TAF配置 涉及到的内容包括:   Oracle RAC 客户端连接负载均衡(Load Balance)      实现负载均衡(Load Balance)是Oracle RAC最重要的特性之一,主要是把负载平均分配到集群中的各个节点,以提高系统的整体吞吐能力.通常情况下有两种方式来实现负载均衡,一个是基于客户端连接的负载均衡,一个是基于服务器端监听器(Listener)收集到的信息来将新的连接请求分配到连接数较少实例上的实现方式.本文主要讨论的是基于客

【RAC】Oracle 11gR2 RAC 中的 Grid Plug and Play(GPnP) 是什么?

[RAC]Oracle 11gR2 RAC 中的 Grid Plug and Play(GPnP) 是什么? 一. 什么是GPnP?   Grid Plug and Play (GPnP):Foundation for a Dynamic Cluster Management    (1)GPnPeliminates the need for a per node configuration –It is an underlying gridconcept that enables the au

使用OpenFiler来模拟存储配置RAC中ASM共享盘及多路径(multipath)的测试

第一章 本篇总览   之前发布了一篇<Oracle_lhr_RAC 12cR1安装>,但是其中的存储并没有使用多路径,而是使用了VMware自身提供的存储.所以,年前最后一件事就是把多路径学习一下,本文介绍了OpenFiler.iSCSI和多路径的配置. 本文内容:     第二章 安装OpenFiler OpenFile是在rPath Linux基础上开发的,它能够作为一个独立的Linux操作系统发行.Openfiler是一款非常好的存储管理操作系统,开源免费,通过web界面对存储磁盘的管理

【RAC】rac中如何指定job的运行实例

[RAC]rac中如何指定job的运行实例   1.1  BLOG文档结构图     1.2  前言部分   1.2.1  导读和注意事项 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不知道的知识,~O(∩_∩)O~: ① 如何指定job的运行实例(重点) ② 代码获取rac所有节点的IP地址     Tips:        ① 若文章代码格式有错乱,推荐使用QQ.搜狗或360浏览器,也可以下载pdf格式的文档来查看,pdf文档下载地址:http://yunpan.

RAC中一次混乱的性能诊断过程 1

                     RAC中一次混乱的性能诊断过程    众所周知在RAC中,问题很可能来自于CACHE FUSION(内存融合)的机制,简单的说就是CACHE BUFFER中的块在内存融合的机制下通过LMD进程进行传递,比如我节点1需要访问数据块A,通过SEND MESSAGE到MASTER节点询问块A所在的节点,MASTER告诉节点1 A块在节点2的BUFFER CACHE中,这个时候又可能是当前读和一致性读,如果块正在被修改就会构架PI来进行一致性读,传递块给节点1,

Oracle RAC中Srvctl命令详细说明

Oracle RAC中srvctl命令详细说明(转) SRVCTL Add 添加数据库或实例的配置信息.在增加实例中,与-i一起指定的名字应该与INSTANCE_NAME 和 ORACLE_SID参数匹配. srvctl add database -d database_name [-m domain_name] -o oracle_home [-s spfile] srvctl add instance -d database_name -i instance_name -n node_nam

gdi+-GDI+中一个窗体的CDC* pDC实例化两个Graphics对象问题

问题描述 GDI+中一个窗体的CDC* pDC实例化两个Graphics对象问题 代码如下:void CMyCtrl::Draw(CDC* pDC CRect rc){ Pen pen(Color(255 255 255 255)(float)1.5); SolidBrush solidbrush(Color(255 213 213 213)); pen.SetDashStyle((DashStyle)DashStyleSolid); Graphics Test(pDC->m_hDC); Tes