Consider:
#import "TTAppDelegate.h"
@interface TTParent : NSObject
@property (atomic) NSMutableArray *children;
@end
@implementation TTParent
@end
@interface TTChild : NSObject
@property (atomic) TTParent *parent;
@end
@implementation TTChild
@end
@implementation TTAppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
TTParent *parent = [[TTParent alloc] init];
parent.children = [[NSMutableArray alloc] init];
for (int i = 0; i < 10; i++) {
TTChild *child = [[TTChild alloc] init];
child.parent = parent;
[parent.children addObject:child];
}
return YES;
}
@end
What is the bug in this code and what is its consequence? How could you fix it?
Answer
This is a classic example of a retain cycle. The parent will retain the children array, and the array will retain each TTChild object added to it. Each child object that is created will also retain its parent, so that even after the last external reference to parent is cleared, the retain count on parent will still be greater than zero and it will not be removed.
In order to fix this, the child’s reference back to the parent needs to be declared as a weak reference as follows:
@interface TTChild : NSObject
@property (weak, atomic) TTParent *parent;
@end
A weak reference will not increment the target’s retain count, and will be set to nil when the target is finally destroyed.
Note:
For a more complicated variation on this question, you could consider two peers that keep references to each other in an array. In this case, you will need to substitute NSArray/NSMutableArray with an NSPointerArray declared as:
NSPointerArray *weakRefArray = [[NSPointerArray alloc] initWithOptions: NSPointerFunctionsWeakMemory];
since NSArray normally stores a strong reference to its members.