1

我有一个向串行 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;
}
4

1 回答 1

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);拨打电话,您会看到“某物仍然指向 obj?” log 发生在最后一个doMethod& dealloclog 语句之前。我怀疑这只是线程和 NSLog 缓冲,这就是我放入的原因,sleep(1);而且果然行为改变了,正如我预期的那样。

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

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

这是有道理的,也可能会影响各种释放操作的时间安排。

于 2013-03-11T01:21:16.663 回答