gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 16:01:03 27 4
gpt4 key购买 nike

当我创建一个信号并将其带入函数范围时,根据 Cocoa 约定,其有效保留计数为 0:

RACSignal *signal = [self createSignal];

当我订阅信号时,它会保留订阅者并返回一个一次性的,根据 Cocoa 约定,它的保留计数也为零。
RACDisposable *disposable = [signal subscribeCompleted:^ {
doSomethingPossiblyInvolving(self);
}];

大多数时候,订阅者会关闭并引用 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 的内存管理相当复杂,但值得的最终结果是 您不需要保留信号来处理它们 .

如果框架要求您保留每个信号,则使用起来会更加笨拙,尤其是对于像 future 一样使用的一次性信号(例如,网络请求)。您必须将任何长期存在的信号保存到属性中,然后确保在完成后将其清除。不好玩。

订户

在继续之前,我应该指出 subscribeNext:error:completed: (及其所有变体)使用给定的块创建隐式订阅者。因此,从这些块引用的任何对象都将作为订阅的一部分保留。就像任何其他对象一样,self如果没有直接或间接引用,则不会保留。

(根据您问题的措辞,我认为您已经知道这一点,但这可能对其他人有所帮助。)

有限或短期信号

RAC 内存管理最重要的指导原则是 订阅在完成或出错时自动终止,并删除订阅者 .要使用您的循环引用示例:

calling scope -> disposable -> signal -> subscriber -> calling scope

… 这意味着 signal -> subscriber关系一撕就断了 signal完成,打破保留循环。

这通常就是您所需要的 ,因为 RACSignal 的生命周期在内存中自然会匹配事件流的逻辑生命周期。

无限信号

然而,无限信号(或生命周期太长以至于它们也可能是无限的信号)永远不会自然消失。这就是一次性用品大放异彩的地方。

处理订阅将删除关联的订阅者 ,并且通常只是清理与该订阅相关的任何资源。对于那个订阅者来说,就像信号已经完成或出错一样,除了没有在信号上发送最终事件。所有其他订阅者将保持不变。

但是,作为一般经验法则, 如果您必须手动管理订阅的生命周期,那么可能有更好的方法来做您想做的事。 方法如 -take:-takeUntil:将为您处理处理,您最终会获得更高级别的抽象。

源自 self 的信号

不过,这里仍然有一些棘手的中间情况。任何时候信号的生命周期都与调用范围相关联,您将很难打破循环。

这通常发生在使用 RACAble() 时或 RACAbleWithStart()在相对于 self 的关键路径上,然后应用需要捕获的块 self .

这里最简单的答案就是 捕获 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;
}];

与无限信号一样,通常有一些方法可以避免引用 self (或任何对象)来自信号链中的块。

为了有效地使用 ReactiveCocoa,以上信息确实是您所需要的全部信息。但是,我想再说明一点,仅供技术上的好奇者或任何有兴趣为 RAC 做出贡献的人:

I also notice that the implementation retains a process-global list of active signals.



这是绝对正确的。

“无需保留”的设计目标引出了一个问题:我们如何知道何时应该释放信号?如果它刚刚创建,逃脱了自动释放池,还没有被保留怎么办?

真正的答案是我们不这样做,但我们通常可以假设调用者将在当前运行循环迭代中保留信号,如果他们想保留它。

最后:
  • 创建的信号会自动添加到一组全局事件信号中。
  • 该信号将等待主运行循环的单次传递,然后如果它没有订阅者,则将其从事件集中删除。除非信号以某种方式被保留,否则它会在此时解除分配。
  • 如果在该运行循环迭代中确实订阅了某些内容,则信号会保留在集合中。
  • 稍后,当所有订阅者都消失时,再次触发#2。

  • 如果运行循环以递归方式旋转(例如在 OS X 上的模态事件循环中),这可能会适得其反,但对于大多数或所有其他情况,它使框架使用者的生活变得更加轻松。

    关于cocoa - ReactiveCocoa 的引用所有权语义是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14100161/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com