如何包装异步类以使其同步?使用 NSRunLoop?

发布于 2024-09-13 06:38:46 字数 1812 浏览 2 评论 0原文

我目前正在开发一个 iPhone 应用程序,我有一个来自第三方的库,它具有异步行为,但我想用我自己的类包装它并使其看起来同步。

该库中的中心类(我们称之为 Connection 类)具有多个函数,当调用委托类实例上的方法时,这些函数将解析其最终结果。我想做的是包装此类和委托,使其看起来是同步的而不是异步的。如果我在 Java 中执行此操作,我会使用 FutureTask 或 CountdownLatch 或只是 join()。但我不确定在 Objective C 中执行此操作的最佳方法。

我首先创建一个 NSThread 扩展 NFCThread,它符合上述委托协议。我的想法是,我将初始化 NFCThread,将 NFCThread 实例传递给 Connection 的 setDelegate 方法,启动线程,然后调用 Connection 上的异步方法。我的期望是,最终将调用 NFCThread 实例上的三个委托方法之一,导致线程退出。

为了模拟连接,我执行了以下操作。我向 NFCThread 添加了 NSConditionalLock:

joinLock = [[NSConditionLock alloc] initWithCondition:NO];

调用 Connection 的代码看起来像这样:

NFCThread *t = [[NFCThread alloc] init];
[connection setDelegate:t];
[t start];

[connection openSession];
// Process errors, etc...

[t.joinLock lockWhenCondition:YES];
[t.joinLock unlock];
[t release];
[connection setDelegate:nil];

委托的协议具有三个方法。在 NFCThread 中,我实现了每个方法,如下所示:

- (void)didReceiveMessage:(CommandType)cmdType 
                     data:(NSString *)responseData 
               length:(NSInteger)length {
    NSLog(@"didReceiveMessage");
    // Do something with data and cmdType...
    [joinLock lock];
    [joinLock unlockWithCondition:YES];
    callBackInvoked = YES;
}

我重载了 NFCThread 的 main 方法,以便它不断循环。像这样的事情:

while (!callBackInvoked) { ; }

我发现这并不是一个好主意,因为它会导致 cpu 使用率飙升。因此,我尝试使用我在本网站上找到的一些示例中的运行循环:

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

