0

I'm a bit confused about ARC behaviour when setting variable that is an input pointer, and is expected to remain valid outside function scope.

considering the following example that uses openDirectory framework.


@interface bbb
-(bool)doSomethingWithADRecord:
-(void)obtainADRecord(NSString*)user
-(NSString*)getADrecord:(ODAttributeType)attr fromRecord:(ODRecord*)record;
@end 

@interface bbb {

    ODRecord *_myRecord;
}
@end 


@implementation bbb
-(void)doSomethingWithADRecord:
{
     // here we access _myRecord and expect it to be valid.
}

-(bool)obtainADRecord:(NSString*)user
{
    ...
    // here I call the method that will set the member _myRecord from type ODRecord*
    // whose scope related to the lifespan of the containing class (bbb)
    [self getADrecord:attr toRecord:_myRecord];

}

// the following function should set the variable record to be used by the caller. 
-(NSString*)getADrecord:(ODAttributeType)attr fromRecord:(ODRecord*)record {
    ...
    // here a set an ODQuery object.
    ODQuery *query = [[ODQuery alloc] initWithNode ... 

    // queryResults is an array of items from type ODQuery* 
    NSArray* queryResults = [query resultsAllowingPartial:NO error:&err];

    for(ODRecord *item in queryResults) {
        if (/*some logic*/)
        { 
            //option 1: just regular set operator, expecting the ARC will do the retain itself 
            record = item;

            //option 2: explicits take a reference on that item.  
            record = [[item retain] autorelease];
            return @"found item";
        }
    }
}
@end

To Clarify my question, I seek to know which one of the 2 options I stated above is the correct one , in terms of passing the reference to record and eventually to _myRecord, so it will store the correct value even after the temporal list of queryResults will be cleaned.

Notice that in both options I simply setting the pointer value without initiate new object from type ODquery and copying the data to this new object.

thanks !

Irad K
  • 867
  • 6
  • 20
  • 2
    If saying `[[item retain] autorelease];` compiles, you are not using ARC at all. So what’s the question even about? – matt Dec 14 '19 at 14:58
  • @matt, I'd ilke to know whether simply doing `record = item` will be enough for the data pointed by this object to last beyond the scope of the function `getADrecord`. Because if using c/c++ without smart pointers, I might end up in dangling pointer in this case. I hope I cleared my point now – Irad K Dec 14 '19 at 17:14
  • I repeat what I said. If `retain` compiles, ARC is off for this entire file. That means all your code is wrong unless you manage memory yourself throughout. – matt Dec 14 '19 at 19:18
  • @Matt, ok thanks for clarifying that. So I'll abandon this option as I want to avoid dealing with memory management myself. So this leaves me with option No. 1 of `record = item`, but will it pass the reference to `_myRecord` when leaving the function ? – Irad K Dec 14 '19 at 20:40
  • It doesn't matter! If you _are_ using ARC, don't even think about what memory management does. Just write the code and trust ARC to do the right thing. That is what ARC means: it is Automatic. That is why I suggest that the whole question is meaningless. – matt Dec 14 '19 at 20:47
  • As for your question, the only way `_myRecord` would ever get is if you said `_myRecord = ...` or `[setMyRecord:...]`. But I don't see you ever saying that. Saying `record=` has no effect on `_myRecord`. But that has nothing to do with ARC or memory management! It seems like what you don't understand is what a _pointer_ is, which is a much simpler, more basic matter. You can _mutate_ `myRecord` by way of another reference but you cannot _set_ it. – matt Dec 14 '19 at 20:49
  • @matt - or by way of an out parameter and that seems to be what the OP intended as the method directly returns a string. (Answer added.) – CRD Dec 14 '19 at 20:54
  • @CRD very good point; I think I've made my point too, which was only that the invocation of ARC was a red herring all along. :) – matt Dec 14 '19 at 21:00

2 Answers2

4

I'd like to know whether simply doing record = item will be enough for the data pointed by this object to last beyond the scope of the function getADrecord

You are misunderstanding how parameters work; a parameter, such as record, is essentially a local variable which is initialised to the value passed in the call.

Therefore any assignment of an object reference to record will have zero effect on the lifetime of the referenced object outside of the scope of getADrecord as record is local to the function.

To return a value of type T via a parameter the type of the parameter must be of type "pointer to a variable of type T". An example with a simple value type:

- (void) add:(int)value           // an int value
          to:(int *)ptrToVariable // a pointer to an int variable
{
   // note the need to indirect (`*`) through pointer stored in
   // `ptrToVariable` to access the pointed at variable
   *ptrToVariable = *ptrToVariable + value; 
}

int x = 31;
[self add:11 to:&x]; // &x creates a pointer to the variable x
// x = 42 after call

Now you don't want to return a simple value type but a value which is a reference to an object and you wish ARC to manage the lifetime correctly. This is a little more complicated.

Under ARC a variable which holds a reference to an object has both a type and an ownership attribute; this attribute informs ARC how to handle storing references in the variable. The common ownership attributes are __strong and __weak, without an explicit attribute __strong is assumed. So your instance variable declaration is shorthand for:

ODRecord __strong *_myRecord;

This declaration means that for any reference to an ODRecord stored into _myRecord ARC will keep the referenced ODRecord alive at least as long as _myRecord exists and the reference is not overwritten by a different reference or nil. It is "at least as long" as the same reference could be stored elsewhere and these will also effect the lifetime.

Almost there! To return a reference to an ODRecord via a parameter the type of the parameter must be "pointer to a variable of type strong reference to ODRecord, i.e.:

- (NSString *)getADrecord:(ODAttributeType)attr
               fromRecord:(ODRecord * __strong *)record

now an assignment such as:

*record = item;

will result in an assignment to the pointed-at variable and as that variable is of type ODRecord __strong * ARC will ensure the referenced ODRecord will live at least as long as a reference to it is stored in the pointed-at variable.

Your call to this method must pass a pointer to your variable:

[self getADrecord:attr toRecord:&_myRecord];

Notes:

Important:

As pointed out by @matt in the comments your code contains retain and autorelease calls which are forbidden in ARC and therefore if your code is compiling you DO NOT have ARC enabled. For new projects ARC will be enabled, for existing projects you may need to enable it your project's Build Settings, the setting is called "Objective-C Automatic Reference Counting".

CRD
  • 52,522
  • 5
  • 70
  • 86
0

A call to "autorelease" means the object has an additional retain count that will go away when you leave the current autorelease scope, which is typically when the current event is finished.

record = item is obviously not enough, because record's retain count goes away when records leaves scope, that is when the function returns.

But what you do - calling autorelease for each item makes sure that all the items remain allocated for a while, not just "record".

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • thanks, I've changed my function to return after it found a single record. this was my intention - that only one record shell be retained. – Irad K Dec 14 '19 at 17:46
  • I just wanted to make sure if there's an alternative for option 2 (`record = [[item retain] autorelease]`) by using ARC ... I thought ARC is activated automatically unless specifically said otherwise (using autoreleasepool), isn't it the case ? – Irad K Dec 14 '19 at 17:53