objective-cmultithreadingreactivereactive-cocoadealloc

MultiThreaded delaying class deallocation


below i am defining a solution and using interval as a timer in background thread as follow :

@weakify(self)

 //IMPORTANT:- Throttle is working exactly the same way debounce works in RX SO DO NOT USE IT.
RACScheduler *bacgroundScheduler = [RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground];

RACSignal *sampler = [RACSignal interval:3 onScheduler: bacgroundScheduler];
enter code here
 // updateListenerPositionSubject is a RACReplaySubject.
    RACSignal *fallbackSignal = [[RACSignal
      merge:@[ self.updateListenerPositionSubject, sampler ]]
      takeUntil:[self.updateListenerPositionSubject ignoreValues]];


    @weakify(self);
    [fallbackSignal subscribeNext:^(id _Nullable x) {
      @strongify(self);
      [self solutionFallBack];
    } error:^(NSError *error) {
      NSLog(@"Error: %@", error);
    } completed:^{
// to make sure subscription get completed when updateListenerPositionSubject sends complete.  
      NSLog(@"Completed");
    }];
  }

And solutionFallBack function is defined as follow:

-(void) solutionFallback {
  // block the original solution.
  [self.updateListenerPositionSubject sendCompleted];
  // bunch of conditions
  [self performSwitchWith:shape];
}

In case "solution Fallback" condition satisfied the viewmodel will be deallocated after a while (maybe 30sec or 1min) which is not good specially i am doing unloadings in dealloc.

So I have tried different solution to avoid have "sample" & "ipdateListenerPositionSubject" in different threads, I have tried to subscribe to sample signal and takeuntil fallback condition satisfied as follow :

RACScheduler *bacgroundScheduler = [RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground];
RACSignal *rac_viewModelWillDealloc = [self rac_signalForSelector:@selector(performSwitchWith:)];
RACSignal *sampler = [[RACSignal interval:self.sceneSwitchConfiguration.roundDuration onScheduler:bacgroundScheduler] takeUntil: rac_viewModelWillDealloc];
    @weakify(self);
    [sampler subscribeNext:^(id _Nullable x) {
      @strongify(self);
      self.backgroundThread = [NSThread currentThread];
      [self solutionFallback];
    } error:^(NSError *error) {
      NSLog(@"Error: %@", error);
    } completed:^{
      NSLog(@"Completed");
    }];

and when i make sure solutoionFallback solution condition satisfied which is calling "performSwitchWith"... i am cancelling current background thread and handover to another as follow:

@weakify(self);
    [self.backgroundThread cancel];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
      @strongify(self);
   // continue init the new vm process here.
    });

So when I switch the interval to be scheduled in Main thread all works as expected, and dealloc take a place instantly :

RACSignal *sampler = [[RACSignal
                    interval:self.sceneSwitchConfiguration.roundDuration onScheduler:[RACScheduler mainThreadScheduler]] takeUntil:rac_viewModelWillDealloc];

I want to keep the sampler signal in background thread and to have the class deallocated immediately.


Solution

  • The NSThread cancel method does not perform preemptive cancelation. All it does is set a Boolean that you can check in your other thread. As the docs say:

    The semantics of this method are the same as those used for Operation. This method sets state information in the receiver that is then reflected by the isCancelled property. Threads that support cancellation should periodically call the isCancelled method to determine if the thread has in fact been cancelled, and exit if it has been.

    For more information about cancellation and operation objects, see Operation.

    Whether canceling NSThread, dispatch work item, or operations, the only time you can cancel something that is currently running on another thread is if that code on that thread was explicitly written to support cancelation (e.g., in compute tasks, periodically checking isCancelled state).

    In short, your issue will not be solved by the use of cancel method.


    If I understand you correctly, you are saying that something is holding on to self for 30+ seconds when called from a background thread, but not when you call it from the main thread. That is a pretty unusual scenario.

    It’s hard to say what would cause that on the basis of what has been provided. (It would be nice if you could prepare a MCVE that manifests this problem without all of these external dependencies.)

    Anyway, I would suggest a few things:

    1. Add log statements before, in, and after your solutionFallback. Confirm whether this method is taking longer. This helps you narrow down the source of the delay. If those methods are taking longer, then that’s clearly the problem and you can set yourself on diagnosing why they’re taking longer than you expected. If they’re returning immediately, then we know the problem rests elsewhere.

    2. I would suggest turning on the main thread checker, if you have not already. Historically attempting UI updates on a background thread would cause mysterious delays, and the main thread checker identifies these issues immediately.

      I don’t think it will find anything causal, but you might also temporarily turn on the thread sanitizer, too, and make sure you do not see any issues there.

      The Main Thread Checker and TSAN are discussed in Diagnosing Memory, Thread, and Crash Issues Early.

    3. If you are not seeing the object deallocated in a timely manner, press the “Debug Memory Graph” button during this 30+ second delay. (See Gathering Information About Memory Use or WWDC video Visual Debugging with Xcode.) This will show you precisely what is keeping the lingering strong reference. And if you turn on the “Malloc stack” feature (described in How to debug memory leaks when Leaks instrument does not show them? or in that video), it will show you not only what has the strong reference, but where the strong reference was originally established. It obviously cannot tell you why or where the strong reference wasn’t removed, but at least it will provide a list of strong references from which you can commence your analysis.