一 、打开和关闭垃圾搜集器
Let's say that there are two instances of Person and that each has a favoriteColor, a pointer to a color object. If two people have the same favoriteColor, the objects will have pointers to the same color object. As the people age, their favorite color might change. Eventually, the color object might be no one's favorite (Figure 4.1).
Figure 4.1. The Problem
We do not want this orphaned color to be taking up room in our program's memory. We want the memory deallocated, so that we can put new objects in that memory, but we must be sure that the color is not deallocated while any objects are pointing to it.
This is a relatively tricky problem. Apple has come up with two solutions:
What are the trade-offs? Retain counts are a bit cumbersome(笨重的;不方便的): You need to explicitly retain objects that you want to keep around and to explicity release them when you are no longer interested in them. The retain-count mechanism also creates a dastardly(卑怯的) problem: object A retains object B, and B retains A. They are an island of garbage that will never go away, because they are retaining each other. This is known as a retain cycle. Figure 4.2 is an example of a common retain cycle.
Figure 4.2. An Island of Garbage
Why not always use the garbage collector? If you use the garbage collector, your application will not run on any version of Mac OS before 10.5. Also, the garbage collector requires some CPU time to scan through the objects, looking for garbage. This can sometimes result in poorer performance. In an application that does a lot of audio or video processing, the garbage collector can cause hiccups(打嗝声) in the processing while it is doing a scan.
Turning the Garbage Collector On and Off
In the Groups and Files outline view of Xcode, you will see a group called Targets. Every project has at least one target. The targetrepresents one build process and typically results in one product. So, for example, you might have a project with two targets: One builds an application, and the other builds the Spotlight importer for that application's data file.
Look at the lottery project. Double-click the lottery target to open the Inspector. Look at the Build panel. This is where you can set all the variables that control how the program is built.
In the search field, type in Garbage. Many lines will disappear, and you will see Objective-C Garbage Collection. Here, you can choose whether you want the garbage collector enabled. If you choose Unsupported, the garbage collector will not be enabled (Figure 4.3).
Figure 4.3. Enabling/Disabling the Garbage Collector
The other two choices will enable the garbage collector. You must recompile before the change takes effect. (The difference between Supported and Required is interesting only if you are creating a framework or plug-in that will be used in another application.)
Your application will have an instance of NSGarbageCollector if and only if it is using the garbage collector. Add a line near the end of lottery.m:
[pool drain]; NSLog(@"GC = %@", [NSGarbageCollector defaultCollector]); return 0;
Build and run the tool. Are you using the garbage collector?
If you are using the garbage collector, it is important that you not keep references to objects that you don't care about. In themain function of lottery, you should set the now and array pointers to nil when you lose interest in them. Add those lines now:
}
// Done with 'now',now是一个局部变量
now = nil;
for (LotteryEntry *entryToPrint in array) {
NSLog(@"%@", entryToPrint);
}
NSLog(@"%@", entryToPrint);
}
// Done with 'array',array是一个局部变量array = nil;[pool drain];
NSLog(@"GC = %@", [NSGarbageCollector defaultCollector]);
return 0;
}
Now the garbage collector will know that it can deallocate the NSCalendarDate that now pointed to and the NSMutableArray thatarray pointed to. (In reality, the program almost certainly exits before the garbage collector gets around to deallocating those objects.)
三、Living with Retain Counts
Creating Autoreleased Objects
You created a description method that looks like this:
- (NSString *)description
{
NSString *result;
result = [[NSString alloc] initWithFormat:@"%@ = %d and %d",
[entryDate descriptionWithCalendarFormat:@"%b %d %Y"],
firstNumber, secondNumber];
return result;
}
This code would work perfectly well but would result in an annoying memory leak. The alloc operation always yields an object with a retain count of 1; thus, the string being returned has a retain count of 1. Any object asking for the string would retain it. The string would then have a retain count of 2. When no longer interested in the string, the object would release it. The retain count would become 1. As you see, the string would never be deallocated.
Our next attempt might look something like this:
- (NSString *)description { NSString *result; result = [[NSString alloc] initWithFormat:@"%@ = %d and %d", [entryDate descriptionWithCalendarFormat:@"%b %d %Y"], firstNumber, secondNumber]; [result release]; return result; }
This code would not work at all. When sent the message release, the string's retain count would go to zero, and the string would be deallocated. The object asking for the string would get a pointer to a freed object.
The problem, then, is that you need to return a string, but you do not want to retain it. This is a common problem throughout the frameworks, which leads us to how the NSAutoreleasePool is used in a non-GC application.
Objects are added to the current autorelease pool when they are sent the message autorelease. When the autorelease pool is drained, it sends the message release to all objects in the pool.
In other words, when an object is autoreleased, it is marked to be sent release sometime in the future. In particular, in a Cocoa application, an autorelease pool is created before every event is handled and is drained after the event has been handled. Thus, unless the objects in the autorelease pools are being retained, they will be destroyed as soon as the event has been handled.
A correct solution, then, is
- (NSString *)description { NSString *result; result = [[NSString alloc] initWithFormat:@"%@ = %d and %d", [entryDate descriptionWithCalendarFormat:@"%b %d %Y"], firstNumber, secondNumber]; [result autorelease]; return result; }
Rules Concerning Release
Objects created by alloc, new, copy, or mutableCopy have a retain count of 1 and are not in the autorelease pool.
If you get an object by any other method, assume that it has a retain count of 1 and is in the autorelease pool. If you do not wish it to be deallocated with the current autorelease pool, you must retain it.
Because you will frequently need objects that you are not retaining, many classes have class methods that return autoreleased objects. NSString, for example, has stringWithFormat:. The simplest correct solution then would be
- (NSString *)description
{
return [NSString stringWithFormat:@"%@ = %d and %d",
[entryDate descriptionWithCalendarFormat:@"%b %d %Y"],
firstNumber, secondNumber];
}
Temporary Objects
Note that the autoreleased object won't be released until the event loop ends. This behavior makes it perfect for providing an intermediate result. For example, if you had an array of NSString objects, you could create a string with all the elements in uppercase and concatenated together, like this:
- (NSString *)concatenatedAndAllCaps
{
int i;
NSString *sum = @"";
NSString *upper;
for (i=0; i < [myArray count]; i++) {
upper = [[myArray objectAtIndex:i] uppercaseString];
sum = [NSString stringWithFormat:@"%@%@", sum, upper];
}
return sum;
}
With this method, if you have 13 strings in the array, 26 autoreleased strings will be created: 13 by uppercaseString and 13 bystringWithFormat:; the initial constant string is a special case and doesn't count. One of the resulting strings is returned and may be retained by the object that asked for it. The other 25 strings are deallocated automatically at the end of the event loop. (Note that you would probably get better performance in this example by appending the uppercased string to anNSMutableString instead of creating a new string and adding it to the autorelease pool each time through the loop.)