while (!callBackInvoked) {
    [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

在我的两个实现中,主线程始终被阻塞,并且似乎没有调用任何委托方法。但是,我知道该库运行正常,并且通常会调用对委托方法的调用。

我觉得我在这里遗漏了一些明显的东西。非常感谢任何帮助。

富有的

I'm currently working on an iPhone app and I have a library from a third-party that has asynchronous behavior but that I'd like to wrap with my own class and make it appear synchronous.

The central class in this library, let's call it the Connection class, has several functions that have their ultimate result resolved when methods on an instance of a delegate class are called. What I'm trying to do is wrap this class and delegate so that it appears to be synchronous instead of asynchronous. If I were doing this in Java I would use FutureTask or a CountdownLatch or just join(). But I'm not sure the best way to do this in Objective C.

I started by creating a NSThread extenstion, NFCThread, which conforms to the above mentioned delegate protocol. The idea is that I would init and NFCThread, pass the NFCThread instance to Connection's setDelegate method, start the thread and then call an asynchronous method on Connection. My expectation is that one of the three delegate methods on the NFCThread instance would be called ultimately causing the thread to exit.

To simulate a join I did the following. I added a NSConditionalLock to NFCThread:

joinLock = [[NSConditionLock alloc] initWithCondition:NO];

The code around the call to Connection looks something like this:

NFCThread *t = [[NFCThread alloc] init];
[connection setDelegate:t];
[t start];

[connection openSession];
// Process errors, etc...

[t.joinLock lockWhenCondition:YES];
[t.joinLock unlock];
[t release];
[connection setDelegate:nil];

The protocol for the delegate has three methods. In NFCThread I implemented each method something like this:

- (void)didReceiveMessage:(CommandType)cmdType 
                     data:(NSString *)responseData 
               length:(NSInteger)length {
    NSLog(@"didReceiveMessage");
    // Do something with data and cmdType...
    [joinLock lock];
    [joinLock unlockWithCondition:YES];
    callBackInvoked = YES;
}

I overloaded NFCThread's main method so that it just loops continually. Something like this:

while (!callBackInvoked) { ; }

I found that this isn't really a good idea since it cause cpu usage to go through the roof. So instead I tried using a run loop from some examples I found on this site:

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

while (!callBackInvoked) {
    [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

In both of my implementations the main thread is always blocked and it appears that none of the delegate methods are ever called. However, I know that the library is functioning properly and that calls to the delegate methods are normally called.

I feel like I'm missing something obvious here. Any help much appreciated.

Rich

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

一直在等你来 2024-09-20 06:38:47

我刚刚实现了类似的东西。背景是:
- 在后台线程中包装对 ZipArchive 的多次调用
- 每次调用解压缩时,显示不同的进度表(无限旋转的轮子,并显示正在扩展的文件名)
- 当所有档案都已展开时执行清理任务

结果 NSConditionLock 使其变得简单,代码的工作原理如下:

NSConditionLock* lock = alloc/init
for(int idx = 0; idx < [list count]; idx++) {
  NSString* fileName = ["getIdx's element in list"]
  [cond lockWhenCondition:idx]; //

  ... prepare things before forking

  [cond unlockWithCondition:-1];
  [Notification forkBlock:^(void){
    [cond lockWhenCondition:-1];

    NSAutoreleasePool *pool = [alloc/init];
    [ZipArchive unzipFile:fileName];
    [pool release];
    [cond unlockWithCondition:idx+1];
  }];
}

[cond lockWhenCondition:[list count];
// all the tasks are now completed

每个块都在后台线程中调度。 Notification 类负责使用旋转轮对 UIView 进行动画处理,并将该块包装在另一个线程中。锁定/解锁必须从同一线程调用,因此条件是启用后台线程和后台线程之间的乒乓(-1 表示后台,1,2,3..n 表示前台)。

I just implemented something similar. The context was:
- wrap several calls to ZipArchive in a background thread
- for each call to unzip, display a different progress meter (infinitely spinning wheel with the name of the file being expanded)
- perform a cleanup task when all the archives have been expanded

Turns out NSConditionLock makes it straightforward, and the code works as follows:

NSConditionLock* lock = alloc/init
for(int idx = 0; idx < [list count]; idx++) {
  NSString* fileName = ["getIdx's element in list"]
  [cond lockWhenCondition:idx]; //

  ... prepare things before forking

  [cond unlockWithCondition:-1];
  [Notification forkBlock:^(void){
    [cond lockWhenCondition:-1];

    NSAutoreleasePool *pool = [alloc/init];
    [ZipArchive unzipFile:fileName];
    [pool release];
    [cond unlockWithCondition:idx+1];
  }];
}

[cond lockWhenCondition:[list count];
// all the tasks are now completed

Each of the blocks are scheduled in a background thread. The Notification class takes care of animating the UIView with the spinning wheel and wrapping the block in another thread. Lock/unlock must be called from the same thread, so the condition is what enables the ping-pong between from and background threads (-1 for the background, 1,2,3..n for the foreground).

心舞飞扬 2024-09-20 06:38:47

好吧,这里有一些不同的问题,我正在考虑从哪里开始。

但是,为了让我们理解您想要完成的任务,当您说您希望调用“看起来”同步时,您的意思是您希望调用被阻止吗?您是从主线程进行此调用吗?如果是这样,那么您似乎有意阻止主线程。

请记住,第三方库可能正在主运行循环上安排事件。您可以创建自己的运行循环并在另一个线程中运行它,但是您是否告诉其他库将该运行循环用于其事件? (例如,它可能会发出安排在您已阻止的主运行循环上的异步网络请求)

我会重新考虑一下您在做什么,但首先我们需要知道您的意图是否是阻止线程您正在拨打此电话。另外,需要支持iPhoneOS 3.x吗?

Ok, so there are a few different issues here, I'm trying to think where to start.

But just so we understand what you're trying to accomplish, when you say you want the call to "appear" synchronous, do you mean you want the call to block? Are you making this call from the main thread? If so, then it seems you are blocking the main thread by design.

Keep in mind the third party library is probably scheduling events on the main run loop. You can create your own run loop and run it in another thread, but have you told the other library to use that run loop for its events? (For example, it could be making async network requests that are scheduled on the main run loop which you have blocked)

I would rethink what you're doing a bit, but first we would need to know if your intention is to block the thread from which you are making this call. Also, do you need to support iPhoneOS 3.x?

爱你是孤单的心事 2024-09-20 06:38:47

您想要做的是使用 NSCondition 等待当前线程,并在第二个线程上发出信号以让第一个线程继续。

- (void) firstThread
{
    workIsDone = NO;
    //Start second thread here
    [condition lock];
    while (!workIsDone) {
        [condition wait];
    }
    [condition unlock];

    // Keep going
}

- (void) secondThread
{
    [condition lock];

    //Do some work.
    workIsDone = YES;
    [condition signal];
    [condition unlock];
}

What you want to do is to use NSCondition to wait on the current thread, and signal on the second thread to let the first one continue.

- (void) firstThread
{
    workIsDone = NO;
    //Start second thread here
    [condition lock];
    while (!workIsDone) {
        [condition wait];
    }
    [condition unlock];

    // Keep going
}

- (void) secondThread
{
    [condition lock];

    //Do some work.
    workIsDone = YES;
    [condition signal];
    [condition unlock];
}
街角迷惘 2024-09-20 06:38:46

您需要一个信号量,它将允许您的主代码路径阻塞,直到您的异步回调向信号量发出信号并允许其继续。

iOS 4 中可以通过 Grand Central Dispatch 使用信号量。

看来信号量的行为可以在 iOS 3 中通过 NSCondition 来实现。

You want a semaphore, which will allow your primary codepath to block until your asynchronous callback signals the semaphore and allows it to continue.

Semaphores are available in iOS 4 through Grand Central Dispatch.

It appears that the behavior of semaphores can be implemented in iOS 3 with NSCondition.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文