ReactiveCocoa的引用所有权语义是什么?

当我创建一个信号并将其带入函数的范围时,其有效保留计数为0每Cocoa约定:
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时,我应该如何考虑所有权?

ReactiveCocoa的内存管理是相当复杂的,老实说,但最值得的结果是,你不需要保留信号,以处理它们。

如果框架要求你保留每个信号,那么使用起来会更加困难,特别是对于未来(例如网络请求)使用的单次信号。你必须将任何长寿命的信号保存到一个属性,然后还要确保在你完成它后清除它。不好玩。

订阅

在进一步之前,我应该指出,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上的模态事件循环),这可能会回火,但是它使得框架消费者的生活对于大多数或所有其他情况更容易。

相关文章

一、前言 在组件方面react和Vue一样的,核心思想玩的就是组件...
前言: 前段时间学习完react后,刚好就接到公司一个react项目...
前言: 最近收到组长通知我们项目组后面新开的项目准备统一技...
react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom...