Is there a simple way (in Cocoa/iOS) to queue a method call to run once in the next run loop?

UIView has a setNeedsDisplay method that one can call several times within the same event loop, safe in the knowledge that the redrawing work will happen soon, and only the once.

Is there a generic mechanism for this sort of behaviour Cocoa? A way of of saying, “Queue a selector as many times as you like, when it’s time, the selector will run once & clear the queue.”

  • Get the color a pixel on the screen in objective-c cocoa app
  • UITabBar customization
  • How to call scrollViewDidScroll: the same way UIScrollView does, but during custom animation?
  • Is there a correct way to determine that an NSNumber is derived from a Bool using Swift?
  • How to make a sprite jump to a specific height with SpriteKit?
  • Determine which share extension was used
  • I know I could do this with some kind of state tracking in my target, or with an NSOperationQueue. I’m just wondering if there’s a lightweight approach I’ve missed.

    (Of course, the answer may be, “No”.)

    2 Solutions Collect From Internet About “Is there a simple way (in Cocoa/iOS) to queue a method call to run once in the next run loop?”

    setNeedsDisplay is not a good example of what you’re describing, since it actually does run every time you call it. It just sets a flag. But the question is good.

    One solution is to use NSNotificationQueue with NSNotificationCoalescingOnName.

    Another solution is to build a trampoline to do the coalescing yourself. I don’t have a really good blog reference for trampolines, but here’s an example of one (LSTrampoline). It’s not that hard to build this if you want to coalesce the messages over a period of time. I once built a trampoline with a forwardInvocation: similar to this:

    - (void)forwardInvocation:(NSInvocation *)invocation {
      [invocation setTarget:self.target];
      [invocation retainArguments];
      [self.timer invalidate];
      self.timer = [NSTimer scheduledTimerWithTimeInterval:self.timeout invocation:invocation repeats:NO];
    }
    

    This actually coalesces all messages to the object over the time period (not just matching messages). That’s all I needed for the particular problem. But you could expand on this to keep track of which selectors are being coalesced, and check your invocations to see if they match “sufficiently.”

    To get this to run on the next event loop, just set timeout to 0.

    I keep meaning to blog about trampolines. Required shilling: My upcoming book covers trampolines in Chapter 4 and Chapter 20.

    [NSObject cancelPreviousPerformRequestsWithTarget:self 
                                             selector:@selector(doTheThing:)
                                               object:someObject];
    [self performSelector:@selector(doTheThing:) 
               withObject:someObject 
               afterDelay:0];
    

    This is not exactly how UIView is doing it because setNeedsDisplay simply sets a flag and the internal UIView mechanism makes sure to call drawRect: after setting up the drawing environment, but this is a generic way and doesn’t require any special state tracking in your class.