【问题标题】:Retain cycle with ARC, NSOperationQueue and dispatch_source_t (timer)使用 ARC、NSOperationQueue 和 dispatch_source_t(计时器)保持循环
【发布时间】:2012-10-10 08:02:57
【问题描述】:

我有一个向串行 NSOperationQueue 添加操作的方法。由于我想定期调用该方法,所以我使用调度源计时器。

但是,也可以调用此方法来响应用户操作。当发生这种情况时(例如,由于计时器调用方法之前的片刻)我延长计时器的触发日期。

问题是我写的代码有一个保留循环,我不明白在哪里。

这是演示问题的简化示例(不要忘记将部署 SDK 设置为 10.7):

#import <Foundation/Foundation.h>


@interface MyObject : NSObject

@end


@implementation MyObject
{
    NSOperationQueue *_queue;
    dispatch_source_t _timer;
}

- (id)init
{
    self = [super init];

    if (self)
    {
        _queue = [[NSOperationQueue alloc] init];
        [_queue setMaxConcurrentOperationCount:1];
    }

    return self;
}

- (void)dealloc
{
    NSLog(@"dealloc");
    [_queue cancelAllOperations];
    dispatch_source_cancel(_timer);
    dispatch_release(_timer);
}

- (void)scheduleTimer
{
    if (_timer)
    {
        dispatch_source_cancel(_timer);
        dispatch_release(_timer);
    }

    _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
                                    0,
                                    0,
                                    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));

    if (_timer)
    {
        __weak MyObject *selfBlock = self;
        dispatch_source_set_event_handler(_timer, ^{
            dispatch_source_cancel(_timer);
            [selfBlock doMethod];
        });
        dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0);
        dispatch_resume(_timer);
    }
}

- (void)doMethod
{
    NSLog(@"doMethod");

    __weak MyObject *selfBlock = self;

    [_queue cancelAllOperations];
    [_queue addOperationWithBlock:^{
        [selfBlock scheduleTimer];
    }];
}

@end


int main(int argc, const char * argv[])
{
    @autoreleasepool {

        MyObject *obj = [MyObject new];
        [obj doMethod];

        sleep(10);

        obj = nil;
        NSLog(@"something still points to obj");

        sleep(10);
    }
    return 0;
}

【问题讨论】:

  • 这个问题是用ARC标记的,但是你怎么能在那里调用dispatch_release呢?
  • @Eiko Dispatch 对象不需要 dispatch_release 如果最小部署 SDK 是 10.8 所以你仍然需要在 10.7 下手动释放它们
  • 您在哪里发现了保留周期?我在 Instruments 中运行了您的代码,部署目标为 10.7。它没有检测到周期。静态分析器没有显示任何保留周期。
  • @NSBum MyObject 实例永远不会被释放(除非应用程序当然被终止)。我希望它会在obj = nil 之后很快被释放。不幸的是,有些东西保留了对对象的引用

标签: objective-c automatic-ref-counting nsoperationqueue retain-cycle


【解决方案1】:

这里实际上没有保留周期。问题是您正在做的事情或dispatch_release() 的内部结构(我没有花时间弄清楚)正在发送autorelease 消息而不是release 消息,所以最后的release 没有直到您的 autorelease 块关闭后才会发生。如果您将您的 main 例程更改为以下内容,它将显示您所期望的工作:

int main(int argc, const char * argv[])
{
    @autoreleasepool {

        MyObject *obj = nil;

        @autoreleasepool {
            obj = [MyObject new];
            [obj doMethod];

            sleep(10);

            NSLog(@"set to nil");
            obj = nil;
        }

        sleep(1);  // need this to give the background thread a chance to log

        NSLog(@"something still points to obj?");

        sleep(10);

        NSLog(@"done sleeping");
    }

    return 0;
}

我更改了您的其他代码以添加一些日志记录并清理了一些内容,但注释掉了仅用于挑剔清洁编码的更改:),它仍然可以正常工作。

#import <Foundation/Foundation.h>


@interface MyObject : NSObject

