RACSignal *signal = [self createSignal];
当我订阅信号时,它保留订阅者并返回一个一次性的,根据Cocoa约定,保留计数为零。
RACDisposable *disposable = [signal subscribeCompleted:^ { doSomethingPossiblyInvolving(self); }];
大多数时候,订阅者将关闭并引用自身或其ivars或包围范围的其他部分。因此,当您订阅信号时,信号具有对订户的拥有引用,并且订户具有对您的拥有引用。而一次性你得到的回报有一个拥有参考的信号。
disposable -> signal -> subscriber -> calling scope
假设你坚持这种一次性,所以你可以在某个时候取消你的订阅(例如,如果信号是从Web服务检索数据,用户导航离开屏幕,取消她的意图查看正在检索的数据)。
self.disposeToCancelWebRequest = disposable;
此时我们有一个循环引用:
calling scope -> disposable -> signal -> subscriber -> calling scope
负责的事情是确保在取消请求或请求完成后,循环被中断。
[self.disposeToCancelWebRequest dispose] self.disposeToCancelWebRequest = nil;
注意,你不能这样做,当self被释放,因为这将永远不会发生,由于保留周期!在回到用户期间打破保持周期似乎很麻烦,因为信号可能被解除分配,而其实现仍然在调用栈上。
我还注意到,实现保留活动信号的过程全局列表(截至我最初提出这个问题的时间)。
在使用RAC时,我应该如何考虑所有权?
如果框架要求你保留每个信号,那么使用起来会更加困难,特别是对于未来(例如网络请求)使用的单次信号。你必须将任何长寿命的信号保存到一个属性,然后还要确保在你完成它后清除它。不好玩。
订阅者
在进一步之前,我应该指出,subscribeNext:error:completed:(及其所有变体)使用给定的块创建隐式订阅者。因此,从这些块引用的任何对象将作为订阅的一部分保留。就像任何其他对象一样,如果没有对它的直接或间接引用,self将不会被保留。
(根据你的问题的措辞,我认为你已经知道这一点,但它可能对其他人有帮助。)
有限或短期信号
RAC内存管理的最重要指南是,订阅在完成或错误后自动终止,并且订户已删除。要使用循环引用示例:
calling scope -> disposable -> signal -> subscriber -> calling scope
…这意味着信号 – 一旦信号完成,用户关系就立即断开,打破保持周期。
这通常是你需要的,因为RACSignal在内存中的生命周期自然会匹配事件流的逻辑生命周期。
无限信号
无限信号(或信号,生活得如此长,以至于它们可能是无限的),然而,永远不会自然地撕裂。这是一次性用品。
处理订阅将删除相关联的订户,并且通常只清除与该订阅相关联的任何资源。对于那个用户,它就像信号已经完成或错误,除了没有最终事件发送信号。所有其他订阅者将保持不变。
然而,作为一般的经验法则,如果你必须手动管理订阅的生命周期,可能有更好的方法来做你想要的。像-take:或-takeUntil:这样的方法会处理你的处理,你最终得到一个更高级的抽象。
信号来源于自我
这里仍然有一个棘手的中间情况。任何时候一个信号的生命周期都绑定到调用范围,你将有一个更难的周期来打破。
当在相对于self的密钥路径上使用RACAble()或RACAbleWithStart(),然后应用需要捕获self的块时,通常会发生这种情况。
这里最简单的答案就是自我弱化:
__weak id weakSelf = self; [RACAble(self.username) subscribeNext:^(NSString *username) { id strongSelf = weakSelf; [strongSelf validateUsername]; }];
或者,在导入包含的EXTScope.h标题后:
@weakify(self); [RACAble(self.username) subscribeNext:^(NSString *username) { @strongify(self); [self validateUsername]; }];
(如果对象不支持弱引用,则分别将__weak或@weakify替换为__unsafe_unretained或@unsafeify。)
但是,可能有一个更好的模式,你可以改用。例如,上面的样本可能写成:
[self rac_liftSelector:@selector(validateUsername:) withObjects:RACAble(self.username)];
要么:
RACSignal *validated = [RACAble(self.username) map:^(NSString *username) { // Put validation logic here. return @YES; }];
与无限信号一样,通常有一些方法可以避免引用来自信号链中块的自身(或任何对象)。
上面的信息真的是所有你需要为了有效地使用ReactiveCocoa。但是,我想再谈一点,只是为了技术上的好奇或任何有兴趣参与RAC的人:
I also notice that the implementation retains a process-global list of active signals.
这是绝对真实的。
“不需要保留”的设计目标提出了一个问题:我们怎么知道什么时候信号应该被释放?如果它刚刚创建,转义自动释放池,并且尚未保留,该怎么办?
真正的答案是我们不,但是我们可以通常假设调用者将保留当前运行循环迭代中的信号,如果他们想保留它。
所以:
>创建的信号自动添加到全局活动信号集。
>信号将等待主运行循环的单次通过,然后如果没有订户,则将其从活动集中移除。除非信号以某种方式保留,否则会在此时释放。
>如果在该运行循环迭代中有东西没有订阅,则信号保留在集合中。
>后来,当所有的用户都去了,#2再次被触发。
如果运行循环被递归地旋转(如在OS X上的模态事件循环),这可能会回火,但是它使得框架消费者的生活对于大多数或所有其他情况更容易。