I just complete my 2nd iOS homework, which took me cumulative 40 hours. The part that impressed (or... baffled...) me most is about dealing with memory management. In one scree, I opened the Xcode IDE, and in another screen, I just cross-referenced StackOverflow, Developer Library, and Google searching over and over again. There was no one-stop solution for all of the problems I met. But it only made me feel more enjoyable when I finally found the path to solution:) In case that I might forget the key points all I have collected, I record them here with references.
1.
Declaration of object.
MyObject* myObject;
WRONG! This means myObject points to a chunk of garbage memory, which would not correctly work at all. Basically, there are two ways to do this correctly:
MyObject* myObject=nil;
MyObject* myObject=[[MyObject alloc] init];
The former one makes myObject to point to nothing. Now it at least consistently does nothing, or I could use this pointer points to some useful object later on. But this will also lends to another issue about the life-cycle of a object which I will talk about in detail later. The latter one is to allocate a MyObject object in the memory, initialize it, and then make myObject point to the chunk of memory correctly allocated and initialized. Now, if the MyObject has the interface
@interface MyObject:NSObject{
NSString* string;
}
@end
@implementation MyObject
-(id) init {
if(self=[super init]){
...do something ...
}
return self;
}
after [super init] is successfully performed, Objective-C guarantees that the ivar string is set to nil, i.e. string points to nothing. But it is not that an NSString is allocated or initialized.
2. Basic rules about ownership and memory management:
- Any object returned by alloc, copy, copyWithZone, or new has a retain count of 1.
- retain increases the receiving object's retain count.
- release decreases the receiving object's retain count.
- autorelease tells the current autorelease pool to send the receiving object the release message “later”.
- Any factory method that doesn't have “new” or “copy” in the name (e.g., stringWithString:) returns an object that it has autoreleased on your behalf.
- Conversely, if you are not the creator of an object and have not expressed an ownership interest, you must not release it.
Or, digested a bit:
- Any method whose name contains copy, alloc, retain, or new returns an object that you own.
- Any method that doesn't, returns an object that you don't own.
- To own an object, retain it.
Reference:
StackOverflow, Developer Library.
An example about retain and release, and
another deeper discussion.
3.
copy versus
retain
There are lots of discussions about the subtle differences between these two operations, though they both intend to take the ownership from the acquired objects. "
(Almost) every time you use retain in Objective-C/Cocoa, you really should be using copy. Using retain can introduce some subtle bugs, and copy is faster then you think…" The problem with using retain to “take ownership” of an object is that someone else has a pointer to the same object, and if they change it, you will be affected.
There are some other discussions concerning the same kind of problem in
StackOverflow and
post in a blog. Besides, I also made to rookie mistakes that took me quite a while to find out and correct.
4.
Has a @synthesize property already init & alloc-ed?
The answer is NO. It needs to populate the property manually. The exception is if you have an IBOutlet property that you've connected in a nib file; that will get populated automatically when the nib is loaded.
For view controllers, the vast majority of properties are IBOutlets and properties that describe what the view will show, and the latter case is usually set by the object that creates the view controller. That will usually be the case for a view controller that shows a detail view for some object.
If you do have properties that are completely local to the view controller, a common pattern is to write your own getter and setter (rather than using @synthesize) and create the object in the getter if it doesn't exist. This lazy-loading behavior means you can easily free up resources in low-memory conditions, and that you only pay the cost of loading an object when you need it.
// simple lazy-loading getter
- (MyPropertyClass*) propertyName {
if(propertyIvarName == nil) {
propertyIvarName = [[MyPropertyClass alloc] init];
...... // ... other setup here
}
return propertyIvarName;
}
5. Pitfall about the use of getter/setter:
@interface MyObject:NSObject{
NSString *aString;
}
@property (nonatomic, copy) NSString *aString;
- (void) someMethod;
@end
@ implementation MyObject {
@synthesize aString;
- (id) init {
......
}
- (void) someMethod {
......
aString = [NSString stringWithString: @"blah, blah, blah."];
aString = [[NSString stringWithString: @"blah, blah, blah."] retain];
aString = [[NSString stringWithString: @"blah, blah, blah."] copy];
self.aString = [NSString stringWithString: @"blah, blah, blah."];
......
}
The compiler would not pop up any warning or error about the code above. But the 1st line in the method is extremely dangerous as well as a bug hard to find: the aString does not own the returned object, which will be released at some time after the method is completed. Corresponding to the second factor that I write above, it is obvious that this sentence has no keywords about ownership. Worse still, although the ownership is considered in the
@property, this sentence does not call the setter method. The right way to activate the setter is the 4th sentence. Besides, the 2nd and 3rd sentence request the ownership of the new object in another form. They are legal, of course.
[ UPDATE:
6.
viewDidUnload vs.
dealloc
Unless the program needs to break a retain cycle, it should generally only be releasing objects in the
dealloc method.
viewDidUnload is an
exception; it is invoked in low memory situations and should be used to release anything useless. Then, a preferable way to do this is to write another
releaseMemory function where takes most or all of the used objects into consideration and sets them into
nil, and then to call this function both in
viewDidUnload and
dealloc.
The general principle is just as mentioned above: if you do need to release them anywhere else, then always set the reference to nil after the release. That will protect the app from blowing up later (likely in dealloc).
References:
StackOverFlow-1,
StackOverFlow-2.
]
It is glad that I have gone this far. And, I will move on, and dig deeper.
cheers.