ARC and releasing object created in method

大憨熊 提交于 2019-12-10 11:14:58

问题


I have stumbled upon an issue for which I can't find answer elsewhere. When I am calling a method which returns pointer to an object which is later used and at the end set to nil, it is still allocated in memory (according to Instruments). I'm using XCode 4.6.3 and iOS 6.1. ARC is turned on.

Here is sample code:

ClassA.h

@interface ClassA : NSObject
    -(void)runSomething;
@end

ClassA.m

#import "ClassA.h"
#import "ClassB.h"

@implementation ClassA

-(void)runSomething {
    int counter = 0;
    while (true) {
        ClassB *instance = [self makeClassBWithNumber:counter];
        NSLog(@"%d", [instance getNumber]);
        [NSThread sleepForTimeInterval:0.01];
        instance = nil;
        counter++;
    }
}

-(ClassB*) makeClassBWithNumber:(int)number {
    return [[ClassB alloc] initWithNumber:number];
}

@end

ClassB.h

@interface ClassB : NSObject
@property int number;
    -(id)initWithNumber:(int)number;
    -(int)getNumber;
@end

ClassB.m

#import "ClassB.h"

@implementation ClassB

-(id)initWithNumber:(int)number {
    self = [super init];
    if(self) {
        _number = number;
    }
    return self;
}

-(int)getNumber {
    return [self number];
}

@end

ClassB is created in view controller and method runSomething is called. This sample code produces that created object (ClassB) is never released from memory. If I change code from

ClassB *instance = [self makeClassBWithNumber:counter];

to

ClassB *instance = [[ClassB alloc] initWithNumber:counter];

created object is properly released in each of loop cycle. What is the reason for such behaviour? I found some old answers here on stackoverflow that makeClassBWithNumber should return result invoking autorelease return [result autorelease], but this can't be done if ARC is enabled.


回答1:


The difference is that +alloc returns an object with a +1 retain, which ARC will balance with a release at the end of its scope, and so immediately deallocate. +make… returns an object with a +1 retain and a matching autorelease. The autorelease pool will send a release message when it drains. Since you stay in loop "while true," the autorelease pool never drains and you accumulate memory.

The solution is to give your loop an autorelease pool:

while (true) {
    @autoreleasepool { // <== Add an autorelease block here.
      ClassB *instance = [self makeClassBWithNumber:counter];
      //NSLog(@"%d", [instance getNumber]);
      NSLog(@"%d", [instance number]); // Fix naming; do not prefix accessors with `get`
      [NSThread sleepForTimeInterval:0.01];
      // instance = nil; // Does nothing in this loop.
      counter++;
    }
}

This will cause the pool to drain on every iteration. In any case the instance=nil is unnecessary.


EDIT: Do read MartinR's answer. It gives some more details on the implementation details, and particularly why this may behave differently depending on the optimization level, and whether the called method is in the same compile unit (.m file) as the calling method. That is only an optimization detail; you still need to put this @autoreleasepool in the loop for correctness.




回答2:


makeClassBWithNumber returns an autoreleased object, even with ARC. (More precisely, it can return an autoreleased object, depending on the optimization.)

The difference to manual reference counting is that the ARC compiler inserts the autorelease call where required, not you.

From the Clang/ARC documentation:

3.2.3 Unretained return values

A method or function which returns a retainable object type but does not return a retained value must ensure that the object is still valid across the return boundary.

When returning from such a function or method, ARC retains the value at the point of evaluation of the return statement, then leaves all local scopes, and then balances out the retain while ensuring that the value lives across the call boundary. In the worst case, this may involve an autorelease, but callers must not assume that the value is actually in the autorelease pool.

makeClassBWithNumber is not a alloc, copy, init, mutableCopy, or new method and therefore returns an unretained return value.




回答3:


The operative word in your question is "OLD". Old answers are no longer relevant.

This is what ARC is for.

You no longer need to worry about any memory management.

If ARC tells you not to do it... don't.

In this case, you don't need autorelease.




回答4:


As others have said the difference you are seeing is down to whether a method returns and object the caller owns or an object the caller does not own.

In the former category are methods in the alloc, init, new, copy & mutableCopy categories. These all return an object owned by the caller and ARC will ensure it is released.

In the latter category are all the methods not in the first! These return an object which is not owned by the caller, ARC will ensure that this object is retained if needed and released if it was retained. Return values in this category may be in the auto-release pool, which means they will live at least as long as they are in the pool (maybe longer if they've been retained by ARC) and the standard pool is emptied at the end of each run loop cycle. This means that in loops which generate a lot of entries into the auto-release pool that a lot of no longer needed objects can accumulate in the pool - this is what you are seeing.

Under MRC the solution was to introduce a local auto-release pool, within such loops, to avoid accumulation of no longer needed objects. However under ARC this is probably not the best choice.

Under ARC the better solution is probably to follow the naming conventions, and in this case you want to use the new pattern - new is the standard pattern for alloc + init in one method. So rename your makeClassBWithNumber as newClassBWithNumber:

// *create* a new ClassB object
- (ClassB *) newClassBWithNumber:(int)number
{
   return [[ClassB alloc] initWithNumber:number];
}

This indicates the method returns an object the caller owns, it is a "creation" method, and ARC will handle the rest without no longer objects accumulating.

(Adding a newWithNumber method to ClassB itself is often a good idea under ARC.)



来源:https://stackoverflow.com/questions/18727445/arc-and-releasing-object-created-in-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!