The suggestion of @MikeDunlavey to set up a timer was the right solution for me. A real timer wouldn't work in my case, so I used a background thread with a high priority and a NSCondition
(a lower level mechanism like pthread_cond_timedwait
would work as well). With each runloop iteration, the condition is triggered. If the main thread takes too long, the condition aborts.
Here's the very crude code I've used. Since my problem was in the very busy startup, this was sufficient for me to detect my issues; if you want to use this to detect issues during "normal" runtime you need to enhance it to handle the waiting part of the runloop.
I wasn't able to catch all problems with this solution, though; scrolling is still a bit jittery but it's way better than before.
- (void)startWatchdog
{
static NSDate * lastIteration;
static BOOL hasHandled = NO;
NSCondition * condition = [[NSCondition alloc] init];
lastIteration = [NSDate date];
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL,
kCFRunLoopBeforeTimers | kCFRunLoopBeforeWaiting, true, 0,
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSDate * now = [NSDate date];
if (activity == kCFRunLoopBeforeTimers) {
if (!hasHandled) {
[condition lock];
[condition signal];
[condition unlock];
}
lastIteration = now;
hasHandled = NO;
} else {
// About to wait for events. We might want tell the watchdog method
// that it's OK that there's going to be a timeout.
[condition lock];
[condition signal];
[condition unlock];
hasHandled = YES;
}
}
);
NSThread * watchdog = [[NSThread alloc] initWithTarget:self selector:@selector(watchdog:) object:condition];
[watchdog setThreadPriority:1];
[watchdog start];
CFRunLoopRef runloop = [[NSRunLoop currentRunLoop] getCFRunLoop];
CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
CFRunLoopAddObserver(runloop, observer, (CFStringRef)UITrackingRunLoopMode);
}
- (void)watchdog:(NSCondition *)condition
{
@autoreleasepool {
while (1) {
BOOL wasHandled;
NSDate * start = [NSDate date];
[condition lock];
wasHandled = [condition waitUntilDate:[start dateByAddingTimeInterval:0.2]];
[condition unlock];
if (!wasHandled) {
NSLog(@"-- Timeout: %f", [[NSDate date] timeIntervalSinceDate:start]);
}
}
}
}