gpt4 book ai didi

ios - 为什么将UIViewController释放在主线程上?

转载 作者:可可西里 更新时间:2023-11-01 05:11:31 25 4
gpt4 key购买 nike

我最近在一些Objective-C代码中偶然发现了The Deallocation Problem。之前在Block_release deallocating UI objects on a background thread中的Stack Overflow上讨论了该主题。我想我了解这个问题及其含义,但是可以肯定的是,我想在一个小的测试项目中重现它。我首先创建了自己的SOUnsafeObject(=应该始终在主线程上释放的对象)。

@interface SOUnsafeObject : NSObject

@property (strong) NSString *title;

- (void)reloadDataInBackground;

@end


@implementation SOUnsafeObject

- (void)reloadDataInBackground {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
self.title = @"Retrieved data";
});

sleep(3);
});
}

- (void)dealloc {
NSAssert([NSThread isMainThread], @"Object should always be deallocated on the main thread");
}

@end}]

现在,按预期方式,如果我将 [[[SOUnsafeObject alloc] init] reloadDataInBackground];放入 application:didFinishLaunching..中,则该应用会在3秒后由于断言失败而崩溃。建议的修复程序似乎有效。 IE。如果我将 reloadDataInBackground的实现更改为:该应用程序不再崩溃:
__block SOUnsafeObject *safeSelf = self;

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
safeSelf.title = @"Retrieved data";
safeSelf = nil;
});

sleep(3);
});

好的,所以我对这个问题以及在ARC下如何解决这个问题的理解似乎是正确的。但是,请确保100%可以确定。.让我们对 UIViewController进行相同的尝试(因为 UIViewController可能会在现实生活中扮演 SOUnsafeObject的角色)。实现几乎与 SOUnsafeObject相同:
@interface SODemoViewController : UIViewController

- (void)reloadDataInBackground;

@end


@implementation SODemoViewController

- (void)reloadDataInBackground {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
self.title = @"Retrieved data";
});

sleep(3);
});
}

- (void)dealloc {
NSAssert([NSThread isMainThread], @"UI objects should always be deallocated on the main thread");
NSLog(@"I'm deallocated!");
}

@end

现在,让我们将 [[SODemoViewController alloc] init] reloadDataInBackground];放入 application:didFinishLaunching..。嗯,断言不会失败。.消息 I'm deallocated!在3秒钟后打印到控制台,因此我很确定 View Controller 将被释放。

为什么不安全对象被释放在后台线程上时, View Controller 为何被释放到主线程上?代码几乎相同。 UIKit是否在幕后做一些花哨的事情,以确保将 UIViewController始终释放在主线程上?我开始怀疑这一点,因为以下代码片段也不会破坏我的断言:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
SODemoViewController()
});

如果是这样,此行为是否记录在某处?可以依靠这种行为吗?还是我只是完全错了,这里有什么明显的我想念的东西吗?

注意:我完全知道我可以在这里使用 __weak引用的事实,但是让我们假设 View Controller 应该仍然活着,可以在主线程上执行我们的完成代码。另外,在尝试解决此问题之前,我正在尝试了解问题的核心。我将代码转换为Swift,并获得了与Objective-C相同的结果( SOUnsafeObject的修复在语法上更加丑陋)。

最佳答案

tl;博士-虽然我找不到官方文档,但是当前的实现确实确保了deallocUIViewController在主线程上发生。

我想我只能给出一个简单的答案,但是也许今天我可以做些“教人钓鱼”的事情。

好的。我在任何地方都找不到此文档,而且我也不记得曾经公开说过它。实际上,我始终不遗余力地确保将 View Controller 分配在主线程上,这是我第一次看到有人指出UIViewController对象自动分配在主线程上。

也许其他人可以找到一份正式声明,但我找不到。

但是,我确实有证据证明确实如此。实际上,起初,我以为您没有正确处理块或引用计数,并且某种程度上在主线程上保留了引用。

但是,经过粗略的看后,我很有兴趣亲自尝试一下。为了满足我的好奇心,我制作了一个类似于您的从UIViewController继承的类。它的dealloc运行在主线程上。

