It is pretty common to have relationships that go in two directions. For example, maybe an asset should know which employee is currently holding it. Let’s add that relationship. The new object diagram would look like this:
From a design standpoint, you would say that we are adding a pointer from the child (an instance of Asset) back to its parent (the instance of Employee that is holding it).
In Asset.h, add a pointer instance variable to hold on to the holding employee:
#import <Foundation/Foundation.h> @class Employee; @interface Asset : NSObject { NSString *label; Employee *holder; unsigned int resaleValue; } @property (strong) NSString *label; @property (strong) Employee *holder; @property unsigned int resaleValue; @end
In Asset.m, synthesize the accessors and extend the description method to display the holder:
#import "Asset.h" #import "Employee.h" @implementation Asset @synthesize label, resaleValue, holder; - (NSString *)description { // Is holder non-nil? if ([self holder]) { return [NSString stringWithFormat:@"<%@: $%d, assigned to %@>", [self label], [self resaleValue], [self holder]]; } else { return [NSString stringWithFormat:@"<%@: $%d unassigned>", [self label], [self resaleValue]]; } } - (void)dealloc { NSLog(@"deallocing %@", self); } @end
This brings us to a style question: When people use the Asset class and Employee class together, how do we make sure that the two relationships are consistent? That is, an asset should appear in an employee’s assets array if and only if the employee is the asset’s holder. There are three options:
Set both relationships explicitly:
[vicePresident addAssetsObject:townCar]; [townCar setHolder:vicePresident];
In the method that sets the child’s pointer, add the child to the parent’s collection.
- (void)setHolder:(Employee *)e { holder = e; [e addAssetsObject:self]; }
This approach is not at all common.
In the method that adds the child to the parent’s collection, set the child’s pointer.
In this exercise, you will take this last option. In Employee.m, extend the addAssetsObject: method to also set holder:
- (void)addAssetsObject:(Asset *)a { // Is assets nil? if (!assets) { // Create the array assets = [[NSMutableArray alloc] init]; } [assets addObject:a]; [a setHolder:self]; }
(One of my favorite bugs: have both accessors automatically call the other. This creates an infinite loop: addAssetsObject: calls setHolder: which calls addAssetsObject: which calls setHolder: which….)
Build and run the program. You should see something like this:
Employees: ( "<Employee 0: $0 in assets>", "<Employee 1: $153 in assets>", "<Employee 2: $119 in assets>", "<Employee 3: $68 in assets>", "<Employee 4: $0 in assets>", "<Employee 5: $136 in assets>", "<Employee 6: $119 in assets>", "<Employee 7: $34 in assets>", "<Employee 8: $0 in assets>", "<Employee 9: $136 in assets>" ) Giving up ownership of one employee Giving up ownership of array deallocating <Employee 0: $0 in assets> deallocating <Employee 4: $0 in assets> deallocating <Employee 8: $0 in assets>
Notice that now none of the employees with assets are getting deallocated properly. Also, none of the assets are being deallocated, either. Why?
The asset owns the employee and the employee owns the assets array, and the assets array owns the asset. It is an island of garbage created by this circle of ownership. These objects should be getting deallocated to free up memory, but they aren’t. This is known as a retain cycle. Retain cycles are a very common source of memory leaks.
To find retain cycles in your program, you can use Apple’s profiling tool, Instruments. When you profile a program, you monitor it while it runs to see what’s happening behind the scenes with your code and the system. However, your program runs and exits very, very quickly. To give you time to profile, put in a hundred seconds of sleep() at the end of your main() function:
... } sleep(100); return 0; }
In Xcode, choose Product → Profile in the menu. Instruments will launch. When the list of possible profiling instruments appears, choose Leaks:
As your program runs, you can browse the state of things. You have two instruments to choose from on the lefthand side of the window (Figure 20.3). Clicking on the Allocations instrument will let you see a bar graph of everything that has been allocated in your heap:
You can see, for example, that 10 instances of Asset are still living on your heap.
To look for retain cycles, change to the Leaks instrument and choose the Cycles view from the menu bar above the table. Select a particular cycle to see an object graph of it: