Objective-C:如果线程不是主线程,则不会调用 [NSObject performSelector: onThread...]

Objective-C: [NSObject performSelector: onThread...] does not get called if the thread is not the main one

已经讨论了非常相似的问题 here。手头的问题和我想要实现的是在创建它的线程中调用给定对象的函数。这是完整的案例:

  1. 在给定的 NSThread(线程 A)(不是主线程)中创建了 class A 的实例。该实例将其创建 NSThread 作为成员变量。
  2. class B 的一个实例在另一个 NSThread - 线程 B 中执行其成员函数之一,并希望在 A 的创建线程中调用 A 的函数。因此 B 当前正在执行的函数发出以下调用:

    [_a performSelector: @(fun) 
               onThread: _a.creationThread
             withObject: nil
          waitUntilDone: NO];
    

如果 A 实例的创建线程不是主线程,fun 永远不会被调用。如果创建线程是主线程,它总是被调用。首先我在想创建 A 实例的线程是否已被销毁并且指针指向无效线程但实际上调用线程对象(线程 A)上的任何函数都会产生有效结果并且没有崩溃。还根据 this check 检查对象是否有效。有什么建议吗?


更新:

我正在做的是在后台线程上创建一个计时器:

_timer = [NSTimer timerWithTimeInterval:60.0 target:self selector:@selector(fun:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];

此代码是启动计时器的代码。计时器未在任何特定的后台线程中启动。碰巧创建计时器的函数可以在任何线程中调用。因此,计时器应该在与 NSTimer 文档所述完全相同的情况下失效:"you should always call the invalidate method from the same thread on which the timer was installed."

对于后台线程上的 运行 计时器,您有两个选择。

  1. 使用调度计时器来源:

    @property (nonatomic, strong) dispatch_source_t timer;
    

    然后您可以将此计时器配置为每两秒触发一次:

    - (void)startTimer {
        dispatch_queue_t queue = dispatch_queue_create("com.domain.app.timer", 0);
        self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
        dispatch_source_set_event_handler(self.timer, ^{
            // call whatever you want here
        });
        dispatch_resume(self.timer);
    }
    
    - (void)stopTimer {
        dispatch_cancel(self.timer);
        self.timer = nil;
    }
    
  2. 运行 NSTimer 在后台线程上。为此,您可以执行以下操作:

    @property (atomic) BOOL shouldKeepRunning;
    @property (nonatomic, strong) NSThread *timerThread;
    @property (nonatomic, strong) NSTimer *timer;
    

    - (void)startTimerThread {
        self.timerThread = [[NSThread alloc] initWithTarget:self selector:@selector(startTimer:) object:nil];
        [self.timerThread start];
    }
    
    - (void)stopTimerThread {
        [self performSelector:@selector(stopTimer:) onThread:self.timerThread withObject:nil waitUntilDone:false];
    }
    
    - (void)startTimer:(id)__unused object {
        @autoreleasepool {
            NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
            self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(handleTimer:) userInfo:nil repeats:YES];
            [runLoop addTimer:self.timer forMode:NSDefaultRunLoopMode];
    
            self.shouldKeepRunning = YES;
            while (self.shouldKeepRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
                ;
    
            self.timerThread = nil;
        }
    }
    
    - (void)handleTimer:(NSTimer *)timer {
        NSLog(@"tick");
    }
    
    - (void)stopTimer:(id)__unused object {
        [self.timer invalidate];
        self.timer = nil;
        self.shouldKeepRunning = FALSE;
    }
    

    我不喜欢 shouldKeepRunning 状态变量,但是如果你看看 Apple documentation for the run method,他们不鼓励将 sources/timers 添加到 运行循环:

    If you want the run loop to terminate, you shouldn't use this method. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop. A simple example would be:

    BOOL shouldKeepRunning = YES;        // global
    NSRunLoop *theRL = [NSRunLoop currentRunLoop];
    while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
    

就个人而言,我推荐调度计时器方法。