线程和自动释放池问题
据我了解,有多种方法可以发送要在线程中执行的任务。我使用的最常见的是:
1)performSelector:withObject:afterDelay:
2)performSelectorOnMainThread:withObject:waitUntilDone:
3)performSelectorInBackground:withObject:
4)[NSThread detachNewThreadSelector:toTarget:withObject:]
我的第一个问题是,什么是除了明显的参数差异之外,1) 和 2) 之间的区别是什么?它们实际上都在主线程中工作吗(其自动释放池是在 main.m 中自动创建的)?我刚刚从 Stackoverflow 上某人的帖子中读到,方法 1) 实际上是在一个新线程中工作,因此应该为其选择器方法创建一个自动释放池。这是正确的吗?我经常使用 1),主要是为了利用延迟参数,但我从未为它们创建自动释放池。没有发生任何灾难性的事情。
接下来,3) 和 4) 都在单独的线程中执行任务。我听说 UI 的东西不应该在这些线程中完成,但我对什么是严格的 UI 感到困惑。 我试图编写代码来基本上播放重复加载动画,同时从导航控制器以模式方式启动表格视图。然后动画在 tableview 控制器的 viewDidLoad 方法中停止。最初,我只是将启动动画的代码粘贴在启动模态视图的代码行上方。发生的事情是动画从未播放过。
[[self loadingView] playAnimation];
SettingsViewController *menus = [[SettingsViewController alloc] initWithNibName:@"SettingsViewController" bundle:nil];
MyNavigationController *navController = [[MyNavigationController alloc] initWithRootViewController:menus];
[menus setParent:navController];
[navController setDelegate:self];
menus.mainViewController = self;
[self presentModalViewController:navController animated:YES];
[navController release];
[menus release];
然后我尝试了以下操作,它起作用了...
[NSThread detachNewThreadSelector:@selector(settingsOpeningThread) toTarget:self withObject:nil];
[[self loadingView] playAnimation];
- (void) settingsOpeningThread {
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
SettingsViewController *menus = [[SettingsViewController alloc] initWithNibName:@"SettingsViewController" bundle:nil];
MyNavigationController *navController = [[MyNavigationController alloc] initWithRootViewController:menus];
[menus setParent:navController];
[navController setDelegate:self];
menus.mainViewController = self;
[self presentModalViewController:navController animated:YES];
[navController release];
[menus release];
[apool release];
}
动画一直播放,直到 SettingsViewController 视图完全启动。但是启动这样的模态视图是否算作“UI”并且应该避免?另外,每次启动模态视图时,我都会在 Instruments 中遇到一些奇怪的内存泄漏错误。但它来自那些“系统库”之一,我被告知很难调试。这里可能出了什么问题?
很抱歉这篇令人尴尬的长帖子。任何帮助将不胜感激!
As I understand there are several ways to send tasks to be performed in threads. The most common ones that I use are:
1) performSelector:withObject:afterDelay:
2) performSelectorOnMainThread:withObject:waitUntilDone:
3) performSelectorInBackground:withObject:
4) [NSThread detachNewThreadSelector:toTarget:withObject:]
My first question is, what is the difference between 1) and 2), besides the obvious parameter differences? Are they actually both working in the Main thread (whose autorelease pool was automatically created in main.m)? I just read from someone's post on Stackoverflow that method 1) is actually working in a new thread, and so an autorelease pool should be created for its selector method. Is this correct? I've been using 1) a lot, mostly to take advantage of the delay parameter, but I've never created an autorelease pool for them. Nothing catastrophic has happened.
Next, 3) and 4) both perform tasks in a separate thread. I hear that UI stuff should never be done in these threads, but I'm confused as to what is strictly UI.
I was trying to write code to basically play a repeating loading animation while a tableview is launching modally from a navigationcontroller. The animation is then stopped in the viewDidLoad method of the tableview controller. Initially, I just stuck the code to start the animation above the lines of code that start the modal view. What happened was the animation was never played.
[[self loadingView] playAnimation];
SettingsViewController *menus = [[SettingsViewController alloc] initWithNibName:@"SettingsViewController" bundle:nil];
MyNavigationController *navController = [[MyNavigationController alloc] initWithRootViewController:menus];
[menus setParent:navController];
[navController setDelegate:self];
menus.mainViewController = self;
[self presentModalViewController:navController animated:YES];
[navController release];
[menus release];
I then tried the following, and it worked...
[NSThread detachNewThreadSelector:@selector(settingsOpeningThread) toTarget:self withObject:nil];
[[self loadingView] playAnimation];
- (void) settingsOpeningThread {
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
SettingsViewController *menus = [[SettingsViewController alloc] initWithNibName:@"SettingsViewController" bundle:nil];
MyNavigationController *navController = [[MyNavigationController alloc] initWithRootViewController:menus];
[menus setParent:navController];
[navController setDelegate:self];
menus.mainViewController = self;
[self presentModalViewController:navController animated:YES];
[navController release];
[menus release];
[apool release];
}
Animation keeps playing until the SettingsViewController view is fully launched. But does launching modal views like this count as "UI" and should be avoided? Also I am getting some weird memory leak errors in Instruments every time the modal view is launched. But it's from one of those "System Libraries", which I've been told is very difficult to debug. What might be going wrong here?
Sorry for the embarrassingly long post. Any help will be appreciated!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
(1) 为当前运行循环安排一个任务。在非常高的层面上,UIKit 应用程序看起来像
这就是为什么您在第一次尝试时没有看到 UI 更新的原因;对 playAnimation 的调用安排 UI 在运行循环的下一次迭代中更新,但直到其后面的代码完成之前它永远不会到达那里。
请注意,
performSelector:withObject:afterDelay
确实不会在单独的线程中运行指定的代码。(2) 做了一些非常相似的事情,但是不是为当前运行循环调度一些东西,而是为主线程上的运行循环调度一些东西。仅当从单独的线程调用时这才有用,通常是因为您想要从辅助线程更新 UI。
是的,你的代码有点粗体。我建议这样做:
加载表的实际代码位于
loadTable
中。这意味着当运行循环出现时,您的 UI 将更新,动画开始播放,然后调用loadTable
方法并完成其工作。但是,如果动画需要主线程的干预来执行,这仍然不起作用。也就是说,如果加载表的代码使主线程停止,您的动画也可能停止。除了在单独的线程中执行长时间运行任务(可能会也可能不会使用performSelector:onMainThread:waitUntilDone 来安排主线程上的UI 更新)之外,确实没有其他办法解决这个问题。
如果您不太关心动画本身,您可能会发现类似 https://github.com/samvermette /SVProgressHUD 有用。
(1) schedules a task for the current runloop. At a very high level, an UIKit application looks like
This is why you didn't see your UI updated in your first attempt; the call to
playAnimation
schedules the UI to be updated in the next iteration of the runloop, but it never gets there until the code that followed it is completed.Note that
performSelector:withObject:afterDelay
does not run the specified code in a seperate thread.(2) does something very similar, but rather than scheduling something for the current runloop it schedules something for the runloop on the main thread. This is only useful if invoked from a separate thread, typically because you want to update the UI from a secondary thread.
And yes, your code is slightly bold. I would suggest to do something like:
where the actual code to load the table is in
loadTable
. This means that when the runloop comes around, your UI will be updated, the animation starts playing, then theloadTable
method gets called and do its job.This still will not work however if the animation requires intervention from the main thread to execute. That is, if the code to load the table stalls the main thread, your animation might stall too. There really is no way around that except doing the long run task in a separate thread (which may or may not use
performSelector:onMainThread:waitUntilDone
to schedule UI updates on the main thread).If you don't care too much about the animation itself, you might find something like https://github.com/samvermette/SVProgressHUD useful.