自定义事件循环和 UIKit 控件。苹果的事件循环有什么额外的魔力?
有谁知道或有好的链接来解释 iPhone 的事件循环在幕后的作用吗?
我们在基于 OpenGL 的 iPhone 游戏框架中使用自定义事件循环。它调用我们的游戏渲染系统,调用presentRenderbuffer并使用CFRunLoopRunInMode泵送事件。详情请参阅下面的代码。
当我们不使用 UIKit 控件时,它效果很好(作为证明,请尝试 Facetap,我们的第一个发布的游戏)。
然而,当使用 UIKit 控件时,一切几乎都能工作,但又不完全有效。具体来说,UIKit 控件的滚动无法正常工作。
例如,让我们考虑以下场景。
- 我们在自己的视图之上显示 UIImagePickerController。
- UIImagePickerController 覆盖了我们的自定义视图
- 我们还暂停了自己的渲染,但继续使用自定义事件循环。
如前所述,除了滚动之外,一切正常。
- 挑选照片有效。
- 深入查看相册作品和过渡动画都很流畅。
- 当尝试滚动相册视图时,视图会跟随您的手指移动。
问题:滚动时,松开手指后滚动立即停止。通常,它会根据您的移动速度平稳地继续,但当我们使用自定义事件循环时则不然。看起来 iPhone 的事件循环正在执行一些与 UIKit 滚动相关的魔法,但我们自己还没有实现。
现在,我们可以使用 Apple 的事件循环并通过 NSTimer 回调调用我们自己的渲染,让 UIKit 控件与我们自己的系统一起正常工作。然而,我仍然想了解,iPhone 的事件循环内部可能发生了什么,而我们的自定义事件循环中没有实现。
- (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();
// bind 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;
}
}
}
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();
// bind 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;
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
如果您在
[UIScrollView isDecelerating]
时从[UIScrollView setContentOffset:]
中NSLog
[[NSRunLoop currentRunLoop] currentMode]
true 你会看到UITrackingRunLoopMode
。一般来说,系统会在主 UI 线程运行循环上使用除
kCFRunLoopDefaultMode
之外的模式,仅记录了其中的一些模式。获得完整系统行为的唯一方法是与主线程上的系统运行循环配合。您可以尝试使用
NSTimer
并让系统调用您,而不是自己调用CFRunLoopRunInMode
。 NSTimer 可以随着时间的推移自由运行,并且当没有显示其他 UI 时,系统运行循环除了调用计时器之外不会执行任何操作。另一种方法是在显示系统控件时从 customEventLoop 函数返回,并在恢复自定义 UI 时再次调用它。
If you
NSLog
[[NSRunLoop currentRunLoop] currentMode]
from[UIScrollView setContentOffset:]
when[UIScrollView isDecelerating]
is true you will seeUITrackingRunLoopMode
.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 callingCFRunLoopRunInMode
yourself. AnNSTimer
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.