@end


@implementation MyObject
{
    NSOperationQueue *_queue;
    dispatch_source_t _timer;
}

- (id)init
{
    self = [super init];

    if (self)
    {
        _queue = [[NSOperationQueue alloc] init];
        [_queue setMaxConcurrentOperationCount:1];
    }

    return self;
}

- (void)dealloc
{
    NSLog(@"dealloc");
    [_queue cancelAllOperations];

    if ( _timer )
    {
        dispatch_source_cancel(_timer);
        dispatch_release(_timer);
        // _timer = nil;
    }
}

- (void)scheduleTimer
{
    NSLog(@"Schedule timer");

    if (_timer)
    {
        dispatch_source_cancel(_timer);
        dispatch_release(_timer);
        //        _timer = nil;
    }

    _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
                                0,
                                0,
                                 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));

    if (_timer)
    {
        __weak MyObject *selfBlock = self;
        dispatch_source_set_event_handler(_timer, ^{
            dispatch_source_cancel(_timer);
            [selfBlock doMethod];
        });
        dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0);
        dispatch_resume(_timer);
    }
}

- (void)doMethod
{
    NSLog(@"doMethod");

    __weak MyObject *selfBlock = self;

    [_queue cancelAllOperations];
    [_queue addOperationWithBlock:^{
        [selfBlock scheduleTimer];
    }];
}

@end

这是我得到的输出:

2013-03-10 18:15:33.829 testtimer[35328:403] doMethod
2013-03-10 18:15:33.832 testtimer[35328:1e03] Schedule timer
2013-03-10 18:15:34.833 testtimer[35328:1e03] doMethod
2013-03-10 18:15:34.835 testtimer[35328:2203] Schedule timer
2013-03-10 18:15:35.837 testtimer[35328:1e03] doMethod
2013-03-10 18:15:35.839 testtimer[35328:1d03] Schedule timer
2013-03-10 18:15:36.839 testtimer[35328:1d03] doMethod
2013-03-10 18:15:36.841 testtimer[35328:1e03] Schedule timer
2013-03-10 18:15:37.842 testtimer[35328:1e03] doMethod
2013-03-10 18:15:37.844 testtimer[35328:1e03] Schedule timer
2013-03-10 18:15:38.846 testtimer[35328:1e03] doMethod
2013-03-10 18:15:38.848 testtimer[35328:1e03] Schedule timer
2013-03-10 18:15:39.849 testtimer[35328:1e03] doMethod
2013-03-10 18:15:39.851 testtimer[35328:1d03] Schedule timer
2013-03-10 18:15:40.851 testtimer[35328:1d03] doMethod
2013-03-10 18:15:40.853 testtimer[35328:2203] Schedule timer
2013-03-10 18:15:41.854 testtimer[35328:2203] doMethod
2013-03-10 18:15:41.856 testtimer[35328:1e03] Schedule timer
2013-03-10 18:15:42.857 testtimer[35328:1d03] doMethod
2013-03-10 18:15:42.859 testtimer[35328:1d03] Schedule timer
2013-03-10 18:15:43.831 testtimer[35328:403] set to nil
2013-03-10 18:15:43.861 testtimer[35328:1d03] doMethod
2013-03-10 18:15:43.861 testtimer[35328:1d03] dealloc
2013-03-10 18:15:44.833 testtimer[35328:403] something still points to obj?

如果您拨打sleep(1); 电话,您会看到“某些东西仍然指向obj?”日志发生在最后一个 doMethoddealloc 日志语句之前。我怀疑这只是线程和 NSLog 缓冲,这就是我放入 sleep(1); 的原因,果然行为改变了,正如我预期的那样。

同样来自 Xcode 文档查看器中dispatch_queue_create 的文档,它说:

任何提交到队列的待处理块都持有对该队列的引用,因此在所有待处理块完成之前不会释放队列。

这是有道理的,也可能会影响各种解除分配操作的时间。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-03-13
    • 1970-01-01
    • 1970-01-01
    • 2012-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多