Passing objects safely as opaque context params under ARC - ios

Under manual memory management, I use this pattern fairly often:
NSString * myStr = /* some local object */
[UIView beginAnimation:#"foo" context:(void *)[myStr retain]];
And then, later and asynchronously:
- (void)animationDidStop:(NSString *)anim finished:(NSNumber *)num context:(void *)context
{
NSString * contextStr = (NSString *)context;
// ...
[contextStr release];
}
i.e. I manually managed the lifetime of an object used as an opaque context. (This is true for the old UIView animations but also for other kinds of API that I use.)
Under ARC, my instinct is that I want to __bridge_retained going in and __bridge_transfer in the handler, as suggested here. But this treats a Cocoa object as a CFType not because it's really bridged, but just for the purpose of shoving a retain down its throat.
Is this valid, and is this stylistically acceptable? If not, what's the better* solution?
(The accepted answer in this question gives a different answer, saying that __bridge alone is OK, but that seems to me to be wrong, since the original string would be at risk of being deallocated as soon as it goes out of scope in the first function. Right?)
*Please don't say "use block-based animations instead". (That's not what I'm asking about.)

Go with your instinct. __bridge_retained transfers management of an object to you from ARC, while __bridge_transfer does the reverse, don't worry about treating the object as a CFType - you're not really doing that just taking over management.
The other approach you see recommended is to construct your code so that ARC retains management, but this can easily come across as contrived (and get messy). Having the API you're using maintain the value as it is designed to do is clean; just comment the code appropriately where management is handed to the API and returned back to ARC.

Even if using __bridge_retained/__bridge_transfer seems fine to me (transferring ownership to CoreFoundation or to any C code or to yourself is quite the same you just tell ARC that you are responsible for the object ownership at some point and give the ownership back to ARC later), you can instead keep a strong reference on your object you use as your context somewhere if you prefer, so that ARC does not reclaim its memory.
This can be achieved by using a #property(strong) in your class for example, affecting it to the value when you previously did your retain and assigning it to nil when you previously did your release to let the string go.
Note that if you need to keep around multiple contexts in the same class, you may opt for the option to use an NSMutableArray that keeps your context strings around instead of declaring a property for each context.
#interface YourClass ()
#property(strong) NSMutableArray* runningAnimationContexts;
#end
#implementation YourClass
-(id)init {
self = [super init];
if (self) {
self.runningAnimationContexts = [NSMutableArray array];
}
return self;
}
-(void)someMethod
{
// Example with two different parallel animations using old API
NSString * myStr = /* some local object */
[self.runningAnimationContexts addObject:myStr]; // ~ retain
[UIView beginAnimation:#"foo" context:(__bridge)myStr];
...
[UIView commitAnimations];
NSString * myStr2 = /* some other local object */
[self.runningAnimationContexts addObject:myStr2]; // ~ retain
[UIView beginAnimation:#"foo2" context:(__bridge)myStr2];
...
[UIView commitAnimations];
}
- (void)animationDidStop:(NSString *)anim finished:(NSNumber *)num context:(void *)context
{
NSString * contextStr = (__bridge NSString *)context;
// ...
[self.runningAnimationContexts removeObject:contextStr]; // ~ release
}
#end

Related

beginAnimations with "context"variable in ARC?

The question partially similar to existing ones but I still get error with memory management.
The following non-ARC code work:
[UIView beginAnimations:... context:[[NSNumber numberWithInt:i] retain]];
and somewhere in didStopSelector:
NSNumber * n = (NSNumber *)context;
...
[n release];
I tried to remove retain/release and to add copy (and combined these ways) but with no effect.
Additionally I saw another similar question:
UIView Animation on multiple UIImageViews in ARC
They pass imageName variable as context but they don't describe if it is retained or autoreleased.
Questions:
1)How to convert my code to ARC correctly?
2)Is there any difference in code if you pass retained/autoreleased context (of cousre, if autoreleased will work in general)?
Try __bridge_retained to retain object and cast it to void*
void *context = (__bridge_retained void *)( #1000 );
and then in animationDidStop you have to transfer ownership with __bridge_transfer. At this point ARC should naturally release the object in current autorelease pool.
- (void)animationDidStop:(void *)context {
NSNumber *n = (__bridge_transfer id)context;
}
Alternatively you can switch to block based API and reference views directly.

Why does this KVO code crash 100% of the time?

The code below will crash inside of NSKVOUnionSetAndNotify calling CFDictionaryGetValue with what appears to be a bogus dictionary.
It seems to be a race between the swizzled addFoos / NSKVOUnionSetAndNotify code and the act of adding and removing KVO observers.
#import <Foundation/Foundation.h>
#interface TestObject : NSObject
#property (readonly) NSSet *foos;
#end
#implementation TestObject {
NSMutableSet *_internalFoos;
dispatch_queue_t queue;
BOOL observed;
}
- (id)init {
self = [super init];
_internalFoos = [NSMutableSet set];
queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
return self;
}
- (void)start {
// Start a bunch of work hitting the unordered collection mutator
for (int i = 0; i < 10; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (YES) {
#autoreleasepool {
[self addFoos:[NSSet setWithObject:#(rand() % 100)]];
}
}
});
}
// Start work that will constantly observe and unobserve the unordered collection
[self observe];
}
- (void)observe {
dispatch_async(dispatch_get_main_queue(), ^{
observed = YES;
[self addObserver:self forKeyPath:#"foos" options:0 context:NULL];
});
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
dispatch_async(dispatch_get_main_queue(), ^{
if (observed) {
observed = NO;
[self removeObserver:self forKeyPath:#"foos"];
[self observe];
}
});
}
// Public unordered collection property
- (NSSet *)foos {
__block NSSet *result;
dispatch_sync(queue, ^{
result = [_internalFoos copy];
});
return result;
}
// KVO compliant mutators for unordered collection
- (void)addFoos:(NSSet *)objects {
dispatch_barrier_sync(queue, ^{
[_internalFoos unionSet:objects];
});
}
- (void)removeFoos:(NSSet *)objects {
dispatch_barrier_sync(queue, ^{
[_internalFoos minusSet:objects];
});
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
TestObject *t = [[TestObject alloc] init];
[t start];
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10000, false);
}
return 0;
}
The actual crash you get is an EXC_BAD_ACCESS when the key value observing dictionary is accesed. The stack trace is as follows:
* thread #2: tid = 0x1ade39, 0x00007fff92f8e097 libobjc.A.dylib`objc_msgSend + 23, queue = 'com.apple.root.default-priority', stop reason = EXC_BAD_ACCESS (code=1, address=0x18)
frame #0: 0x00007fff92f8e097 libobjc.A.dylib`objc_msgSend + 23
frame #1: 0x00007fff8ffe2b11 CoreFoundation`CFDictionaryGetValue + 145
frame #2: 0x00007fff8dc55750 Foundation`NSKVOUnionSetAndNotify + 147
* frame #3: 0x0000000100000f85 TestApp`__19-[TestObject start]_block_invoke(.block_descriptor=<unavailable>) + 165 at main.m:34
frame #4: 0x000000010001832d libdispatch.dylib`_dispatch_call_block_and_release + 12
frame #5: 0x0000000100014925 libdispatch.dylib`_dispatch_client_callout + 8
frame #6: 0x0000000100016c3d libdispatch.dylib`_dispatch_root_queue_drain + 601
frame #7: 0x00000001000182e6 libdispatch.dylib`_dispatch_worker_thread2 + 52
frame #8: 0x00007fff9291eef8 libsystem_pthread.dylib`_pthread_wqthread + 314
frame #9: 0x00007fff92921fb9 libsystem_pthread.dylib`start_wqthread + 13
If you set a symbolic breakpoint with the symbol NSKVOUnionSetAndNotify the debugger will stop where this method is being invoked.
The crash you are seeing is because automatic key-value notifications are being sent from one thread when you invoke your [addFoos:] method, but then the change dictionary is being accessed from another thread. This is stimulated by your use of the global dispatch queue when calling this method, as that will execute the block in many different threads.
There are mulitple ways to fix this crash, and I will try to walk you through this to give you a more thourough understanding of what is going on.
In the simplest case, you can fix the crash by using the key-value coding mutable proxy object for this key:
NSMutableSet *someSet = [self mutableSetValueForKey:#"foos"];
[someSet unionSet:[NSSet setWithObject:#(rand() % 100)]];
That will stop this particular crash. What's happening here? When mutableSetValueForKey: is called, the result is a proxy object that forwards messages to your KVC-compliant accessor methods for the key "foos". The author's object does not actually fully conform to the required pattern for a KVC compliant property of this type. If other KVC accessor methods are messaged for this key, they may go through non-thread safe accessors provided by Foundation, which can result in this crash all over again. We'll get to how to fix that in a moment.
The crash is being triggered by automatic KVO change notifications crossing threads. Automatic KVO notifications work by swizzling classes and methods at runtime. You can read a more in-depth explanation here and here. KVC accessor methods are essentially wrapped at runtime with KVO-supplied methods. This is in fact where the crash in the original application is happening. This is the KVO inserted code disassembled from Foundation:
int _NSKVOUnionSetAndNotify(int arg0, int arg1, int arg2) {
r4 = object_getIndexedIvars(object_getClass(arg0));
OSSpinLockLock(_NSKVONotifyingInfoPropertyKeysSpinLock);
r6 = CFDictionaryGetValue(*(r4 + 0xc), arg1);
OSSpinLockUnlock(_NSKVONotifyingInfoPropertyKeysSpinLock);
var_0 = arg2;
[arg0 willChangeValueForKey:r6 withSetMutation:0x1 usingObjects:STK-1];
r0 = *r4;
r0 = class_getInstanceMethod(r0, arg1);
method_invoke(arg0, r0);
var_0 = arg2;
r0 = [arg0 didChangeValueForKey:r6 withSetMutation:0x1 usingObjects:STK-1];
Pop();
Pop();
Pop();
return r0;
}
As you can see, this is wrapping a KVC compliant accessor method with willChangeValueForKey:withSetMutation:usingObjects: and didChangeValueForKey: withSetMutation:usingObjects:. These are the methods that send out KVO notifications. KVO will insert this wrapper at runtime if the object has opted into automatic key value observer notification. In between these calls you can see class_getInstanceMethod. This is getting a reference to the KVC compliant accessor being wrapped, and then calling it. In the case of the original code, this is being triggered from inside NSSet's unionSet:, which was happening across threads and causing the crash when it accessed the change dictionary.
Automatic notifications are sent by the thread where the change occured, and are intended to be received on the same thread. This being Teh IntarWebs, there is a lot of bad or misleading information out there about KVO. Not all objects and not all properties emit automatic KVO notifications, and in your classes you can control which do and don't. From the Key Value Observing Programming Guide: Automatic Change Notification :
NSObject provides a basic implementation of automatic key-value change notification. Automatic key-value change notification informs observers of changes made using key-value compliant accessors, as well as the key-value coding methods. Automatic notification is also supported by the collection proxy objects returned by, for example, mutableArrayValueForKey:
This may lead one to believe that all descendants of NSObject emit automatic notifications by default. This is not the case - may framework classes do not, or implement special behavior. Core Data is an example. From the Core Data Programming Guide :
NSManagedObject disables automatic key-value observing (KVO) change notifications for modeled properties, and the primitive accessor methods do not invoke the access and change notification methods. For unmodeled properties, on OS X v10.4 Core Data also disables automatic KVO; on OS X v10.5 and later, Core Data adopts to NSObject’s behavior.
As a developer, you can ensure that automatic key value observer notifications are on or off for a particular property by implementing a method with the correct naming convention, +automaticallyNotifiesObserversOf<Key>. When this method returns NO, automatic key value notifications are not emitted for this property. When automatic change notifications are disabled KVO also does not have to swizzle the accessor method at runtime, as this is done primarily to support automatic change notifications. For example:
+ (BOOL) automaticallyNotifiesObserversOfFoos {
return NO;
}
In a comment the author stated that the reason he was using dispatch_barrier_sync for his accessor methods is that if he did not, KVO notifications would arrive before changes occured. With automatic notifications disabled for a property, you still have the option of sending these notifications manually. This is done by using the methods willChangeValueForKey: and didChangeValueForKey:. Not only does this give you control of when these notifications are sent (if at all), but on what thread. Automatic change notifications, as you recall, are sent from and received on the thread where the change occured.
For example, if you wanted change notifications to happen only on the main queue, you could do so using recursive decomposition:
- (void)addFoos:(NSSet *)objects {
dispatch_async(dispatch_get_main_queue(), ^{
[self willChangeValueForKey:#"foos"];
dispatch_barrier_sync(queue, ^{
[_internalFoos unionSet:objects];
dispatch_async(dispatch_get_main_queue(), ^{
[self didChangeValueForKey:#"foos"];
});
});
});
}
The original class in the author's question was forcing KVO observation to start and stop on the main queue, which seems have been an attempt to emit notifications on the main queue. The above example demonstrates a solution that not only addresses that concern, but ensures that the KVO notifications are correctly sent before and after the data changes.
In the example above I modified the author's original method as an illustrative example - this class is still not correctly KVC compliant for the key "foos". To be Key-Value Observing compliant, an object must first be Key-Value Coding compliant. To address this, first create the correct Key-value coding compliant accessors for an unordered mutable collection :
Immutable:
countOfFoos
enumeratorOfFoos
memberOfFoos:
Mutable:
addFoosObject:
removeFoosObject:
These are just the minimum, there are additional methods that can be implemented for performance or data integrity reasons.
The original application was using a concurrent queue and dispatch_barrier_sync. This was dangerous, for many reasons. The approach recommended by the Concurrency Programming Guide is to instead use a serial queue. This ensures that only one thing can be touching the protected resource at a time, and it is from a consistent context. For example, two of the above methods would look like this:
- (NSUInteger)countOfFoos {
__block NSUInteger result = 0;
dispatch_sync([self serialQueue], ^{
result = [[self internalFoos] count];
});
return result;
}
- (void) addFoosObject:(id)object {
id addedObject = [object copy];
dispatch_async([self serialQueue], ^{
[[self internalFoos] addObject:addedObject];
});
}
Note that in this example and the next, I am not including manual KVO change notifications for brevity and clarity. If you want manual change notifications to be sent, that code should be added to these methods like what you saw in the previous example.
Unlike using dispatch_barrier_sync with a concurrent queue, this will not allow a deadlock.
The WWDC 2011 Session 210 Mastering Grand Central Dispatch showed the correct use of the dispatch barrier API for implementing a reader/writer lock for a collection using a concurrent queue. This would be implemented like this:
- (id) memberOfFoos:(id)object {
__block id result = nil;
dispatch_sync([self concurrentQueue], ^{
result = [[self internalFoos] member:object];
});
return result;
}
- (void) addFoosObject:(id)object {
id addedObject = [object copy];
dispatch_barrier_async([self concurrentQueue], ^{
[[self internalFoos] addObject:addedObject];
});
}
Note that the dispatch barrier is accessed asynchronously for the write operation, while the read operation uses dispatch_sync. The original application used dispatch_barrier_sync for both reads and writes, which the author stated was done to control when automatic change notifications were sent. Using manual change notifications would address that concern (again, not shown in this example for brevity and clarity).
There are still issues with the KVO implementation in the original. It does not use the context pointer to determine ownership of an observation. This is a recommended practice, and can use a pointer to self as a value. The value should have the same address as the objected used to add and remove the observer:
[self addObserver:self forKeyPath:#"foos" options:NSKeyValueObservingOptionNew context:(void *)self];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == (__bridge void *)self){
// check the key path, etc.
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
From the NSKeyValueObserving.h header:
You should use -removeObserver:forKeyPath:context: instead of -removeObserver:forKeyPath: whenever possible because it allows you to more precisely specify your intent. When the same observer is registered for the same key path multiple times, but with different context pointers each time, -removeObserver:forKeyPath: has to guess at the context pointer when deciding what exactly to remove, and it can guess wrong.
If you are interested in a further understanding of applying and implementing Key Value Observing, I suggest the video KVO Considered Awesome
In summary:
• Implement the required key-value coding accessor pattern (unordered mutable collection)
• Make those accessors thread safe (using a serial queue with dispatch_sync/dispatch_async, or a concurrent queue with dispatch_sync/dispatch_barrier_async)
• Decide wether you want automatic KVO notifications or not, implement automaticallyNotifiesObserversOfFoos accordingly
• Add manual change notifications appropriately to accessor methods
• Make sure that code which accesses your property does so through the correct KVC accessor methods (i.e. mutableSetValueForKey:)

Possible memory leak with KVO and context

I'm trying to make a little binding system between my UILabel and my object Data using KVO. If my UI change, my data have to change, and if my data change my UI should refresh to display the new value.
The biggest issue I have is that I need to cast a custom object to a void* (context) with __bridge_retained void* - or CFBridgingRetain() - but I don't know where I should call CFBridgingRelease(). If call it in observeValueForKeyPath method I get a bad access error (I guess because my Reference Count to the object pointed by context is 0)
// viewDidLoad
// binding my label text with a custom data object
[self bindObject:_myLabel withPath:#"text" toObject:_user path:#"name"];
-(void) bindObject:(id)uiObj withPath:(NSString *)uiPath toObject:(id)dataObj path:(NSString *)dataPath
{
// custom object storing the object I want to bind and his path
PLSObjectPath* op = [[PLSObjectPath alloc] init];
op.theObj = dataObj;
op.thePath = dataPath;
PLSObjectPath* ob = [[PLSObjectPath alloc] init];
ob.theObj = uiObj;
ob.thePath = uiPath;
/* possible leak because I don't know where to call CFBridgingRelease */
[uiObj addObserver:self forKeyPath:uiPath options:NSKeyValueObservingOptionNew context:(__bridge_retained void*)(op)];
[dataObj addObserver:self forKeyPath:dataPath options:NSKeyValueObservingOptionNew context:(__bridge_retained void*)(ob)];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
PLSObjectPath *obj = (__bridge PLSObjectPath*) context;
PLSObjectPath* pairObj = [[PLSObjectPath alloc] init];
pairObj.theObj = object;
pairObj.thePath = keyPath;
// avoid infinite loop
[obj.theObj removeObserver:self forKeyPath:obj.thePath];
[obj.theObj setValue:change[#"new"] forKeyPath:obj.thePath];
[obj.theObj addObserver:self forKeyPath:obj.thePath options:NSKeyValueObservingOptionNew context:(__bridge_retained void*)(pairObj)];
}
Traditionally users of this have used a static char * as the context parameter, so as to differentiate the different observeValueForKeyPath messages. That said, it should be possible to do something as you are attempting.
What I would suggest is to switch from a custom object to a Core Foundation one, where you can do you own memory management explicitly. Thus I'd suggest changing PLSObjectPath to CFDictionary. You can first create a NSDictionary, then "transfer" it to the CF domain with the appropriate cast, and pass that CFDictionary object for context (which is now a retained CF object). Recast it in observeValueForKeyPath to a CFDictionary, properly ARC cast it to a NSDictionary, use that, then it should get released if you've done the ARC correctly. This is all a well understood paradyme - moving objects in and out of ARC.
Another way you could do it is us a static NSMutableDictionary, and use the context pointer to go to a int value, which when converted to a NSNumber is the key to the dictionary. If all KVO occurs on the main thread, you don't need to protect the dictionary, but if not then you will need to put all access to the dictionary behind a serial dispatch queue that in forces serial access on one thread.
The memory leak is from [obj.theObj removeObserver:self forKeyPath:obj.thePath]. You are removing the observer but as the system doesn't treat the context as an object, it will not release it. Also, there is no way for you to get the context from the observed object itself.
At some point you will need to stop all observation to allow your view to be deallocated. This should happen from viewDid(Will)Disappear:. To be able to release the PLSObjectPath:s at that point, you will need to store them somewhere, possibly an NSMutableArray. If you will store these anyway for this purpose, you don't need to use __bridge_retained. Also, in this case your PLSObjectPath:s might/should contain a void* pointing to the other context. This way, you save the creation of PLSObject in observeValueForKeyPath:ofObject:change:context:.
Just a comment, you should start the KVO from viewWill(Did)Appear: and not from viewDidLoad. It gives you a better KVO start/stop management and also removes the unnecessary observation while your view is not on the screen.

Is it ok not to invoke [super init] in a custom init method?

I have a MKPolyline subblass which I want to implement NSCoding, i.e.
#interface RSRoutePolyline : MKPolyline <NSCoding>
I asked a question on the best way to encode the c-array and got an excellent answer. However, there is no init method defined on MKPolyline, i.e. there is no other way to give it data other than its class method polylineWithPoints:points.
Is this code where my comment is ok?
- (void)encodeWithCoder:(NSCoder *)aCoder
{
MKMapPoint *points = self.points;
NSUInteger pointCount = self.pointCount;
NSData *pointData = [NSData dataWithBytes:points length:pointCount * sizeof(MKMapPoint)];
[aCoder encodeObject:pointData forKey:#"points"];
[aCoder encodeInteger:pointCount forKey:#"pointCount"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
NSData* pointData = [aDecoder decodeObjectForKey:#"points"];
NSUInteger pointCount = [aDecoder decodeIntegerForKey:#"pointCount"];
// Edit here from #ughoavgfhw's comment
MKMapPoint* points = (MKMapPoint*)[pointData bytes];
// Is this line ok?
self = (RSRoutePolyline*)[MKPolyline polylineWithPoints:points count:pointCount];
return self;
}
You should call an init method on any subclass of NSObject. Since MKPolyline is an NSObject, you should init it.
But MKPolyline has no methods and no init. This is Objective C's was of telling you that you can't subclass it.
Instead, as WDUK suggested, define your own class. It keeps track of your list point points, and manages NSCoding to save and restore them as needed.
#interface RSPolyline: NSObject<NSCoding>
- (id) initWithPoints: (NSArray*) points;
- (id) initWithCoder:(NSCoder *)aDecoder;
- (void) encodeWithCoder:(NSCoder *)aCoder;
- (MKPolyline*) polyLine;
#end
Your class can generate a polyline on request, perhaps caching the result if performance is an issue.
As a rule, don't reach for inheritance first. When you want to extend and improve a class, think first of composition.
It's dirty not to call [super init], and it doesn't bode well with my idea of good programming. Without calling super yourself, it isn't a true subclass; just a bastardization of composition that relies on a side effect of calling a convenience constructor. Saying this, I believe your method described will work OK, but it goes against the grain of good Objective-C programming and its conventions.
What I would suggest is to use MKPolyLine as an MKPolyLine instance, and use a category to add the extra bells and whistles you need. As for adding extra instance variables and such, you can use associated objects. An introduction to this concept can be found here, and this SO question addresses the use of them with categories: How do I use objc_setAssociatedObject/objc_getAssociatedObject inside an object?
While it is generally allowed to create and return a different object in an init method, there are three problems with that line (explained below). Instead of this, I would suggest overriding the points and pointCount properties so that you can return values stored in an instance variable, and call the super implementation there if the instance variable is empty. Then, your initializer just sets these instance variables so that they will be used.
- (MKMapPoint *)points {
if(myPointsIvar == NULL) return [super points];
else return myPointsIvar;
}
// similarly for pointCount
The first problem is that you are creating a new object, but not releasing the old one, which means you are leaking it. You should store the result in a different variable, then release self, then return the result (you don't need to store it in self).
Second, polylineWithPoints:count: returns an autoreleased object, but initWithCoder: should return a retained one. Unless there is another retain on it, it could be deallocated while you are still using it.
If these were the only problems, you could solve both like this:
MKPolyline *result = [MKPolyline polylineWithPoints:points count:pointCount];
[self release];
return [result retain];
However, there is a third problem which cannot be solved so easily. polylineWithPoints:count: does not return a RSRoutePolyline object, and the object it returns may not be compatible with your subclass's methods (e.g. it probably won't support NSCoding). There really isn't a way to fix this, so you can't use polylineWithPoints:count:.

Hooking end of ARC dealloc

Given the following simple implementation:
#implementation RTUDeallocLogger
-(void)dealloc
{
NSLog(#"deallocated");
}
#end
we run the following code under ARC:
#implementation RTURunner
{
NSArray* arr;
}
-(void)run{
arr = [NSArray
arrayWithObjects:[[RTUDeallocLogger alloc]init],
[[RTUDeallocLogger alloc]init],
[[RTUDeallocLogger alloc]init],
nil];
NSLog(#"nulling arr");
arr = NULL;
NSLog(#"finished nulling");
}
#end
we get the following log output:
nulling arr
finished nulling
deallocated
deallocated
deallocated
I'd like to perform an action after all the deallocations have finished. Is this possible?
The aim of this question is really to understand a little more about the mechanics of ARC, in particular, at what point ARC triggers these deallocations, and whether or not this can ever happen synchronously when I drop references.
-dealloc is always synchronous, and occurs when the last strong reference is removed. In the case of your code though, +arrayWithObjects: is likely (if compiled at -O0 at least) putting the array in the autorelease pool, so the last strong reference is removed when the pool drains, not when you set the variable to NULL (you should use nil for ObjC objects, btw).
You can likely avoid having the object in the autorelease pool by using alloc/init to create, and you may (implementation detail, bla bla) be able to avoid it by compiling with optimizations turned on. You can also use #autoreleasepool { } to introduce an inner pool and bound the lifetime that way.
If I were an engineer from Apple I'd probably argue that your problem is probably your design. There are almost no reasons you'd want effectively to act by watching dealloc rather than having dealloc itself act.
[a huge edit follows: weak properties don't go through the normal property mechanisms, so they aren't KVO compliant, including for internal implicit KVO as originally proposed]
That said, what you can do is bind the lifetime of two objects together via object associations and use the dealloc of the latter as a call-out on the dealloc of the former.
So, e.g.
#import <objc/runtime.h>
#interface DeallocNotifier;
- (id)initWithObject:(id)object target:(id)target action:(SEL)action;
#end
#implementation DeallocNotifier
- (id)initWithObject:(id)object target:(id)target action:(SEL)action
{
... blah ...
// we'll use a static int even though we'll never access by this key again
// to definitely ensure no potential collisions from lazy patterns
static int anyOldKeyWellNeverUseAgain;
objc_setAssociatedObject(object, &anyOldKeyWellNeverUseAgain, self, OBJC_ASSOCIATION_RETAIN);
... blah ...
}
- (void)dealloc
{
[_target performSelector:_action];
}
#end
-(void)run{
arr = ...
[[DeallocNotifier alloc]
initWithObject:arr target:self action:#selector(arrayDidDealloc)];
/* you may not even need *arr in this case; I'm unclear as
to why you have an instance variable for something you don't
want to keep, so I guess it'll depend on your code */
} // end of run
- (void)arrayDidDealloc
{
NSLog(#"array was deallocated");
}
I've assumed you're able to tie the lifecycle of all the objects you're interested in to that of a single container; otherwise you could associate the notifier to all relevant objects.
The array has definitely gone by the time you get arrayDidDealloc.
at what point ARC triggers these deallocations
ARC inserts allocations/deallocations into your code based on static analysis. You can see where it does this by looking at the assembly of your source -- go to Product -> Generate Output in Xcode.
whether or not this can ever happen synchronously when I drop references
Retain/release/autorelease is always synchronous.
Your code
arr = [NSArray arrayWithObjects:[[RTUDeallocLogger alloc] init],
[[RTUDeallocLogger alloc] init],
[[RTUDeallocLogger alloc] init],
nil];
will be implicitly placing the objects into an autorelease pool. After the object is allocated, you don't want it retained (because the NSArray will do the retain once it receives the object), but you can't release it immediately, otherwise it will never make it to the NSArray alive. This is the purpose of autorelease - to cover the case where the object would otherwise be in limbo between two owners.
The retain count at alloc time is 1, then it's retained by the autoreleasepool and released by you, so the retain count remains 1. Then, it's retained by the NSArray, so the retain count becomes 2.
Later, the NSArray is released and so the retain count returns to 1, and the objects are finally cleaned up when the autorelease pool gets its chance to run.
You can make the autorelease act faster by nesting another pool - by wrapping your NSArray creation with an #autorelease{} clause.

Resources