ios - 使用 dispatch_semaphore 时泄漏

标签 ios memory-leaks grand-central-dispatch

有一些 Action 我想在不同的队列中做,所以我使用信号量。但是我用仪器检查它时有泄漏。该框架是 dispatch_semaphore_create。我用的是ARC,之前检查的时候没有漏水。代码如下:

dispatch_async(queue,^{
     dispatch_semaphore_t signal = dispatch_semaphore_create(1);
     for (int i=0;i<100;i++)
     {
            dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
            dispatch_async(queue,^{
                /* some actions */
                dispatch_semaphore_signal(signal);
            }); 
     }
});

代码就是这样,我是WINDOWS电脑输入的,所以有些拼写错误,请见谅。

附言

我想一个一个地删除带有动画的 TableView 的单元格,如果我使用 deleteRowsAtIndexpaths 方法使用它们的索引路径数组删除它们,或者循环删除它们在主队列中,它们将被一起删除,而不是一个一个,所以我用两个队列来做。代码如下:

dispatch_async(queue,^{

     dispatch_semaphore_t signal = dispatch_semaphore_create(1);

     for (int i=0;;i++)
     {
            dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);

            if (i == [indexPathsAry count])
            {
                 dispatch_semaphore_signal(finishAllSignal);
                 break;
            }

            dispatch_async(main_queue,^{
                NSIndexPath *indexPath = indexPathsAry[0];
                id item = items[indexPath.row];
                [items removeObject:item];

                [tableView beginUpdates];
                [tableView deleteRowsAtIndexPaths:indexPath withAnimation:...];
                [tableView endUpdates];
                dispatch_semaphore_signal(signal);
            }); 
     }
});

附言 抱歉,NSIndexPath *indexPath = indexPathsAry[i];需要改为 NSIndexPath *indexPath = indexPathsAry[0];该代码将正确地一个一个地删除单元格,而不是一起删除它们。它说 dispatch_semaphore_t signal = dispatch_semaphore_create(1); 行有泄漏但是当我在一个小演示中测试这段代码时,它运行良好,所以我不知道为什么。起初我使用 dispatch_sync 而不是信号量,但它有时不能同步工作,这让我很困惑。是不是我的项目队列太多了?

最佳答案

从您发布的代码中看不出泄漏的原因。也就是说,使用后台队列和信号量可能不是最好的方法。充其量,它会在序列的大部分持续时间内让两个线程处于阻塞状态(一个线程定期唤醒以排队下一个删除操作,另一个线程等待 finishAllSignal。)有更好的方法。哪种方法最好实际上取决于您要达到的确切效果。

例如,在通常情况下,您当前的方法看起来会允许(至少)在启动删除操作/动画之间旋转一次主运行循环(解释见下文)。我的第一个想法是使用计时器开始每次移除比前一帧晚一两帧。它看起来像这样:

NSArray* indexPathsAry = @[ ... ];
const NSTimeInterval intervalInSeconds = 1.0 / 30.0;

__block NSUInteger i = 0;

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW,  (uint64_t)(intervalInSeconds * NSEC_PER_SEC), 0);
dispatch_source_set_event_handler(timer, ^{
    NSIndexPath *indexPath = indexPathsAry[i++];
    id item = items[indexPath.row];
    [items removeObject:item];

    [tableView beginUpdates];
    [tableView deleteRowsAtIndexPaths:indexPath withAnimation:...];
    [tableView endUpdates];

    if (i >= indexPathsAry.count)
    {
        dispatch_source_cancel(timer);
    }
});

dispatch_source_set_cancel_handler(timer, ^{
    // whatever you want to happen when all the removes are done.
})
dispatch_resume(timer);

如果你真的需要保证运行循环的完全/只有一次旋转(你的信号量方法 FWIW 不能保证)那么 NSOperation 可能是最简单的方法(因为主线程 NSOperationQueue 每次运行循环旋转仅服务一个操作。)如果您需要“完成”行为(即 finishAllSignal),您可以使用 NSOperation 依赖项来实现.它可能看起来像这样:

NSOperation* finishOp = [NSBlockOperation blockOperationWithBlock:^{
    // ... whatever you want to have happen when all deletes have been processed.
}];

for (NSIndexPath* toDelete in indexPathsAry)
{
    NSOperation* op = [NSBlockOperation blockOperationWithBlock:^{
        id item = items[toDelete.row];
        [items removeObject: item];

        [tableView beginUpdates];
        [tableView deleteRowsAtIndexPaths: @[toDelete] withAnimation:...];
        [tableView endUpdates];
    }];
    [finishOp addDependency: op];
    [[NSOperationQueue mainQueue] addOperation: op];
}
[[NSOperationQueue mainQueue] addOperation: finishOp];

这两种方法都不应遭受泄漏。此外,这两种方法中的任何一种都比让两个线程在信号量上等待多长时间要好。

更多详情:

以下是对我认为发布的方法将要做的事情的解释:

  • 后台线程为主线程排入一个 block ,然后进入休眠等待信号量。
  • 主线程runloop被libdispatch唤醒并最终执行block。
  • 该 block 删除项目,并在 TableView 上启动删除动画以删除该行。
  • 然后它向信号量发送信号,然后就完成了。主线程当前正在执行(即未休眠)并将继续处理主调度队列中的 block ,直到没有更多 block 为止,并最终继续运行循环,最终进入休眠状态等待下一个事件/唤醒.
  • 由于信号量信号,后台线程在稍后唤醒。此时,重复这些步骤。

需要注意的是,主运行循环和后台线程之间没有强耦合。如果在你的 block 之后有另一个不相关的 block 在主队列中排队,那么后台队列可能会在不相关的 block 完成之前将下一个删除 block 放到主队列中。如果发生这种情况,那两个删除 block 可能会在同一个运行循环过程中执行,并且两个相应的项目似乎会同时被删除。在常见情况下,这可能不会发生,但我的观点是信号量方法不能保证每次运行循环一次删除行为,但我相信 NSOperation 方法。

老实说,计时器方法可能是最好的,因为它在语义上捕获了您在这里想要的内容,即一行一行地删除行,并且它们之间的延迟最小。

关于ios - 使用 dispatch_semaphore 时泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20149063/

相关文章:

ios - AVAssetWriterInput 追加样本缓冲区 : Cannot append sample buffer: Must start a session (using -AVAssetWriter startSessionAtSourceTime:) first'

swift - 如何测量 Swift 中 DispatchQueue 并发异步中的代码块执行时间?

ios - 如何修复主 Storyboard上不正确的(小) View

ios - 使用 NSURLSession 并返回特殊字符

java - BufferedImage 的像素访问 - 内存泄漏?

在 Red Hat 上运行时 Java 内存泄漏,但在 Mac OS X 上没有内存泄漏

objective-c - 仅在小于1的情况下才将 float 呈现给2个空格

ios - 无法使用我的字符串填充 PickerView

c++ - Stack.pop 内存管理