Cocoa Programming for Mac OS X 第四章(Memory Management)摘录

一 、打开和关闭垃圾搜集器

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:

  1. The old solution uses retain counts: Every object has a retain count, which should represent the number of other objects that have pointers to it. If the color is the favorite of two people, the retain count of that color should be 2. When it goes to zero, the object is deallocated.

  2. The new solution, introduced in 10.5, is a garbage collector, which babysits(保姆) the entire object graph, looking for objects that can't be reached from the variables that are in scope. The unreachable objects are automatically deallocated.

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.)


二 、 Living with the Garbage Collector

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 allocnewcopy, 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.)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值