因此,我只是将基类更改为UIResponder,它是UIViewController的基类,然后再次运行它。这次,它的dealloc在后台线程上运行。

嗯也许闭门造车。我们有很多调试技巧。答案总是取决于您尝试的最后一个方法,但是我认为我会尝试使用通常的技巧来解决此类问题。

日志通知

找出所有实现方式的我最喜欢的工具之一是记录所有通知。

[[NSNotificationCenter defaultCenter]
addObserverForName:nil
object:nil
queue:nil
usingBlock:^(NSNotification *note) { NSLog(@"%@", note); }];

然后,我使用了这两个类,并且没有发现两者之间有任何意外或不同之处。我没想到,但是这个小技巧很简单,它极大地帮助了我发现许多其他事情的工作原理,因此通常是第一件事。

日志方法/消息发送

我的第二个技巧是启用方法日志记录。但是,我不想记录所有方法,仅记录最后一个块执行到调用dealloc之间发生的情况。因此,通过将其添加为“sleeping”块的最后一行来打开方法日志记录。
instrumentObjcMessageSends(YES);

然后我关闭了登录,并将其作为 dealloc方法的第一行。
instrumentObjcMessageSends(NO);

现在,在我所知道的任何 header 中都找不到这个C函数,因此您需要在文件顶部声明它。
extern void instrumentObjcMessageSends(BOOL);

日志进入/tmp中名为msgSends-的唯一文件。

两次运行的文件包含以下输出。
$ cat msgSends-72013
- __NSMallocBlock__ __NSMallocBlock release
- SOUnsafeObject SOUnsafeObject dealloc

$ cat msgSends-72057
- __NSMallocBlock__ __NSMallocBlock release
- SOUnsafeObject UIViewController release
- SOUnsafeObject SOUnsafeObject dealloc

对此并没有太大的惊讶。但是, UIViewController release的存在表明 UIViewController+release方法具有特殊的替代实现。我想知道为什么?可以专门将对 dealloc的调用转移到主线程吗?

调试器

是的,这是我想到的第一件事,但是我没有证据表明 UIViewController中存在替代,因此我可以正常进行操作。我发现当我跳过步骤时,通常需要更长的时间。

无论如何,既然我们知道了要寻找的内容,就在“sleeping”块的最后一行放置一个断点,并使该类继承自 UIViewController

当我达到断点时,我添加了一个手动断点...
(lldb) b [UIViewController release]
Breakpoint 3: where = UIKit`-[UIViewController release], address = 0x000000010e814d1a

继续之后,我迎接了这个很棒的程序集,该程序集在视觉上确认了正在发生的事情。

enter image description here
pthread_main_np是一个告诉您是否在主线程上运行的函数。逐步执行汇编指令,确认我们没有在主线程上运行。

进一步走到第27行,跳过对 dealloc的调用,而是运行您可以轻松看到的代码,以便在主线程上运行dealloc-helper。

您可以指望这一进展吗?

由于找不到文档,所以我不知道我会一直指望这种情况,但是它非常方便,而且显然是他们有意将其放入代码中的。

每当Apple发行新版本的iOS和OSX时,我都会运行一组测试。我认为大多数开发人员都做类似的事情。我认为我要做的是编写一个单元测试,并将其添加到该集合中。因此,如果他们把它改回来,我会尽快知道的。

否则,我倾向于认为这可能是可以安全地假设的事情之一。

但是,请注意,子类可能会选择覆盖 release(如果在禁用ARC的情况下进行编译),并且如果它们不调用基类实现,则不会出现此行为。

因此,您可能想为所使用的任何第三方 View Controller 类编写测试。

我的详细信息

我只在模拟iPhone 6的情况下使用XCode 6.4,部署目标8.4进行了测试,而将其他版本的测试作为练习留给读者。

顺便说一句,如果您不介意,您发布的示例的详细信息是什么?

关于ios - 为什么将UIViewController释放在主线程上?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32006565/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com