Manual object lifetime with ARC

2019-01-26 22:26发布

问题:

Examine the following code, and assume it was compiled under ARC:

- (void)foo {
    NSOperationQueue *oq = [[NSOperationQueue alloc] init];
    [oq addOperationWithBlock:^{
        // Pretend that we have a long-running operation here.
    }];
}

Although the operation queue is declared as a local variable, its lifetime continues beyond the scope of the method as long as it has running operations.

How is this achieved?

UPDATE:

I appreciate Rob Mayoff's well-thought-out comments, but I think I did not ask my question correctly. I am not asking a specific question about NSOperationQueue, but rather a general question about object lifetime in ARC. Specifically, my question is this:

How, under ARC, can an object participate in the management of its own lifetime?

I've been a programmer for a very long time, and I'm well aware of the pitfalls of such a thing. I am not looking to be lectured as to whether this is a good or bad idea. I think in general it is a bad one. Rather, my question is academic: Whether it's a good or bad idea or not, how would one do this in ARC and what is the specific syntax to do so?

回答1:

As a general case you can keep a reference to yourself. E.g.:

@implementation MasterOfMyOwnDestiny
{
   MasterOfMyOwnDestiny *alsoMe;
}

- (void) lifeIsGood
{
    alsoMe = self;
}

- (void) woeIsMe
{
    alsoMe = nil;
}

...

@end


回答2:

Here are a few possibilities:

  1. The NSOperationQueue retains itself until it is empty, then releases itself.

  2. The NSOperationQueue causes some other object to retain it. For example, since NSOperationQueue uses GCD, perhaps addOperationWithBlock: looks something like this:

    - (void)addOperationWithBlock:(void (^)(void))block {
        void (^wrapperBlock)(void) = ^{
            block();
            [self executeNextBlock];
        };
        if (self.isCurrentlyExecuting) {
            [self.queuedBlocks addObject:wrapperBlock];
        } else {
            self.isCurrentlyExecuting = YES;
            dispatch_async(self.dispatchQueue, wrapperBlock);
        }
    }
    

    In that code, the wrapperBlock contains a strong reference to the NSOperationQueue, so (assuming ARC), it retains the NSOperationQueue. (The real addOperationWithBlock: is more complex than this, because it is thread-safe and supports executing multiple blocks concurrently.)

  3. The NSOperationQueue doesn't live past the scope of your foo method. Maybe by the time addOperationWithBlock: returns, your long-running block has already been submitted to a GCD queue. Since you don't keep a strong reference to oq, there is no reason why oq shouldn't be deallocated.



回答3:

In the example code give, under ARC, the NSOperationQueue, being local to the enclosing lexical scope of the block, is captured is captured by the block. Basically, the block saves the value of the pointer so it can be accessed from within the block later. This actually happens regardless of whether you're using ARC or not; the difference is that under ARC, object variables are automatically retained and released as the block is copied and released.

The section "Object and Block Variables" in the Blocks Programming Topics guide is a good reference for this stuff.



回答4:

The simplest thing I can think of would be to have a global NSMutableArray (or set, or whatever) that the object adds itself to and removes itself from. Another idea would be to put the (as you've already admitted) oddly-memory-managed code in a category in a non-ARC file and just use -retain and -release directly.