使用ARC,NSOperationQueue和dispatch_source_t(计时器)保留循环

时间:2012-10-10 08:02:58

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

我有一个方法可以为串行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;
}

1 个答案:

答案 0 :(得分:3)

这里实际上没有保留周期。问题是你正在做的事情或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);电话,您会看到&#34;某些内容仍指向obj?&#34;日志发生在最后doMethod&amp; dealloc日志语句。我怀疑这只是线程和NSLog缓冲,这就是为什么我放入sleep(1);并确定行为改变了我预期的。

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

  

提交到队列的任何挂起块都保存对该队列的引用,因此在所有挂起块完成之前不会释放队列。

这有意义,也可能影响各种释放行动的时间。