Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
170 views
in Technique[技术] by (71.8m points)

objective c - Handling Pointer-to-Pointer Ownership Issues in ARC

Suppose Object A has a property:

@property (nonatomic, strong) Foo * bar;

Synthesized in the implementation as:

@synthesize bar = _bar;

Object B manipulates a Foo **, as in this example call from Object A:

Foo * temp = self.bar;
[objB doSomething:&temp];
self.bar = temp;
  • Can this, or something similar, be done legitimately?
  • What is the correct declaration for the doSomething: method?

Furthermore, suppose Object B may be deallocated before I have a chance to set the bar property (and thus take on ownership of the instance pointed to by temp) - How would I tell ARC to hand off an owning reference? In other words, if I wanted the following example snippet to work, how would I need to handle the ARC issues?

Foo * temp = self.bar;    // Give it a reference to some current value
[objB doSomething:&temp]; // Let it modify the reference
self.bar = nil;           // Basically release whatever we have
_bar = temp;              // Since we're getting back an owning reference, bypass setter
  • What aren't I thinking of?

EDIT

Based on @KevinBallard 's answer, I just want to confirm my understanding. Is this correct?

Object A:

@implementation ObjectA

@synthesize bar = _bar;

- (void)someMethod
{
    ObjectB * objB = [[ObjectB alloc] initWithFoo:&_bar];
    // objB handed off somewhere and eventually it's "doSomething" method is called.
}

@end

Object B:

@implementation ObjectB
{
    Foo * __autoreleasing * _temp;
}

- (id)initWithFoo:(Foo * __autoreleasing *)temp
{
    id self = [super init];
    if (self)
    {
        _temp = temp;
    }
    return self;
}

- (void)doSomething
{
    ...
    *_temp = [[Foo alloc] init]; 
    ...
}

@end

This creates a compile-time error: passing address of non-local object to __autoreleasing parameter for write-back

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

ARC needs to know the ownership of an object reference so it can determine when to release it etc. For any variable (local, instance or global) ARC has rules for determining the ownership; either by inference or by an explicit attribute. This equates to the pre-ARC need for the programmer to track ownership.

But what happens if you have a reference to a variable? You could not (pre-ARC) yourself write code which accepted a reference to a variable and which would always work correctly regardless of the ownership of that variable - as you could not know whether you needed to release etc. I.e. you can not construct code which works for variable (in the sense of changing!) unknown ownership.

ARC faces the same problem and its solution is to infer, or accept an explicit attribute specifying, the ownership of referenced variable and then require the caller to arrange for a reference to a variable of appropriate ownership to be passed. This latter bit can require the use of hidden temporary variables. This is referred to as "least bad solution" in the specification and is termed "pass-by-writeback".

The first part of the question:

Foo * temp = self.bar;
[objB doSomething:&temp];
self.bar = temp;
  • Can this, or something similar, be done legitimately?

Yes, the code is fine by ARC. temp is inferred to be strong and some behind the scenes stuff happens to pass it by reference to doSomething:.

  • What is the correct declaration for the doSomething: method?
- (void) doSomething:(Foo **)byRefFoo

ARC infers byRefFoo to be of type Foo * __autoreleasing * - a reference to an autoreleasing reference. This is what is required by "pass-by-writeback".

This code is only valid because temp is a local. It would be incorrect to do this with an instance variable (as you found out in your EDIT). It is also only valid assuming the parameter is being used in standard "out" mode and any updated value has been assign when doSomething: returns. Both of these are because the way pass-by-writeback works as part of that "least bad solution"...

Summary: when using local variables they can be passed by reference for use in the standard "out" pattern with ARC inferring any required attributes etc.

Under The Hood

Instead of the Foo of the question we'll use a type Breadcrumbs; this is essentially a wrapped NSString which tracks every init, retain, release, autorelease and dealloc (well almost as you'll see below) so we can see what is going on. How Breadcrumbs is written is not material.

Now consider the following class:

@implementation ByRef
{
   Breadcrumbs *instance;                                // __strong inferred
}

A method to change a value passed by reference:

- (void) indirect:(Breadcrumbs **)byRef                  // __autoreleasing inferred
{
   *byRef = [Breadcrumbs newWith:@"banana"];
}

A simple wrapper for indirect: so we can see what it is passed and when it returns:

- (void) indirectWrapper:(Breadcrumbs **)byRef           // __autoreleasing inferred
{
   NSLog(@"indirect: passed reference %p, contains %p - %@, owners %lu", byRef, *byRef, *byRef, [*byRef ownerCount]);
   [self indirect:byRef];
   NSLog(@"indirect: returned");
}

And a method to demonstrate indirect: called on a local variable (called imaginatively local):

- (void) demo1
{
   NSLog(@"Strong local passed by autoreleasing reference");
   Breadcrumbs *local;                                   // __strong inferred
   local = [Breadcrumbs newWith:@"apple"];
   NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]);
   [self indirectWrapper:&local];
   NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]);
}

@end

Now some code to exercise demo1 localizing the autorelease pool so we can see what is allocated, released and when:

ByRef *test = [ByRef new];

NSLog(@"Start demo1");
@autoreleasepool
{
   [test demo1];
   NSLog(@"Flush demo1");
}
NSLog(@"End demo1");

Executing the above produces the following on the console:

