• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

ios - 如何在 NSOperationQueue 上使用 NSRunLoop?

[复制链接]
菜鸟教程小白 发表于 2022-12-13 15:48:48 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题

我有一个通过蓝牙与 ExternalAccessory 通信的应用,响应有一些延迟,所以我希望 IO 在后台线程上发生。

我为单线程操作设置了一个 NSOperationQueue 以将我的请求排入队列:

self.sessionQueue = [NSOperationQueue new];
self.sessionQueue.maxConcurrentOperationCount = 1;

如果我计划读取和写入来自该队列的 EAAccessory 流,我的应用程序会崩溃,因为如果线程上没有 NSRunLoop 则无法传递来自套接字的数据队列正在使用。初始化队列后,我立即创建了一个带有空 NSMachPort 的运行循环,以保持它运行并启动它:

[self.sessionQueue addOperationWithBlock:^{
    NSRunLoop* queueLoop = [NSRunLoop currentRunLoop];
    [queueLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
    [queueLoop run]; // <- this blocks
}];

这会阻塞队列,因为运行循环永远不会退出,但我不确定如何正确管理运行循环,以便我可以成功地从附属流中读取。



Best Answer-推荐答案


您不应该尝试在 NSOperation 中运行运行循环。 Grand Central Dispatch 拥有运行该操作的线程。您应该启动自己的线程并将其运行循环用于 session 流。

However, you need to be aware that NSRunLoop is not generally thread safe, but CFRunLoop is.这意味着当您想在 session 处理线程上运行一个 block 时,您需要下拉到 CFRunLoop 级别。

此外,获得对后台线程运行循环的引用的唯一方法是在该后台线程上运行某些东西。所以第一步是创建自己的 NSThread 子类来导出自己的运行循环:

typedef void (^MyThreadStartCallback)(CFRunLoopRef runLoop);

@interface MyThread: NSThread

/// After I'm started, I dispatch to the main queue to call `callback`,
// passing my runloop. Then I destroy my reference to `callback`.
- (instancetype)initWithCallbackMyThreadStartCallback)callback;

@end

@implementation MyThread {
    MyThreadStartCallback _callback;
}

- (instancetype)initWithCallbackMyThreadStartCallback)callback {
    if (self = [super init]) {
        _callback = callback;
    }
    return self;
}

- (void)main {
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    dispatch_async(dispatch_get_main_queue(), ^{
        _callback(runLoop);
    });
    _callback = nil;
    CFRunLoopRun();
}

@end

现在您可以创建一个 MyThread 的实例,并传入一个回调。当你启动 MyThread 时,它会使回调在主线程上运行,并将它自己的(MyThread 的)运行循环传递给回调。因此,您可以使用 MyThread 作为 session 处理线程,如下所示:

@implementation Thing {
    CFRunLoopRef _sessionRunLoop;
}

- (void)scheduleStreamsOfSessionEASession *)session {
    MyThread *thread = [[MyThread alloc] initWithCallback:^(CFRunLoopRef runLoop) {
        // Here I'm on the main thread, but the session-handling thread has
        // started running and its run loop is `runLoop`.
        [self scheduleStreamsOfSession:session inRunLoop:runLoop];
    }];
    [thread start];
}

- (void)scheduleStreamsOfSessionEASession *)session inRunLoopCFRunLoopRef)runLoop {

    // Here I'm on the main thread. I'll save away the session-handling run loop
    // so I can run more blocks on it later, perhaps to queue data for writing
    // to the output stream.
    _sessionRunLoop = runLoop;

    NSInputStream *inputStream = session.inputStream;
    NSOutputStream *outputStream = session.outputStream;

    // Here I'm on the main thread, where it's not safe to use the
    // session-handling thread's NSRunLoop, so I'll send a block to
    // the session-handling thread.
    CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{

        // Here I'm on the session-handling thread, where it's safe to
        // use NSRunLoop to schedule the streams.
        NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
        [inputStream scheduleInRunLoop:currentRunLoop forMode:NSRunLoopCommonModes];
        [outputStream scheduleInRunLoop:currentRunLoop forMode:NSRunLoopCommonModes];

    });

    // CFRunLoopPerformBlock does **not** wake up the run loop. Since I want
    // to make sure the block runs as soon as possible, I have to wake up the
    // run loop manually:
    CFRunLoopWakeUp(_sessionRunLoop);
}

@end

关于ios - 如何在 NSOperationQueue 上使用 NSRunLoop?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36364179/

回复

使用道具 举报

懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关注0

粉丝2

帖子830918

发布主题
阅读排行 更多
广告位

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap