为什么这些代码可以制作连续的子线程?

问题描述 投票:0回答:1
- (void)testCreateContinuousChildThread {
    testThread *thread = [[testThread alloc] initWithBlock:^{
        NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
            NSLog(@"current thread:%@", [NSThread currentThread]);
        }];
        NSRunLoop *loop = [NSRunLoop currentRunLoop];
        [loop addTimer:timer forMode:NSRunLoopCommonModes];
        [loop run];
    }];
    
    
    [thread start];
    
    [self performSelector:@selector(testSelector) onThread:thread withObject:nil waitUntilDone:YES];
}

- (void)testSelector {
    NSLog(@"testSelector");
}

'testThread'类仅包含dealloc方法来记录dealloc消息。并且运行这些代码后,testThread不会自动销毁。

当我删除这一行时,testThread 将按预期自动销毁。

[self performSelector:@selector(testSelector) onThread:thread withObject:nil waitUntilDone:YES];
ios objective-c multithreading nstimer runloop
1个回答
0
投票

这是一个很好的问题,涉及 NSRunLoop 如何工作的一些微妙细节。

-run
方法并不打算返回。如果没有源或计时器,则可能会这样,但这是明确不承诺(强调):

从运行循环中手动删除所有已知的输入源和计时器并不能保证运行循环将会退出。 macOS 可以根据需要安装和删除额外的输入源,以处理针对接收者线程的请求。 因此,这些源可能会阻止运行循环退出。

如果您希望运行循环终止,则不应使用此方法....

当您调用

-performSelector:onThread:...
时,系统将附加一个
__NSThreadPerformPerform
run lop 源(如果尚不存在)(这是一种未记录的内部类型)。一旦附加,该源就永远不会被删除,但它会在第一次需要时延迟添加。

定时器像源一样附加到运行循环。当它们失效时(例如在它们触发后,如果它们不重复),它们将被删除。

将所有这些事实放在一起可以解释这种行为。

当没有

-performSelector:onThread:...
调用时,计时器触发后,运行循环中没有任何源或计时器,并且
-run
退出,终止线程,释放 NSThread 对象,并调用
-dealloc

当您调用

-performSelector:onThread:...
时,线程上就有一个
__NSThreadPerformPerform
源。它永远不会被删除,所以
-run
永远不会回来。线程永远不会终止,NSThread 对象也永远不会被释放。

如果您希望线程终止,则不应使用

-run
。相反,请使用
-runMode:beforeDate:
。例如:

NSRunLoop *loop = [NSRunLoop currentRunLoop];

// The docs don't warn you to do this. This adds a dummy source so that
// the loop always has at least one. You can also use a repeating timer.
[loop addPort:[NSMachPort new] forMode:NSDefaultRunLoopMode];

// Infinite loop until `shouldKeepRunning` is false.
// `runMode:beforeDate:` will process one source and then return.
// (A timer is not a "source" for this purpose.)
while (![self isCancelled] && [loop runMode:NSDefaultRunLoopMode 
                                    beforeDate:[NSDate distantFuture]]);

您可以通过调用

isCancelled
来设置
[thread cancel]
。请记住,仅取消不会立即终止该线程。在处理选择器之后,它将在下次调用源修改
-performSelector:...
时终止。

如果您想投票

shouldKeepRunning
,您可以传递下次想要投票的
beforeDate:

如果你不想让线程终止,那么

-run
就可以了。只需添加一个重复计时器或一个虚拟 NSMachPort 即可使其保持活动状态,直到收到第一条消息。

作为一个非常普遍的规则,在现代 ObjC 中应该避免使用 NSThread。 GCD 为大多数传统上直接由 NSThread 处理的问题提供了更好的工具。但线程仍然得到完全支持,所以如果这符合您的需求,那就没有错。

© www.soinside.com 2019 - 2024. All rights reserved.