如何在特定线程(非主线程)中运行方法

4

我在一个特定的线程(非主线程)中每10毫秒调用一次heartBeats方法,如何在这个相同的线程中随时调用另一个方法?

我像这样子类化了NSThread

@implementation MyThread
{
    NSTimeInterval _lastTimeInterval;
}

- (void)main
{

    while (true) {

        NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970]*1000;

        if (timeInterval - _lastTimeInterval > 10)
        {
            [self heartBeats];

            _lastTimeInterval = timeInterval;
        }

    }

}

- (void)heartBeats
{
    NSLog(@"heart beats thread: %@", [NSThread currentThread].description);
}

@end

并像这样运行它

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"main thread: %@", [NSThread currentThread].description);

    MyThread *myThread = [[MyThread alloc]init];
    [myThread start];
}


- (void)someMethod
{
    // do somthing
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

@end

现在的问题是如何在 myThread 中运行 - (void)someMethod 方法?

忙碌的轮询代码无论在哪里都很糟糕,但特别是在移动设备上。 - Paulw11
@Paulw11 是的,但那是不可能的。实际上,heartBeats是一个我必须这样调用的API,以保持这个长连接。:( - Grey
将您的if代码替换为[NSThread sleepForTimeInterval:0.007];可将CPU消耗从98%降至2%,并导致间隔在7.6毫秒至9.8毫秒之间。使用[NSThread sleepForTimeInterval:0.0002];与您的if代码给出了更接近10.3毫秒的间隔,并使用了9%的CPU,但简短的答案是您将不得不使用某种同步和共享内存数据结构来使您的循环确定它应该执行一些其他方法。您不能像使用GCD队列那样简单地在线程上调度方法。 - Paulw11
然后,您需要设置一些信号量,以便您的线程能够识别并使用它来执行其他方法。 - Paulw11
@Paulw11,您能否给我更多信息?提供一个演示将是完美的。谢谢。 - Grey
显示剩余3条评论
1个回答

4
你的NSThread子类的main方法是在该线程上运行的所有内容。你不能中断它以运行其他代码,除非main方法合作。
你真正应该做的是放弃整个循环并用NSRunLoop和NSTimer替换它。
- NSRunLoop保持线程活动,只要有需要做的事情,但也会使线程休眠,直到需要做某些事情。 - 只要在运行循环上安排了NSTimer,它就会按重复间隔执行某些操作。
你需要让你的线程做两件事:
- 向MyThread对象发送heartBeats消息(你正在这样做) - 向视图控制器发送someMethod消息(这就是你所问的)
对于后者,你需要一个额外的东西:一个运行循环源。
因此,请清除你的main方法,并将其替换为以下内容:
1. 获取当前的NSRunLoop并将其存储在本地变量中。 2. 创建一个NSTimer,其中包含10秒的间隔,其目标为self,选择器为heartBeats。(稍微干净一点的版本:再创建一个方法,它接受一个NSTimer *,但忽略它,因此你的计时器调用那个方法,而那个方法调用heartBeats。设置定时器的正确方法是给它一个带有计时器参数的方法,但在实践中,给它一个不带参数的方法也可以。) 3. 如果你没有使用scheduledTimerWith…创建计时器,请将其添加到运行循环中。(scheduledTimerWith…方法会为你完成此操作。) 4. 创建一个运行循环源并将其添加到运行循环中。 5. 调用[myRunLoop run]
第四步需要解释一下:
Core Foundation(但不是Foundation;我不知道为什么)有一个称为“运行循环源”的东西,它是可以添加到运行循环中并在一次发出信号时调用某些内容的自定义对象。
听起来就像你想要的,调用你的视图控制器方法!
首先,在视图控制器中,将myThreadviewDidLoad中的局部变量更改为实例变量。将其重命名为_myThread以明确这一点。
接下来,向MyThread添加一个delegate属性。这应该是weak类型,类型为id <MyThreadDelegate>。你还需要定义一个MyThreadDelegate协议,它应该有一个不带参数且不返回任何内容(void)的方法。
现在您应该能够从视图控制器中设置 _myThread.delegate = self 并在视图控制器中实现您在协议中声明的方法。现在,视图控制器是其 MyThread 的代理。
-[MyThread main] 中,创建版本0的 CFRunLoopSource。Create 函数需要一个“context”结构,其中包含版本(0)、“info”指针(将其设置为self,即 MyThread)和 Perform 回调(一个函数,将以info指针作为其唯一参数调用)。
在您的 perform 回调中,您需要执行类似以下操作:
MyThread *self = (__bridge MyThread *)info;
[self fireDelegateMessage];

在MyThread类中,实现fireDelegateMessage方法。在该方法中,向self.delegate发送您在协议中声明的消息。
接下来,在MyThread中添加一个公共方法(即在MyThread.h中声明并在MyThread.m中实现),命名为“requestDelegateMessage”之类的名称。在此方法中,调用run loop源上的CFRunLoopSourceSignal。(该函数的文档建议您还需要在线程的CFRunLoop上调用CFRunLoopWakeUp。尝试不先调用CFRunLoopWakeUp。)
最后,当视图控制器想要在该线程上调用someMethod时,请调用[_myThread requestDelegateMessage]
所以:
  1. 视图控制器调用requestDelegateMessage
  2. requestDelegateMessage信号运行循环源(如果需要,唤醒运行循环)
  3. 运行循环源调用MyThread线程上的执行回调
  4. 执行回调在MyThread线程上调用fireDelegateMessage
  5. fireDelegateMessage在MyThread线程上调用视图控制器的委托方法的实现
  6. 视图控制器在MyThread线程上调用someMethod

谢谢你的出色回答,它有效了!但我注意到,如果我给计时器足够长的时间间隔,someMethod 会延迟。 - Grey
@GaojinHsu:这就是定时器的作用,而通过委托方法或分派块根据需要执行某些操作通常比通过定时器周期性地执行更好。 - Peter Hosey
@GaojinHsu:另外,我误读了你说的10毫秒而不是10秒的部分。 10毫秒真的非常频繁! 它比屏幕更新速度(60 Hz = 1/60秒= 16 + 2/3毫秒)还要快,几乎肯定比你的计时器实际执行的速度更快-这意味着做工作将使计时器无法按照您想要的速度更新。 计时器可能不是首选的方法来完成您正在进行的任何操作,如果是,则不应该那么频繁。 - Peter Hosey

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