rate limiting calls

  • I am getting a lot of calls to a selector "setValue:" from a slider
    control - but setting this value takes an considerable amount of time.

    In order to keep the UI responsive I need to rate limit the actual
    calls or move the value setting into an async background queue.
    While the queue sounds like the most easiest way around this, queuing
    the calls isn't really what I need either as only the last recent
    value in the queue is of interest.

    Right now I am not sure how to tackle this best yet.
    Any thoughts?

    cheers,
    Torsten
  • Hi,

    Quick question: how often are you calling setValue:? Every time it changes or is it inside an NSTimer/CADisplayLink?
    Can you give us more information on this particular flow?

    Cheers,
    Igor Ranieri

    On May 28, 2013, at 4:28 PM, Torsten Curdt <tcurdt...> wrote:

    > I am getting a lot of calls to a selector "setValue:" from a slider
    > control - but setting this value takes an considerable amount of time.
    >
    > In order to keep the UI responsive I need to rate limit the actual
    > calls or move the value setting into an async background queue.
    > While the queue sounds like the most easiest way around this, queuing
    > the calls isn't really what I need either as only the last recent
    > value in the queue is of interest.
    >
    > Right now I am not sure how to tackle this best yet.
    > Any thoughts?
    >
    > cheers,
    > Torsten
  • > Quick question: how often are you calling setValue:? Every time it changes
    > or is it inside an NSTimer/CADisplayLink?

    Every time the user moves the slider.

    > Can you give us more information on this particular flow?

    If I execute the "setValue:" on each value change of the slider the UI
    feels too slow.

    What I currently have in mind is to just store the latest value. Then
    enqueue "update" calls in an async queue and in the update blocks only
    perform the update if the value is different from the previous/current
    one.
    Any easier or more elegant way you could think of?

    cheers,
    Torsten
  • Hi Torsten-

    You might consider something like the coalescing described in this blog entry:

    http://www.takingnotes.co/blog/2013/01/03/coalescing/

    In short, you'd be using performSelector:withObject:afterDelay: to make the desired method call, and cancelPreviousPerformRequestsWithTarget:selector:object: to prevent the calls from piling up.

    Hope this helps!

    John

    On May 28, 2013, at 8:41 AM, Torsten Curdt <tcurdt...> wrote:

    >> Quick question: how often are you calling setValue:? Every time it changes
    >> or is it inside an NSTimer/CADisplayLink?
    >
    > Every time the user moves the slider.
    >
    >> Can you give us more information on this particular flow?
    >
    > If I execute the "setValue:" on each value change of the slider the UI
    > feels too slow.
    >
    > What I currently have in mind is to just store the latest value. Then
    > enqueue "update" calls in an async queue and in the update blocks only
    > perform the update if the value is different from the previous/current
    > one.
    > Any easier or more elegant way you could think of?
    >
    > cheers,
    > Torsten
  • On May 28, 2013, at 8:41 AM, Torsten Curdt wrote:

    > Any easier or more elegant way you could think of?

    Well, if you don't want to deal with a queue and background thread, use NSTimer. In setValue, cancel the timer if it exists, then create a new one scheduled to run .someting seconds. If a timer ever fires, do your calculation.

    Now if you *want* an occasional intermediate update while the user is dragging, then compare values or current time in setValue...

    --
    Scott Ribe
    <scott_ribe...>
    http://www.elevated-dev.com/
    (303) 722-0567 voice
  • Hey John,

    thanks for the pointer. I used a similar pattern before but at that
    time it was just a selector without parameter. AFAIU
    cancelPreviousPerformRequestsWithTarget:selector:object will match the
    object parameter to see what to cancel. (and nil is not a match-all).
    Since here the parameter is changing it didn't feel like the right way
    to go but...

    On the other hand maybe I could just store away the value and the use
    the same pattern for updates. Along the lines of

    -(void)setValue:(NSObject*)value
    {
      self.value = value;

      [NSObject
      cancelPreviousPerformRequestsWithTarget:self
      selector:@selector(updateValue)
      object:nil];

      [self performSelector:@selector(updateValue)
      withObject:nil];
      afterDelay:kSliderDelay];
    }

    -(void)update
    {
      // slow update from self.value
    }

    cheers,
    Torsten
  • On May 28, 2013, at 10:16:07, Scott Ribe <scott_ribe...> wrote:

    > On May 28, 2013, at 8:41 AM, Torsten Curdt wrote:
    >
    >> Any easier or more elegant way you could think of?
    >
    > Well, if you don't want to deal with a queue and background thread, use NSTimer. In setValue, cancel the timer if it exists, then create a new one scheduled to run .someting seconds. If a timer ever fires, do your calculation.
    >
    > Now if you *want* an occasional intermediate update while the user is dragging, then compare values or current time in setValue...

    If you do that last idea, you'll have to make sure you have the final value get set on mouseUp, otherwise you might be ignoring it because not enough time has passed. If the value this slider is setting takes too much time to process, consider letting the calculation process after mouseUp, and let the slider change its value naturally.

    --
    Steve Mills
    office: 952-818-3871
    home: 952-401-6255
    cell: 612-803-6157
  • kSliderDelay = 0.0

    > -(void)setValue:(NSObject*)value
    > {
    > self.value = value;
    >
    > [NSObject
    > cancelPreviousPerformRequestsWithTarget:self
    > selector:@selector(updateValue)
    > object:nil];
    >
    > [self performSelector:@selector(updateValue)
    > withObject:nil];
    > afterDelay:kSliderDelay];
    > }
    >
    > -(void)update
    > {
    > // slow update from self.value
    > }

    Hm - this does not seem to work and I cannot see why not.

    The cancel and perform selector calls are being called but updateValue
    is only performed when I release the slider handle.
    I don't quite get why.

    cheers,
    Torsten
  • > Hm - this does not seem to work and I cannot see why not.
    >
    > The cancel and perform selector calls are being called but updateValue
    > is only performed when I release the slider handle.
    > I don't quite get why.

    Would still be eager to know why but a GCD implementation was super
    simple and works.
  • On May 28, 2013, at 10:03 AM, Torsten Curdt wrote:

    > The cancel and perform selector calls are being called but updateValue
    > is only performed when I release the slider handle.
    > I don't quite get why.

    Oh, because those post into the event loop, and during slider manipulation I bet the event loop is in a special tracking mode where it's handling (mostly) just the mouse events until the mouse is released. Which means my suggestion re NSTimer would not work either.

    --
    Scott Ribe
    <scott_ribe...>
    http://www.elevated-dev.com/
    (303) 722-0567 voice
  • On May 28, 2013, at 9:03 AM, Torsten Curdt <tcurdt...> wrote:

    >
    > The cancel and perform selector calls are being called but updateValue
    > is only performed when I release the slider handle.
    > I don't quite get why.

    Well, aside from using performSelector:@selector(updateValue:) when the method is actually named -update (which I assume is just a typo), the slider is going to run the runloop in event tracking mode while the user is dragging. -performSelector:… will schedule the selector for NSDefaultRunLoopMode. You need to use the …inModes: variant to get delay-performs to happen while mouse-tracking.

    --Kyle Sluder
  • I use the GCD serial queue approach. On each update from the slider, cancel all operations in the queue and then add a new operation with the latest slider value. However, the NSOperation subclass should NOT check whether it is cancelled once it starts executing. That way, once it starts, it is assured to complete and you will get periodic updates.

    On 2013-05-28, at 12:29 PM, Torsten Curdt <tcurdt...> wrote:

    >> Hm - this does not seem to work and I cannot see why not.
    >>
    >> The cancel and perform selector calls are being called but updateValue
    >> is only performed when I release the slider handle.
    >> I don't quite get why.
    >
    > Would still be eager to know why but a GCD implementation was super
    > simple and works.
  • On May 28, 2013, at 10:12 AM, Dave Fernandes <dave.fernandes...> wrote:

    > I use the GCD serial queue approach. On each update from the slider, cancel all operations in the queue and then add a new operation with the latest slider value. However, the NSOperation subclass should NOT check whether it is cancelled once it starts executing. That way, once it starts, it is assured to complete and you will get periodic updates.

    So which are you using, NSOperationQueue or GCD?

    --Kyle Sluder
  • A while ago I was fiddling with various APIs to do rate limited calls. This is the simplest one. Morph it as you please. Maybe it'll be useful to someone.

    @interface GCDCoalescent : NSObject
    - (void)dispatchOn:(dispatch_queue_t)queue after:(double)seconds block:(dispatch_block_t)block;
    @end


    @implementation GCDCoalescent
    {
        uint64_t count;
    }

    - (void)dispatchOn:(dispatch_queue_t)queue after:(double)seconds block:(dispatch_block_t)block;
    {
    uint64_t eventNumber = __sync_add_and_fetch(&count, 1);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC), queue, ^{
      if (count > eventNumber) return;
      block();
    });
    }

    @end

    --
    Seth Willits
  • Er, that would be NSOperationQueue. Haven't had my coffee yet today.

    On 2013-05-28, at 1:17 PM, Kyle Sluder <kyle...> wrote:

    > On May 28, 2013, at 10:12 AM, Dave Fernandes <dave.fernandes...> wrote:
    >
    >> I use the GCD serial queue approach. On each update from the slider, cancel all operations in the queue and then add a new operation with the latest slider value. However, the NSOperation subclass should NOT check whether it is cancelled once it starts executing. That way, once it starts, it is assured to complete and you will get periodic updates.
    >
    > So which are you using, NSOperationQueue or GCD?
    >
    > --Kyle Sluder
previous month may 2013 next month
MTWTFSS
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    
Go to today