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
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:
Yes, the code is fine by ARC.
temp
is inferred to bestrong
and some behind the scenes stuff happens to pass it by reference todoSomething:
.ARC infers
byRefFoo
to be of typeFoo * __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 whendoSomething:
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 typeBreadcrumbs
; this is essentially a wrappedNSString
which tracks everyinit
,retain
,release
,autorelease
anddealloc
(well almost as you'll see below) so we can see what is going on. HowBreadcrumbs
is written is not material.Now consider the following class:
A method to change a value passed by reference:
A simple wrapper for
indirect:
so we can see what it is passed and when it returns:And a method to demonstrate
indirect:
called on a local variable (called imaginativelylocal
):Now some code to exercise
demo1
localizing the autorelease pool so we can see what is allocated, released and when:Executing the above produces the following on the console:
[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:
Here we see that
[Breadcrumbs newWith:@"apple"]
creates an object at address0x100176f30
. This is stored inlocal
, whose address is0x7fff5fbfedc0
, and the object has 1 owner (local
).Here comes the hidden variable: as
indirect:
requires a reference to an autoreleasing variable ARC has created a new variable, whose address is0x7fff5fbfedb8
, and copied the object reference (0x100176f30
) into that.Inside
indirect:
a new object is created and ARC autoreleases it before assigning it - because the passed references refers to an autoreleasing variable.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 inlocal
, and the retain is for the object referenced by the hidden variable (which was autoreleased byindirect:
)So after the call
local
refers to the new object, and it has 2 owners -local
accounts for one, and the other is theautorelease
inindirect:
demo1
is now finished so ARC releases the object inlocal
and after
demo1
returns the localized@autoreleasepool
handles the autorelease pending fromindirect:
, now the ownership is zero and we get thedealloc
.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
astrongIndirect:
which specifies it requires a reference to a strong variable:and a corresponding
demo2
which usesByRef
's instance variable (again with the imaginative name ofinstance
):Execute this with a similiar piece of code as for
demo1
above and we get:Which is a bit shorter than before. This is for two reasons:
As we are passing a strong variable (
instance
) to a method (strongIndirect:
) which expects a reference to a strong variable there is no need for ARC to use a hidden variable - the variables in line 4 and 5 above are the same (0x100147518
).As ARC knows the referenced variable in
strongIndirect:
is strong there is no need to store an autoreleased reference withinstrongIndirect:
and then write this back after the call - ARC just does a standard strong assignment, lines 6-8, and there is nothing to autorelease later (between lines 11 and 12).Does
strongIndirect:
work for strong locals?Of course, here is
demo3
:Executing this with our standard wrapper produces:
This is almost the same as the previous example, just two minor differences:
The address of the local on the stack is passed (
0x7fff5fbfedc0
), lines 4 and 5As it is stored in a local the new object is cleaned up by ARC, lines 11 and 12
Why not always add
__strong
to reference arguments?One reason is because not everything is strong! ARC's pass-by-writeback works for weak locals as well. Our final demo:
[Here we've just used
instance
so we have something to make a weak reference to.]Executing this with our standard wrapper produces:
Notes:
Lines 3-5 are just setting up
instance
- create a new value and release the old one - the real stuff starts at line 6ARC uses a hidden variable (line 8,
0x7fff5fbfedc8
) for weak locals (line 6,0x7fff5fbfedd0
) as wellARC has not elided the retain/autorelease on assigning to this hidden variable as it did above. You can see the autorelease on line 7 but my
Breadcrumbs
missed theretain
- but the ownership of 2 on line 8 shows it occurred.There are two autoreleases so there must be two corresponding releases when the pool is drained (lines 14 and 16) - there is only one corresponding dealloc (line 15) as the other object (
0x100427d20
) is referenced byinstance
and ARC cleans that up when ourByRef
instance goes away.Summary
Without any added attributes ARC will do the right thing for local (inferred strong) variables passed as parameters by reference (inferred autoreleasing). (And "local" includes parameters to the current method.)
This is implemented by ARC using pass-by-writeback and only works if you follow the "out" parameter pattern. If you wish to store the passed reference for use later you'll need to do more yourself.
If you wish to pass instance variables by reference you either need to copy them into locals or attribute the receiving parameter type with
__strong
.pass-by-writeback also works for
__weak
locals.Hope that helps.
Addendum Apr 2016:
__block
variablesIn the comments Heath Borders has asked:
Interesting question.
The specification states:
Local variables in (Objective-)C by default have automatic storage duration - they are automatically created and destroyed as their enclosing function/method/block is entered/exited. In the above answer when we refer to "local variable" we are implicitly referring to local variables with automatic storage duration.
Local variables can be declared with a storage qualifier or storage class specifier to change the storage duration of the variable. The most commonly seen one is
static
; local variables with static storage duration exist throughout the execution of the program but are only (directly) accessible within their local scope.If you attempt to pass a
static
local variable with pass-by-writeback the compiler will produce an error indicating the variable does not have automatic storage duration. You must handle such variables in the same way as instance variables (which have allocated storage duration).The
__block
storage qualifier was introduced into (Objective-)C as part of blocks and the specification states:So a
__block
local variable acts as if it has allocated storage duration, just like instance variables, and so by the specification of pass-by-writeback such a variable cannot be used as it does not have automatic storage duration...However with the tools current at the time of writing (Xcode 7.2, Clang 7.0.2)
__block
qualified local variables are supported by pass-by-writeback and are handle the same as those with automatic storage duration - a hidden__autoreleasing
temporary is used.This appears to be undocumented.
Having said that it is "safe" to use in the sense that it will either compile or not, and once compiled the code will work even if the tools change and it cannot be compiled again in the future... (at least without handling the variable the same was as instance variables must be handled).
The reason why it can be accepted can be gleaned from the rationale for the restrictions on pass-by-writeback (emphasis added):
There is no technical reason why instance variables could not be supported by pass-by-writeback, but it could be confusing due to aliasing.
__block
variables lie somewhere between automatic and allocated ones, so maybe the current tool writers choose to group them with the former rather than the latter for pass-by-writeback.This is perfectly legitimate. The property access is irrelevant; passing a pointer to an object is commonly done with
NSError*
objects.The correct way to declare your method is
This declares it as a pointer to an
__autoreleasing
object, which basically means that the object being pointed to is assumed to have been-autorelease
d.As for the "Furthermore", that's not an issue under ARC. Your line
is equivalent to
which I hope is obvious to you that this makes
temp
a strong reference, and thus it "owns" its value for as long as the variable exists. In other words, you can sayand
temp
is still valid.