Objective-C - 在继续之前等待另一个线程上的调用返回

时间:2013-12-10 15:44:29

标签: ios objective-c multithreading grand-central-dispatch

在我的iOS应用程序中,我有一个数据库调用,需要一些时间才能完成。在进行此操作时,我在屏幕上看到一个微调器。我在应用程序崩溃时遇到错误,“com.myapp无法及时恢复”,因此它似乎正在主线程上运行数据库调用,从而导致问题。

当前代码

-(void)timeToDoWork
{
    ...
    [CATransaction flush];

    [[DatabaseWorker staticInstance] doWork];

    //Additional UI stuff here
    ...

    if([self->myReceiver respondsToSelector:self->myMessage])
    {
        [self->myReceiver performSelector:self->myMessage];
    }
}

要让doWork函数在后台线程上进行,看起来我可以使用Grand Central Dispatch:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
    [[DatabaseWorker staticInstance] doWork];
});

但是,如何在执行完成之前阻止执行?我应该在doWork调用之后结束方法,并将其下面的所有内容移动到新函数吗?

样品

-(void)timeToDoWork
{
    ...
    [CATransaction flush];

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        [[DatabaseWorker staticInstance] doWork];
        dispatch_async(dispatch_get_main_queue(), ^{
            [self doneDoingWork];
        });
    });
}

-(void)doneDoingWork
{
    //Additional UI stuff here
    ...

    if([self->myReceiver respondsToSelector:self->myMessage])
    {
        [self->myReceiver performSelector:self->myMessage];
    }
}

有更好的方法吗?

4 个答案:

答案 0 :(得分:2)

您也可以使用块。 e.g ..

- (void)doWorkWithCompletionHandler:(void(^)())handler {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        // do your db stuff here...
        dispatch_async(dispatch_get_main_queue(), ^{
            handler();
        });
    });
}

然后像这样使用它:

[[DatabaseWorker staticInstance] doWorkWithCompletionHandler:^{
    // update your UI here, after the db operation is completed.
}];

P.S。 复制handler块可能是个好主意。

答案 1 :(得分:2)

防止主线程中的执行继续是非常糟糕的主意。 iOS将终止您的应用程序,因为主线程应始终使用运行循环。 我建议你按照以下方式处理你的问题: 写一个“储物柜”。让它显示动画微调器的一些视图,根本没有按钮。 当你开始调度异步操作时,只需将它带到前面,然后让它与run loop一起工作。 异步操作完成后关闭锁定器。

答案 2 :(得分:0)

我认为您应该在DatabaseWorker中使用委托,方法doWork始终在后台运行,因此当工作人员完成工作时,它会告诉其委托人工作已完成。必须在主线程中调用委托方法。

如果你有很多对象需要知道,当DatabaseWorker完成而不是使用委托时,我会使用通知。

编辑:

在DatabaseWorker类中,您需要实现方法doWork,如下所示:

- (void) doWork{
         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
         dispatch_async(queue, ^{
             //Do the work.
             dispatch_async(dispatch_get_main_queue(), ^{
                 [self.delegate finishWork];
             });
         });
}

在实现timeTodoWork的类中:

-(void)timeToDoWork
{
    ...
    [CATransaction flush];

    [[DatabaseWorker staticInstance] setDelegate:self];
    [[DatabaseWorker staticInstance] doWork];
}

#pragma mark DatabaseWorkerDelegate
- (void) finishWork{
    //Additional UI stuff here
    ...

    if([self->myReceiver respondsToSelector:self->myMessage])
    {
        [self->myReceiver performSelector:self->myMessage];
    }
}

您也可以使用:

[self performSelectorInBackground:@selector(doWorkInBackground) withObject:nil];

而不是:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
         dispatch_async(queue, ^{
             //Do the work.
});

添加方法:

- (void) doWorkInBackground{
       //Do the work
        [self.delegate performSelectorOnMainThread:@selector(finishWork) withObject:nil waitUntilDone:NO];
}

答案 3 :(得分:0)

您收到的错误表明您在application:didFinishLaunchingWithOptions:applicationDidBecomeAction:或启动周期中的其他位置执行的操作时间过长且应用程序正在被启动监视程序计时器终止。最重要的是,您必须尽快从这些方法返回。我不确定你的代码在发布周期中的位置;但这种解释似乎有道理。

有各种方法可以解决这个问题;但是,如果你注意到,那么从主队列中取出漫长的过程就是第一步。如果不了解主要队列对象(例如UI)依赖于此数据库事务的更多信息,我会说您建议的解决方案完全没问题。也就是说,将工作分配到后台队列;并在完成时将剩余的UI工作分配到主队列。

代表们在别处被建议作为解决方案。这也是可行的,尽管您仍然需要关注调用委托方法的队列。