1

Is there a way to fire NSNotification (that potentially triggers a fair amount of work) without delaying any currently completing code?

Specifically, I have a UIPanGestureRecognizer which allows the user to pan through months, January, February, etc. Once the user lifts their finger (recognizer state changes UIGestureRecognizerStateEnded) there is some clean up code run, including some UI (adjusting a frame, etc), and -- importantly -- an NSNotification fired with the selected month.

This works smoothly in the Simulator, but when run on my iPhone 4, the performance is terrible (i.e. when I lift my finger, the UI locks up for +1.5 seconds). If I remove the notification, the performance is back to being good. Please note that these notifications are being sent all over the app (synchronizing a month-based calendar, a week-based calendar and an event list) and have shown no performance problems until now. It's only at this point when they are being used with the gesture recognizer that performance becomes an issue.

David James
  • 2,430
  • 1
  • 26
  • 35
  • 2
    You really should use the Instruments tool and run the Time Profiler module to see where the code is really stalling for so long. To me it sounds like some operation is blocking the main UI thread instead of running off in its own thread. But without a time profile its just guesswork. – ExitToShell Sep 30 '13 at 19:59

3 Answers3

1

Firstly, you should profile to find out exactly what is taking the time. A 1.5 second delay is a long time whether it's now or delayed till a little while in the future.

When you post a notification it is broadcast and handled immediately. The best you can do is to post the notification on the next iteration of the runloop by using GCD or performSelector:withObject:afterDelay: (with a delay of 0).

Wain
  • 118,658
  • 15
  • 128
  • 151
  • Tried performSelector:withObject:afterDelay: with 0 delay and it did not resolve the initial performance problem. See also my answer below. – David James Sep 30 '13 at 19:45
  • 1
    You really need to do the profiling with Instruments to work out what is taking all the time / blocking... – Wain Sep 30 '13 at 19:48
1

Use NSNotificationQueue. That's the easiest way.

pickwick
  • 3,134
  • 22
  • 30
  • I haven't used it before—just read about it in documentation and in a book—but this seems to be by far the best answer. `NSNotificationQueue` was straight up built for exactly @David James problem. I totally disagree with the commenters saying to profile—the pan gesture recognizer is going to be firing off hundreds of actions in a short time span, so it won't necessarily be feasible to fix performance issues by speeding things up. @mbachrach's solution is also just the correct way to solve the performance problem: do *less* work, not more work faster. – MaxGabriel Oct 01 '13 at 03:22
  • I've tried NSNotificationQueue enqueueNotification: with postStyle NSPostIdle and NSPostASAP and still it blocked. Only thing I found that did not block was dispatch_async/performSelectorOnMainThread (in my answer ^). Also, I tend to agree with @MaxGabriel, it's probably the pan gesture recognizer (in the UIGestureRecognizerStateChanged block) causing a backup of processing bandwidth. When it gets to the notification the OS has to play catch up. I'll focus on that first. – David James Oct 01 '13 at 07:45
0

Using dispatch_async() with performSelectorOnMainThread:withObject:waitUntilDone: is one solution to "firing NSNotification without delaying current operations". NSNotificationQueue may be another solution, but I have not found it as effective as dispatch_async.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    [self performSelectorOnMainThread:@selector(performNotification:) withObject:someObject waitUntilDone:NO];
});

- (void)performNotification:(SomeClass*)someObject
{
    [[NSNotificationCenter defaultCenter] postNotificationName:MyNotificationName object:someObject];
}

(via this SO article)

Community
  • 1
  • 1
David James
  • 2,430
  • 1
  • 26
  • 35