当向自身发送消息时,自身是否会被保留?

4

TestObj 类是一个简单的类,其中有一个名为 doSomethingInBackground 的方法,在该方法中我使用 performSelectorInBackground 方法将其自身置于后台线程睡眠5秒。

@implementation TestObj

- (void)dealloc
{
    NSLog(@"%@, is main thread %u", NSStringFromSelector(_cmd), [NSThread isMainThread]) ;
}

- (void)doSomethingInBackground
{
    [self performSelectorInBackground:@selector(backgroundWork) withObject:nil] ;
}

- (void)backgroundWork
{
    sleep(5) ;
}

@end

我分配并初始化实例,将其发送到 doSomethingInBackground 消息,并将 nil 赋值给它,以便尽快释放它。

TestObj *obj = [[TestObj alloc] init] ;
[obj doSomethingInBackground] ;
obj = nil ;

我发现在执行obj = nil;约5秒后,dealloc方法会运行。看起来当系统发送[self performSelectorInBackground:@selector(backgroundWork) withObject:nil] ;方法时,会保留self,并且在backgroundWork返回后,该实例将被释放。

有人能告诉我系统在背后做了什么吗?谢谢。

3个回答

2

从文档中得知,

performSelectorInBackground:withObject: 方法会创建一个新的分离线程,并使用指定的方法作为新线程的入口点。例如,如果你有一个对象(用变量 myObj 表示),并且该对象有一个名为 doSomething 的方法,你想在后台线程中运行它,你可以使用以下代码:

[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];

调用这个方法的效果相当于使用当前对象、选择器和参数对象作为参数调用 NSThreaddetachNewThreadSelector:toTarget:withObject: 方法。新线程将立即使用默认配置生成并开始运行。
detachNewThreadSelector:toTarget:withObject: 文档中,

aTarget 和 anArgument 对象在分离的线程执行期间被保留,然后释放。正在执行 aSelector 方法的 aTarget 完成后,分离的线程将退出(使用 exit 类方法)。

关于 performSelector:AfterDelay: ,

此方法设置一个定时器,在当前线程的运行循环上执行 aSelector 消息。定时器被配置为在默认模式(NSDefaultRunLoopMode)下运行。当定时器触发时,线程尝试从运行循环中出列消息并执行选择器。如果运行循环正在运行且处于默认模式,则它会成功;否则,定时器将等待运行循环处于默认模式。

还有

定时器将一直强引用此对象,直到它(定时器)失效。

如果您不希望 Obj 被保留,可以使用一个弱引用对象。
 TestObj *obj = [[TestObj alloc] init] ;
 __weak typeof(obj) weakObj = obj;
 [weakObj doSomethingInBackground] ;
 obj = nil ;

您是不是想要使用 [weakObj doSometingInBackground]?目前情况下,创建 weakObj 没有什么实际意义,因为它未被使用。 - The Paramagnetic Croissant
谢谢,你们俩都是对的,我接受回答更快的那个 :) - KudoCC

2

-[NSObject performSelectorInBackground:withObject:]在内部调用-[NSThread initWithTarget:selector:object:],该方法会保留原始的接收者(这里作为target参数传递)。

NSThread文档中写道:“在分离线程执行期间,对象的目标和参数被保留。它们在线程最终退出时被释放。”


如果我将 [self performSelectorInBackground:@selector(backgroundWork) withObject:nil] 更改为 [self performSelector:@selector(backgroundWork) withObject:nil afterDelay:5] 并且在 backgroundWork 方法中什么也不做,结果是相同的。因此,我认为这不是主要原因。 - KudoCC
1
@KudoCC的-[NSObject performSelector:withObject:afterDelay:]会直接保留接收器。在调用选择器后,通过CFRunLoopTimerInvalidate释放接收器。因此我认为-[NSObject performSelector:withObject:afterDelay:]会安排一个计时器来延迟方法调用。计时器会保留它们的目标对象。您可以很容易地通过禁用ARC,在TestObj中重载-retain-release方法并在其中设置断点来进行调查。(你可以运行这个https://gist.github.com/bartekchlebek/c36dba5d41bbe7b6fb5e ) - Bartek Chlebek

1

虽然其他答案中已经讨论了像performSelector:这样的特殊情况,但我认为在一般情况下增加答案是有帮助的:

当发送消息给自身时,self会被保留吗

不会。无论是手动保留计数还是ARC,self都不会隐式保留。您必须确保在方法执行期间,消息的接收者没有被释放。

尽管在ARC下,self的类型是strong,但实际上并没有被保留。请参见 ARC文档


感谢您提供的通用答案和非常有用的文档。 - KudoCC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接