ark[2041:707] Start demo1
ark[2041:707] Strong local passed by autoreleasing reference
ark[2041:707] >>> 0x100176f30: init
ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1
ark[2041:707] indirect: passed reference 0x7fff5fbfedb8, contains 0x100176f30 - apple, owners 1
ark[2041:707] >>> 0x100427d10: init
ark[2041:707] >>> 0x100427d10: autorelease
ark[2041:707] indirect: returned
ark[2041:707] >>> 0x100427d10: retain
ark[2041:707] >>> 0x100176f30: release
ark[2041:707] >>> 0x100176f30: dealloc
ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100427d10 - banana, owners 2
ark[2041:707] >>> 0x100427d10: release
ark[2041:707] Flush demo1
ark[2041:707] >>> 0x100427d10: release
ark[2041:707] >>> 0x100427d10: dealloc
ark[2041:707] End demo1

[The ">>>" lines come from Breadcrumbs.] Just follow the addresses of the objects (0x100...) and variables (0x7fff...) and it is all clear...

Well maybe not! Here it is again with comments after each chunk:

ark[2041:707] Start demo1
ark[2041:707] Strong local passed by autoreleasing reference
ark[2041:707] >>> 0x100176f30: init
ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1

Here we see that [Breadcrumbs newWith:@"apple"] creates an object at address 0x100176f30. This is stored in local, whose address is 0x7fff5fbfedc0, and the object has 1 owner (local).

ark[2041:707] indirect: passed reference 0x7fff5fbfedb8, contains 0x100176f30 - apple, owners 1

Here comes the hidden variable: as indirect: requires a reference to an autoreleasing variable ARC has created a new variable, whose address is 0x7fff5fbfedb8, and copied the object reference (0x100176f30) into that.

ark[2041:707] >>> 0x100427d10: init
ark[2041:707] >>> 0x100427d10: autorelease
ark[2041:707] indirect: returned

Inside indirect: a new object is created and ARC autoreleases it before assigning it - because the passed references refers to an autoreleasing variable.

Note: ARC does not need to do anything with the previous contents (0x100176f30) of the referenced variable (0x7fff5fbfedb8) as it is autoreleasing and hence not its responsibility. I.e. what "autoreleasing ownership" means is that any reference assigned must have already been effectively autoreleased. You'll see when creating the hidden variable ARC did not actually retain and autorelease its contents - it did not need to do this as it knows there is a strong reference (in local) to the object which it is managing. [In the last example below ARC does have to manage this assignment but it still manages to avoid using the autorelease pool.]

ark[2041:707] >>> 0x100427d10: retain
ark[2041:707] >>> 0x100176f30: release
ark[2041:707] >>> 0x100176f30: dealloc

These actions result from copying (the "writeback" in call-by-writeback) the value from the hidden variable into local. The release/dealloc are for the old strong reference in local, and the retain is for the object referenced by the hidden variable (which was autoreleased by indirect:)

Note: this writeback is why this only works for the "out" pattern of using pass-by-reference - you can't store the reference passed to indirect: as it is to a hidden local variable which is about to disappear...

ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100427d10 - banana, owners 2

So after the call local refers to the new object, and it has 2 owners - local accounts for one, and the other is the autorelease in indirect:

ark[2041:707] >>> 0x100427d10: release

demo1 is now finished so ARC releases the object in local

ark[2041:707] Flush demo1
ark[2041:707] >>> 0x100427d10: release
ark[2041:707] >>> 0x100427d10: dealloc
ark[2041:707] End demo1

and after demo1 returns the localized @autoreleasepool handles the autorelease pending from indirect:, now the ownership is zero and we get the dealloc.

Passing instance variables by reference

The above deals with passing local variables by reference, but unfortunately pass-by-writeback does not work for instance variables. There are two basic solutions:

  • copy your instance variable to a local

  • add some attributes

To demonstrate the second we add to class ByRef a strongIndirect: which specifies it requires a reference to a strong variable:

- (void) strongIndirect:(Breadcrumbs * __strong *)byRef
{
   *byRef = [Breadcrumbs newWith:@"plum"];
}

- (void) strongIndirectWrapper:(Breadcrumbs * __strong *)byRef
{
   NSLog(@"strongIndirect: passed reference %p, contains %p - %@, owners %lu", byRef, *byRef, *byRef, [*byRef ownerCount]);
   [self strongIndirect:byRef];
   NSLog(@"strongIndirect: returned");
}

and a corresponding demo2 which uses ByRef's instance variable (again with the imaginative name of instance):

- (void) demo2
{
   NSLog(@"Strong instance passed by strong reference");
   instance = [Breadcrumbs newWith:@"orange"];
   NSLog(@"instance: addr %p, contains %p - %@, owners %lu", &instance, instance, instance, [instance ownerCount]);
   [self strongIndirectWrapper:&instance];
   NSLog(@"instance: addr %p, contains %p - %@, owners %lu", &instance, instance, instance, [instance ownerCount]);
}

Execute this with a similiar piece of code as for demo1 above and we get:

1  ark[2041:707] Start demo2
2  ark[2041:707] Strong instance passed by strong reference
3  ark[2041:707] >>> 0x100176f30: init
4  ark[2041:707] instance: addr 0x100147518, contains 0x100176f30 - orange, owners 1
5  ark[2041:707] strongIndirect: passed reference 0x100147518, contains 0x100176f30 - orange, owners 1
6  ark[2041:707] >&gt

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...