Does anyone know or have good links that explain what iPhone's event loop does under the hood?
We are using a custom event loop in our OpenGL-based iPhone game framework. It calls our game rendering system, calls presentRenderbuffer and pumps events using CFRunLoopRunInMode. See the code below for details.
It works well when we are not using UIKit controls (as a proof, try Facetap, our first released game).
However, when using UIKit controls, everything almost works, but not quite. Specifically, scrolling of UIKit controls doesn't work properly.
For example, let's consider following scenario.
- We show UIImagePickerController on top of our own view.
- UIImagePickerController covers our custom view
- We also pause our own rendering, but keep on using the custom event loop.
As said, everything works, except scrolling.
- Picking photos works.
- Drilling down to photo albums works and transition animations are smooth.
- When trying to scroll photo album view, the view follows your finger.
Problem: when scrolling, scrolling stops immediately after you lift your finger. Normally, it continues smoothly based on the speed of your movement, but not when we are using the custom event loop. It seems that iPhone's event loop is doing some magic related to UIKit scrolling that we haven't implemented ourselves.
Now, we can get UIKit controls to work just fine and dandy together with our own system by using Apple's event loop and calling our own rendering via NSTimer callbacks. However, I'd still like to understand, what is possibly happening inside iPhone's event loop that is not implemented in our custom event loop.
- (void)customEventLoop { OBJC_METHOD;
float excess = 0.0f;
while(isRunning) {
animationInterval = 1.0f / openGLapp->ticks_per_second();
// Calculate the target time to be used in this run of loop
float wait = max(0.0, animationInterval - excess);
Systemtime target = Systemtime::now().after_seconds(wait);
Scope("event loop");
NSAutoreleasePool* pool = [[ NSAutoreleasePool alloc] init];
// Call our own render system and present render buffer
[self drawView];
// Pump system events
[self handleSystemEvents:target];
[pool release];
excess = target.seconds_to_now();
}
}
- (void)drawView { OBJC_METHOD;
// call our own custom rendering
bool bind = openGLapp->app_render();
// bin开发者_StackOverflow中文版d the buffer to be THE renderbuffer and present its contents
if (bind) {
opengl::bind_renderbuffer(renderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
}
- (void) handleSystemEvents:(Systemtime)target { OBJC_METHOD;
SInt32 reason = 0;
double time_left = target.seconds_since_now();
if (time_left <= 0.0) {
while((reason = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, TRUE)) == kCFRunLoopRunHandledSource) {}
} else {
float dt = time_left;
while((reason = CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, FALSE)) == kCFRunLoopRunHandledSource) {
double time_left = target.seconds_since_now();
if (time_left <= 0.0) break;
dt = (float) time_left;
}
}
}
If you NSLog
[[NSRunLoop currentRunLoop] currentMode]
from [UIScrollView setContentOffset:]
when [UIScrollView isDecelerating]
is true you will see UITrackingRunLoopMode
.
In general, the system will use modes besides kCFRunLoopDefaultMode
on the main UI thread run loop, only some of which are documented. The only way to get full system behavior is to cooperate with the system run loop on the main thread.
You could try using an NSTimer
and letting the system call you instead of calling CFRunLoopRunInMode
yourself. An NSTimer
is free to run over time, and when no other UI is shown the system run loop would not be doing anything besides calling the timer.
The alternative would be to return from your customEventLoop function while system controls are being displayed, and call it again when resuming your custom UI.
精彩评论