RAC and cell reuse: placing deliverOn: in the right place?

I played around with RAC and Colin Eberhardt Twitter in particular to find an example and ran into a crash that I couldn't explain to myself.

Here's an example I created to illustrate the problem and ask a question.

Application uses UITableView

with reusable cells; each cell has UIImageView

on it, whose image is loaded by some url.
Also, a signal for loading an image in the background is defined:

- (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];
}

      

In cellForRowAtIndexPath:

I bind the load signal to the image view image

using a macro 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'

      

Now when I run the application and start scrolling the table view up and down, the application crashes with an assertion message:

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

      

However , if I first pack the image load signal into deliverOn:

and then intotakeUntil:

, cell reuse works fine:

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

      

So my questions are:

  • How can you explain why the latter works and the former does not? Obviously there is some kind of race condition causing the new signal to bind to a property image

    before it completes, but I'm completely unsure how exactly this happens.
  • What should I remember to avoid this subtlety in my RAC code? Am I missing some basic principle in the code above, or is there any rule to apply (unless of course there is a bug in RAC)?

Thanks for reading here :-)

+3


source to share


1 answer


I have not confirmed this, but here is a possible explanation:

  • Cell X starts at startup, starts loading the image.
  • Cell X scrolls the screen before loading the image.
  • Core X is used again, prepareForReuse

    invoked.
  • Cell X rac_prepareForReuseSignal

    sends a value.
  • Because of, the deliverTo:

    value is sent to the main queue, introducing the runloop delay. It should be noted that this prevents synchronous / immediate unpinning of the image property.
  • Cell X is used cellForRowAtIndexPath:

  • New image binding is called and raises a warning
  • … next runloop …

  • The original binding is finally now broken, but it's too late.


So basically the signal needs to be decoupled between 4 and 6, but -deliverTo:

reorders the unlock to come later.

+2


source







All Articles