RAC和单元重用:将deliverOn:放在正确的位置?

时间:2014-11-27 14:24:43

标签: ios objective-c reactive-cocoa

我正在玩RAC,特别是Colin Eberhardt的Twitter搜索example,并遇到了我无法向自己解释的崩溃。

这是我创建的sample project来说明问题并将问题作为基础。

该应用使用UITableView可重复使用的单元格;每个单元格上都有一个UIImageView,其图像由某个URL下载 还定义了一个用于在后台队列上下载图像的信号:

- (RACSignal *)signalForLoadingImage:(NSString *)imageURLString
{
    RACScheduler *scheduler = [RACScheduler
                               schedulerWithPriority:RACSchedulerPriorityBackground];

    return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURLString]];
        UIImage *image = [UIImage imageWithData:data];
        [subscriber sendNext:image];
        [subscriber sendCompleted];
        return nil;
    }] subscribeOn:scheduler];
}

cellForRowAtIndexPath:中,我使用image宏将加载信号绑定到图片视图的RAC属性:

RAC(cell.kittenImageView, image) =
[[[self signalForLoadingImage:self.imageURLs[indexPath.row]]
  takeUntil:cell.rac_prepareForReuseSignal]      // Crashes on multiple binding assertion!
 deliverOn:[RACScheduler mainThreadScheduler]];  // Swap these two lines to 'fix'

现在,当我运行应用程序并开始向上和向下滚动表格视图时,应用程序崩溃并显示断言消息:

Signal <RACDynamicSignal: 0x7f9110485470> name:  is already bound to key path "image" on object <UIImageView: <...>>, adding signal <RACDynamicSignal: 0x7f9110454510> name:  is undefined behavior

但是,如果我首先将图像加载信号包装到deliverOn:,然后再装入takeUntil: ,则单元格重用将正常工作:

RAC(cell.kittenImageView, image) =
[[[self signalForLoadingImage:self.imageURLs[indexPath.row]]
  deliverOn:[RACScheduler mainThreadScheduler]]
 takeUntil:cell.rac_prepareForReuseSignal];  // No issue

所以我的问题是:

  1. 如何解释后者为何起作用而前者不起作用?显然有一些竞争条件导致新信号在现有属性完成之前绑定到image属性,但我完全不确定它是如何发生的。
  2. 在RAC驱动的代码中,我应该记住什么才能避免这种微妙之处?我在上面的代码中遗漏了一些基本原则,或者是否有任何经验法则适用(假设当然RAC本身没有错误)?
  3. 感谢您阅读: - )

1 个答案:

答案 0 :(得分:2)

我还没有证实这一点,但这里有一个可能的解释:

  1. Cell X被调用,开始下载图像。
  2. Cell X在图像下载完成之前滚动屏幕。
  3. Cell X被重用,prepareForReuse被调用。
  4. Cell X&#39; s rac_prepareForReuseSignal发送一个值。
  5. 由于deliverTo:,该值被分派到主队列,引入了一个runloop延迟。值得注意的是,这可以防止图像属性的同步/立即解除绑定。
  6. 正在使用Cell X cellForRowAtIndexPath:
  7. 调用新图像绑定并导致警告
  8. … next runloop …
  9. 最终破坏了原始装订,但现在已经太晚了。
  10. 所以基本上信号应该在4到6之间解除绑定,但是-deliverTo:重新排序取消绑定以便稍后出现。