Ios 使用dispatch_信号量时发生泄漏

Ios 使用dispatch_信号量时发生泄漏,ios,memory-leaks,grand-central-dispatch,Ios,Memory Leaks,Grand Central Dispatch,我想在不同的队列中执行一些操作,所以我使用信号量。但当我用仪器检查时,会出现泄漏。帧是dispatch\u semaphore\u create。我使用ARC,以前检查时没有泄漏。代码如下所示: dispatch_async(queue,^{ dispatch_semaphore_t signal = dispatch_semaphore_create(1); for (int i=0;i<100;i++) { dispatch_sem

我想在不同的队列中执行一些操作,所以我使用信号量。但当我用仪器检查时,会出现泄漏。帧是dispatch\u semaphore\u 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);
            }); 
     }
});
附言。
对不起,nsindepath*indepath=indexPathsAry[i];需要更改为NSIndexPath*indexPath=IndeXPathry[0];代码将逐个正确删除单元格,而不是一起删除。它说在线路调度信号量=调度信号量创建(1)中存在泄漏;但当我在一个小的演示中测试这段代码时,它工作得很好,所以我不知道为什么。起初我用dispatch_sync代替信号量,但有时它不能同步工作,这让我很困惑。是因为我的项目中有太多的队列吗?

从您发布的代码来看,您泄漏的原因并不明显。也就是说,使用后台队列和信号量可能不是最好的方法。充其量,它会使两个线程在序列的大部分时间内处于阻塞状态(一个定期唤醒以将下一个删除操作排入队列,另一个等待
完成lsignal
),有更好的方法。哪种方法最好实际上取决于你所追求的确切效果

例如,在常见情况下,您当前的方法看起来将允许(至少)在开始删除操作/动画之间旋转主运行循环一次(请参见下面的说明)。我的第一个想法是使用计时器在每次删除之前的一帧或两帧之后开始。它看起来像这样:

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);
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];
如果确实需要保证运行循环的精确/仅一次旋转(信号量方法FWIW不能保证),那么
NSOperation
可能是最简单的方法(因为主线程
NSOperationQueue
只为每个运行循环旋转一次操作提供服务)(即
finishAllSignal
)您可以使用
NSOperation
依赖项来实现这一点。它可能如下所示:

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);
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];
这两种方法都不应该出现泄漏。此外,这两种方法都比让两个线程等待信号量多长时间要好

更多详情: 以下是我认为发布的方法将要做的一个解释:

  • 后台线程为主线程排队一个块,然后进入睡眠状态,等待信号量
  • libdispatch唤醒主线程runloop并最终执行该块
  • 该块将删除该项,并在表视图上启动“删除”动画以删除该行
  • 主线程当前正在执行(即不休眠),将继续处理来自主调度队列的块,直到不再有块为止,并最终继续运行循环,最终进入休眠状态,等待下一个事件/唤醒
  • 由于信号量信号,后台线程稍后会被唤醒
需要注意的一点是,主运行循环和后台线程之间没有强耦合。如果在您的块之后,有另一个不相关的块在主队列上排队,那么后台队列可能会在不相关的块完成之前将下一个删除块放到主队列上。如果发生这种情况,则这两个块将被删除块可以在同一个runloop过程中执行,而两个对应的项似乎会同时被删除。在一般情况下,这可能不会发生,但我的观点是,每个runloop过程一次删除的行为并不是由信号量方法保证的,但我相信将由
NSOperation
方法保证h


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

我不知道这怎么可能起作用,因为这段代码给我留下了一系列问题:

  • 我推断您的意图是为每个被删除的行分别设置动画,但这样做行不通,因为
    deleteRowsAtIndexPaths
    仅启动动画。此代码将使它们看起来几乎同时被删除


  • 我看不出这是如何工作的,因为您正在改变结构(数组和表视图)当您迭代该结构时。例如,当
    i==0
    时,删除第一项后一切正常,但当
    i==1
    时,数组中的第二项现在是数组中的第一项,因此当您引用
    索引路径[i]
    ,您现在正在抓取原来是第三项的内容,因为原来是第二项的内容现在位于
    indexpath[0]

  • deleteRowsAtIndexPaths
    方法采用索引路径数组,而不是单个索引路径

  • 这里完全不需要使用信号量。更符合逻辑的模式是:

    dispatch_queue_t queue = dispatch_queue_create("com.company.app.tableupdate", 0);
    for (int i = 0; i < count; i++)
    {
        dispatch_async(queue, ^{
            dispatch_sync(dispatch_get_main_queue(), ^{
                // do some stuff to the table
            });
            [NSThread sleepForTimeInterval:0.1]; // if you want a little delay before the next one
        });
    }
    

    完成所有操作后,
    dispatch\u release(signal);
    怎么样?dispatch对象是否表现为ObjC对象(并因此参与ARC)取决于项目的部署目标版本。IIRC,您需要iOS6或更高版本和/或OSX 10.8或更高版本才能获得该行为。否则,
    dispatch\u release
    。如果这是在串行队列上完成的,则此代码将使该队列死锁,因为您正在发送信号并等待同一队列。结果会导致泄漏。此外,您说你正在处理不同队列之间的通信,所以也许你可以更新你的示例来说明你在做什么