Chapter 3. Digging Deeper Into Objective-C

In This Chapter

  • Working through the standard way to do initialization

  • Initializing superclasses and subclasses

  • Managing memory

Chapter 1 of this minibook explains that the birth of an object starts with alloc and init. It is alloc that sets aside some memory for the object and returns back a pointer to that memory. This fact is important to keep in mind, because after you create these new objects, you become responsible for managing them, as I show in this chapter. Managing the memory allocated for your objects can be one of the few real hassles in programming with Objective-C.

Warning

Memory management is not glamorous, but it trumps cool in an application. In fact, memory management is probably the single most vexing thing about iPhone programming. It has made countless programmers crazy, and I can't stress enough how important it is to build memory management into your code from the start. Retrofitting can be a nightmare. (Co-author Neal still has dreams in which hell is having to go back through an infinite number of lines of code and retrofit memory management code.)

The init method is named for initialization, which I introduce in Chapter 1 of this minibook. This chapter spells out some of your initialization options.

Initializing Objects

Initialization is the procedure that sets the instance variables of an object to a known initial state. Essentially, you need to initialize an object to assign initial values to these variables. Although initialization is not required — if you can live with all the instance variables initialized to 0 and nil, then there's nothing you need to do — you may have a class (or superclass) with instance variables that you need to initialize to something else, so you need some kind of initialization method.

An initialization method doesn't have to include an argument for every instance variable, because some will become relevant only during the course of your object's existence. You must make sure, however, that all the instance variables your object uses, including other objects it needs to do its work, are in a state that enables your object to respond to the messages it receives.

You may think the main job in initialization is to, well, initialize the variables in your objects (hence, the name), but more is involved when there's a superclass and a subclass chain.

To see what I mean, start by looking at the initializer I'm going to use for the CashTransaction class in Listing 3-1.

Example 3-1. The CashTransaction Initializer

- (id) initWithAmount: (double) theAmount forBudget:
   (Budget*) aBudget {

  if (self = [super initWithAmount:theAmount
                              forBudget:aBudget]) {

   name = @"Cash";
 }
  return self;
}

By convention, initialization methods begin with the abbreviation init. (This is true, however, only for instance — as opposed to class — methods.) If the method takes no arguments, the method name is just init. If it takes arguments, labels for the arguments follow the init prefix.

As you can see, the initializer in Listing 3-1 has a return type of id. You discover the reason for that later, in the section "Invoking the superclass's init method."

Initialization involves these three steps:

  1. Invoke the superclass's init method.

  2. Initialize instance variables.

  3. Return self.

The following sections explain each step.

Invoking the superclass's init method

This is the type of statement you use to get the init method up and running:

(self = [super initWithAmount:theAmount
                                      forBudget:aBudget])

In this part of the statement, you're invoking the superclass's init method:

[super initWithAmount:theAmount forBudget:aBudget]

The entire statement sets up self and super as equals so that you can then use super.

Chapter 2 of this minibook points out that this is the standard way of sending a message to your superclass.

Warning

Back in Chapter 2 of this minibook — in the section about deriving classes to implement inheritance in a program, to be precise — you also find out that you can use self to send a message to your superclass and that self and super are not always interchangeable. In this case, you need to be careful to use super because the method sending the message has the same name as the method you want to invoke in your superclass. If you were to use self here, you would just send a message to yourself — the initWithAmount:: method in CashTransaction — which would turn around and send the same message to itself again, which would then send the same message to itself again, which would then . . . You get the picture. Fortunately, the OS will put up with this for only a limited amount of time before it gets really annoyed and terminates the program.

In this case, the superclass for the CashTransaction subclass is Transaction, and you invoke the Transaction superclass initialization method initWithAmount::. Your subclass should always invoke its superclass initialization method before doing any initialization. Your subclass's superclass is equally as respectful of its superclass and does the same thing; and up, up, and away you go from superclass to superclass until you reach the NSObject superclass init method. The NSObject superclass init method doesn't do anything; it just returns self. It's there to establish the naming convention, and all it does is return back to its invoker, which does its thing and then returns back to its invoker, until it gets back to you.

As you can see in Listing 3-2, Transaction invokes its superclass's init method as well. But in this case, it simply calls init (as per convention) because its superclass is NSObject.

Example 3-2. The Transaction Initializer

- (id) initWithAmount: (double) theAmount forBudget:
   (Budget*) aBudget {

 if (self = [super init]) {

    budget = aBudget;
    amount = theAmount;
    }
  return self;
}

Next, examine this unusual-looking statement:

if (self = [super initWithAmount:theAmount
  forBudget:aBudget]) {

Ignore the if for the moment. What you're doing is assigning what you got back from your superclass's init method to self. As you remember, self is the "hidden" variable accessible to methods in an object that points to its instance variables. (If you're unclear on this point, refer to the discussion in Chapter 1 of this minibook.) So it would seem that self should be whatever you got back from your allocation step. Well, yes and no. Most of the time, the answer is yes, but sometimes the answer is no, which may or may not be a big deal. So, check out the following bullet list and examine the possibilities.

When you invoke a superclass's initialization method, one of three things can happen:

  • You get back the object you expect. Most of the time, this is precisely what happens, and you go on your merry way. This is true for the classes described in this part of the book (those where you have control over the entire hierarchy), such as the Transaction class.

  • You get back a different object type. Getting back a different object type is something that can happen with some of the framework classes, but it's not an issue here. Even when it happens, if you're playing by the rules (a good idea if you're not the one who gets to make them), you don't even care.

    Why a different object type, you might ask? Well, some of the framework classes such as NSString are really class clusters. When you create an object of one of these classes, its initialization method looks at the arguments you're passing and returns the object it thinks you need (big brotherish to say the least, but effective nonetheless). Anything more about getting back different object types is way beyond the scope of this book.

    But, as I said, if you follow the rules, not only will you not notice getting back a different object type, you won't even care. In these cases, you can use the compound statement format — a sequence of statements surrounded by braces.

    SomeClass *aPointerToSomeClass =
                               [[SomeClass alloc] init];

    If you had done the following

    SomeClass *aPointerToSomeClass = [SomeClass alloc]
    [aPointerToSomeClass init];

    init could return a different pointer, which you haven't assigned to aPointerToSomeClass. This is also why the return type for an initializer needs to be id (a pointer to an object) and not the specific class you're dealing with.

  • You get nil back. One possibility, of course, is that you simply run out of memory or some catastrophe befalls the system, in which case, you're in deep trouble. Although there are some things you might be able to do, they aren't for the faint-hearted or beginners.

    Getting back nil actually explains the statement that seems so puzzling.

    if (self = [super initWithAmount:theAmount
       forBudget:aBudget]) {

    When nil is returned, two things happen here: self is assigned to nil, which as a side effect causes the if statement to be evaluated as NO. As a result, the code block that contains the statements you would have used to initialize your subclass are never executed.

Initializing instance variables

Initializing instance variables, including creating the objects you need, is what you probably thought initialization is about. Notice that you're initializing your instance variable after your superclass's initialization, which you can see in Listings 3-1 and 3-2. Waiting until after your superclass does its initialization gives you the opportunity to actually change something your superclass may have in initialization, but more importantly, it allows you to perform initialization knowing that what you have inherited is initialized and ready to be used.

In the CashTransaction initWithAmount:: initializer, all that's done is the initialization of the name instance variable of the superclass (Transaction) with the kind of transaction it is — cash, in this case.

name = @"Cash";

Returning self

In the section, "Invoking the superclass's init variable," the self = statement ensures that self is set to whatever object you get back from the superclass initializer. After the code block that initializes the variables, you put

return self;

Remember that if a method does not specify the type of its return value, the default is to return a pointer to an object (for example, something of id type). This means that, barring any other sensible thing to return, methods should always return self.

No matter what you get back from invoking the superclass initializer, in the initialization method you need to set self to that value and then return it to the invoking method. That could be a method that wants to instantiate the object or a subclass that invoked the init method (the init method being its superclass's init method).

When you're instantiating a new object, it behooves you to determine whether a return of nil is a nonfatal response to your request (and, if so, coding for it). In the examples in this book, the answer will always be no, and that will generally be the case with framework objects as well. In this example

theBudget = [[Budget alloc] initWithAmount:budgetAmount withE
   xchangeRate:theExchangeRate];

getting nil back would be more than your poor app could handle and would signal that you're in very deep trouble.

Listings 3-3 through 3-13 show how to implement initializers in the conventional way. You create the init... structure that enables you to more easily initialize any new instance variables you may add to existing classes, as well as ensure that you can do initialization correctly when you add any new superclasses or subclasses. (The lines in bold are changes to the code examples in Chapters 1 and 2 of this minibook.)

Example 3-3. Budget.h

#import <Cocoa/Cocoa.h>

@interface Budget : NSObject {

  float  exchangeRate;
  double budget;
  double transaction;
}
- (id) initWithAmount: (double) aBudget
               withExchangeRate: (double) anExchangeRate ;

- (void) spendDollars: (double) dollars ;

- (void) chargeForeignCurrency: (double) euros;
- (double) returnBalance;
@end

Example 3-4. Budget.m

#import "Budget.h"

@implementation Budget

 - (id) initWithAmount: (double) aBudget
                withExchangeRate: (double) anExchangeRate {

   if (self = [super init]) {
     exchangeRate = anExchangeRate;
     budget = aBudget;
   }
   return self;
}
- (void) spendDollars: (double) dollars {

  budget -= dollars;
}
- (void) chargeForeignCurrency: (double) foreignCurrency {

  transaction = foreignCurrency*exchangeRate;
  budget -= transaction;
}
- (double) returnBalance {

  return budget;
}
@end

Example 3-5. Transaction.h

#import <Cocoa/Cocoa.h>
@class Budget;
@interface Transaction : NSObject {

  Budget *budget;
  double amount;
NSString *name;
}
- (id) initWithAmount: (double) theAmount
                                        forBudget: (Budget*) aBudget;
- (void) spend;
@end

Example 3-6. Transaction.m

#import "Transaction.h"
#import "Budget.h"

@implementation Transaction

- (id) initWithAmount: (double) theAmount
                            forBudget: (Budget*) aBudget {
  if (self = [super init]) {
    budget = aBudget;
    amount = theAmount;
  }
  return self;
}
- (void) spend {
}
@end

Example 3-7. CashTransaction.h

#import <Cocoa/Cocoa.h>
#import "Transaction.h"

@interface CashTransaction : Transaction {

}

- (id) initWithAmount: (double) theAmount forBudget:
   (Budget*) aBudget ;
@end

Example 3-8. CashTransaction.m

#import "CashTransaction.h"
#import "Budget.h"

@implementation CashTransaction
- (id) initWithAmount: (double) theAmount forBudget:
   (Budget*) aBudget {

  if (self = [super initWithAmount:theAmount
   forBudget:aBudget]) {
    name = @"Cash";
  }
  return self;
}

- (void) spend {
  [budget spendDollars:amount];
}
@end

Example 3-9. CreditCardTransaction.h

#import <Cocoa/Cocoa.h>
#import "Transaction.h"

@interface CreditCardTransaction : Transaction {

}
- (id) initWithAmount: (double) theAmount forBudget:
   (Budget*) aBudget ;
@end

Example 3-10. CreditCardTransaction.m

#import "CreditCardTransaction.h"
#import "Budget.h"

@implementation CreditCardTransaction

- (id) initWithAmount: (double) theAmount forBudget:
   (Budget*) aBudget {

  if (self = [super initWithAmount:theAmount
   forBudget:aBudget]) {
    name = @"Credit card";
  }
  return self;
}
- (void) spend {
  [budget chargeForeignCurrency:amount];
}
@end

Example 3-11. Destination.h

#import <Cocoa/Cocoa.h>
@class Budget;

@interface Destination : NSObject {

  NSString       *country;
  double          exchangeRate;
  NSMutableArray *transactions;
  Budget         *theBudget;

}
- (id) initWithCountry: (NSString*) theCountry
                      andBudget: (double) budgetAmount
                      withExchangeRate:(double) theExchangeRate;
- (void) spendCash: (double) aTransaction;
- (void) chargeCreditCard:(double) aTransaction;
- (double) leftToSpend
@end

Example 3-12. Destination.m

#import "Destination.h"
#import "CashTransaction.h"
#import "CreditCardTransaction.h"
#import "Budget.h"
#import "Transaction.h"
@implementation Destination

- (id) initWithCountry: (NSString*) theCountry andBudget:
   (double) budgetAmount withExchangeRate: (double)
   theExchangeRate{
  if (self = [super init]) {
    transactions = [[NSMutableArray alloc]
   initWithCapacity:10];
theBudget = [[Budget alloc] initWithAmount:budgetAmount
             withExchangeRate:theExchangeRate];
              country = theCountry;
              NSLog (@"I'm off to %@", theCountry);
             }

             return self;
}

-(void) spendCash:(double)amount{

Transaction *aTransaction = [[CashTransaction alloc]
   initWithAmount:amount forBudget:theBudget];
  [transactions addObject:aTransaction];
  [aTransaction spend];
}
-(void) chargeCreditCard: (double) amount{

Transaction *aTransaction =
          [[CreditCardTransaction alloc]
   initWithAmount:amount forBudget:theBudget];
  [transactions addObject:aTransaction];
  [aTransaction spend];
}
- (double) leftToSpend {

  return [theBudget returnBalance];
}
@end

Example 3-13. main in Vacation.m

#import <Foundation/Foundation.h>
#import "Destination.h"

int main (int argc, const char * argv[]) {

  NSString* europeText = [[NSString alloc]
   initWithFormat:@"%@", @"Europe"];
  Destination* europe = [[Destination alloc]
   initWithCountry:europeText andBudget:1000.00
   withExchangeRate:1.25];

  NSString* englandText = [[NSString alloc]
                       initWithFormat:@"%@", @"England"];

  Destination* england = [[Destination alloc]
   initWithCountry:englandText andBudget:2000.00
   withExchangeRate:1.50];
for (int n = 1; n < 2; n++) {
      double transaction = n*100.00;
      NSLog (@"Sending a %.2f cash transaction", transaction);
      [europe spendCash:transaction];
      NSLog(@"Remaining budget %.2f", [europe leftToSpend]);
      NSLog (@"Sending a %.2f cash transaction", transaction);
      [england spendCash:transaction];
      NSLog(@"Remaining budget %.2f", [england leftToSpend]);
    }

    int n = 1;
    while (n < 4) {
      double transaction = n*100.00;
      NSLog(@"Sending a %.2f credit card transaction",
     transaction);
      [europe chargeCreditCard:transaction];
      NSLog(@"Remaining budget %.2f", [europe leftToSpend]);
      NSLog(@"Sending a %.2f credit card transaction",
     transaction);
      [england chargeCreditCard:transaction];
      NSLog(@"Remaining budget %.2f", [england leftToSpend]);
      n++;
    }
    return 0;
}

Raising and Terminating Responsible Objects

You have given birth to an object by allocating memory for it, and (to push the metaphor to its limits) you've raised it to maturity by initializing it — setting its instance variables to a known initial state. What about retirement (and dare I say it, termination)? As you give birth to more objects, you allocate more memory, until at some point (population explosion?) you run out of memory.

What with everything else going on in prepping your app for rollout, managing memory can be a real challenge not only to someone new to programming, but also to those people with many lines of code under their belts.

Allocating memory when you actually need it isn't that hard. It's realizing you don't need an object anymore and then releasing the memory back to the operating system that can be a challenge. If you don't do that, and your program runs long enough, eventually you run out of memory (see the upcoming sidebar, "The iPhone memory challenge") and your program comes crashing down. Long before that, you may even notice system performance approaching "molasses in February, outdoors in Hibbing, Minnesota" levels. Oh, and by the way, if you do free an object (memory) and that object is still being used, you'll have the "London Bridge Is Falling Down" effect as well.

If you've created a giant application and run out of memory while all the objects you created are being used, that's one issue (beyond the scope of this book). But if you run out of memory because you have all these objects floating around that no one is using, that's another thing, and it's known as a memory leak.

Note

Keeping tabs of your app's memory needs — a process known as memory management — isn't really that hard if you understand how it all works, which also isn't that hard if you pay attention to it. In addition, Xcode can help you track down memory problems. I show you how to use Xcode's memory management features in the "Running the Static Analyzer" section, later in this chapter. The problem is that sometimes, in the rush to develop an application and see things happen on the screen, programmers ignore their memory management issues for the moment, telling themselves that they'll come back later to do it right. Trust me, this strategy does not lead to happy and healthy applications or application developers.

Understanding the object lifecycle

In Chapter 1 of this minibook, you found out how to allocate and initialize objects using a combination of alloc and init. Many objects you allocate stay around for the duration of your program, and for those objects, all you have to do is, well, nothing really. When your program terminates, they are de-allocated, and the memory is returned to the operating system.

But some objects you use for a while to get your money's worth out of them and then . . . you're done with them. When you're done with them, you should return the memory allocated to them back to the OS so it can allocate that memory for new objects. If you don't, you may cause problems.

Start by looking at how memory management works. In Objective-C 2.0 (as opposed to earlier versions), you can manage memory for iPhone apps by reference counting — keeping the system up to date on whether an object is currently being used. Read on to find out more.

Using reference counting

In many ways, Objective-C is like the coolest guy in your school, who now makes a seven-figure income bungee jumping and skateboarding during the summers, while snowboarding around the world in the winter.

In other ways, though, Objective-C is like the nerd in your class, who grew up to be an accountant and reads the Financial Times for fun. Memory management falls into this category.

In fact, memory management is simply an exercise in counting. To manage its memory, Objective-C (actually Cocoa) uses a technique known as reference counting. Every object has its own reference count, or retain count.

When an object is created via alloc or new — or through a copy message, which creates a copy of an object but has some subtleties beyond the scope of this book — the object's retain count is set to 1. As long as the retain count is greater than zero, the memory manager assumes that someone cares about that object and leaves it alone.

It's your responsibility to maintain that reference count by directly or indirectly increasing the retain count when you're using an object and then decreasing it when you're finished with it. When the retain count goes to zero, the runtime system (Cocoa) assumes that no one needs it anymore. Cocoa automatically sends the object a dealloc message, and after that, its memory is returned to the system to be reused.

Take a look at an example: In Vacation.m, you create a string object and then pass that as an argument into the init method when you create the Destination object, as shown here:

NSString* englandText = [[NSString alloc]
                      initWithFormat:@"%@", @"England"];
Destination* england = [[Destination alloc]
   initWithCountry:englandText andBudget:2000.00
   withExchangeRate:1.50];

The Destination object sticks around until the program is terminated. At that point, everything gets de-allocated, so there is really no problem and no real (although some potential) memory management issues.

But what happens if you decide sometime along the way on your trip not to go to England after all. You really have always wanted to go to Antarctica, and an opportunity to hitch a ride on a rock star's private jet presents itself, so bye-bye England, and hello Ushuaia, Tierra del Fuego, Argentina.

Before you take off, however, you want to do one thing — besides send for your long underwear. You need to delete England as a destination, freeing up that budget money, and create a new destination — Antarctica.

Note

When you're doing memory management, it is your responsibility to keep the runtime system informed about your use of objects, so if you don't need an object any longer, you send it a release message.

[england release];

release does not de-allocate the object!

Note

Let me say that again — release does not de-allocate the object!

All release does is decrement the retain count by 1. This is very important to keep in mind because while one method or object in your application may no longer need an object, it still may be needed by another method or object in your program. That's why you don't dealloc it yourself, and instead you need to trust the runtime system to manage the retain count for you. But it is your job to keep the runtime system informed of your object by using the release message.

Well that's cool, and being a good citizen, the england object wants to release all its objects in its dealloc method. No problem here, one would think. Destination has instance variables pointing to the objects it uses:

NSString* country;
double exchangeRate;
NSMutableArray *transactions;
Budget* theBudget;

So, in the dealloc method that is invoked before the Destination object is de-allocated by the OS, those other objects can be released.

- (void) dealloc {

  [transactions release];
  [country release];
  [theBudget release];
  [super dealloc];
}

Note

Normally you don't send the dealloc message to the object directly; you send the object the release message and let the runtime system de-allocate it automatically. The exception is when you're invoking the superclass's implementation in a custom dealloc method (as in super dealloc) — you need to send your superclass a dealloc message after you release the objects that you need to release in the subclass.

Although you don't have to release the exchangeRate because it is not an object, do you really want to release all those other objects? What if other objects in your program still need to use those objects? Actually, taking that into account is very easy, as long as you follow the rules.

As I mention earlier, when you create an object using alloc or new, or through a copy message, the object's retain count is set to 1. So you're cool. In fact, whenever you create an object like that, your solemn responsibility is to release it when you are done. There is a flip side to this coin, however; if you're using an object, a pointer to it is sent to you as an argument in a message, as is the case for the NSString object in the following:

Destination* england = [[Destination alloc]
   initWithCountry:englandText andBudget:2000.00
   withExchangeRate:1.50];

Then it's also your responsibility to increment the retain count by sending it the retain message, as you can see in the bolded section of the implementation of the initWithCountry::: method:

- (id) initWithCountry: (NSString*) theCountry andBudget:
   (double) budgetAmount withExchangeRate: (double)
   theExchangeRate{
  if (self = [super init]) {
    transactions = [[NSMutableArray alloc]
                                     initWithCapacity:10];
    theBudget = [[Budget alloc]
                       initWithAmount:budgetAmount
                       withExchangeRate:theExchangeRate];
    exchangeRate = theExchangeRate;
    country = theCountry;
    [country retain];
  }
  return self;
}

In this method, the Destination object creates two objects on its own, theBudget and transactions. As a result, the retain count for each is set to 1. It also gets passed a pointer to an NSString object that was created at another time and place. If Destination plans to use that object, it needs to send it the retain message. That way, the retain count is increased by 1. If the creator of that object decides it no longer needs the object and sends it the release message, the retain count is decremented by 1. But because the Destination object sent it a retain message, the release count is still greater than 0 — the object lives!

In fact, that is exactly what happens. In main, after the object is created and sent as an argument to the Destination objects, the good little code releases the object because it really has no need for the object. When you do release an object in your code, you're counting on the fact that other objects are playing according to the rules, and the receiving object increases the retain count if it needs to continue to use an object you created. This frees the creator of an object from the responsibility of having to know anything about who's using an object it has created and worrying about when it has to free the object.

In the code in main, the string object sent in the initWithCountry::: message is released after the message is sent because the code in main has no further use for the string object it created. (Note the bolded code in the following block.)

NSString* englandText = [[NSString alloc]
   initWithFormat:@"%@", @"England"];
  Destination* england = [[Destination alloc]
   initWithCountry:englandText andBudget:2000.00
   withExchangeRate:1.50];
  [englandText release];

europeText is released as well. All's right with the world.

Tip

What really confuses some developers is the concept of retain and release. They worry that releasing an object will de-allocate that object. (Note that all release does is tell the memory manager that you're no longer interested in it. The memory manager is the one that makes the life-and-death decision.) New developers sometimes worry that, as a creator, they have to be concerned about others using their objects. In reality, it's your job to simply follow the memory management rules.

Here's the fundamental rule of memory management:

You're responsible for releasing any object that you create using a method whose name begins with alloc or new or contains copy, or if you send it a retain message. In Applespeak, if you do any of these things, you're said to own the object. (Objects are allowed to have more than one owner — talk about how to use terminology to really make things confusing).You can release an object by sending it a release or autorelease message (which I explain shortly).

That's it, with corollaries of course.

If you want to continue to use an object outside the method it was received in, save a pointer to it as an instance variable, as you did previously with the NSString object. Then you must send the object the retain message. In Applespeak, that means you are now an owner of the object.

In general, somewhere in your code there should be a release for every statement that creates an object using alloc or new, or contains copy or sends a retain message.

Running the Static Analyzer

Until the release of Xcode 3.2, you had to track down memory leaks by using the Instruments application (which I cover in Book IV).

But Xcode also has a new Build and Analyze feature (the Static Analyzer) that analyzes your code. It is especially good at detecting certain kinds of memory leaks — especially ones where you create an object and then pass it to another object, and then forget to release it.

When you run the Static Analyzer, the results show up like warnings and errors, with explanations of where (and what) the issue is. You can also see the flow of control of the (potential) problem. I say potential because the Static Analyzer can give you false positives. Here's how to put Static Analyzer through its paces:

  1. Choose Build

    Running the Static Analyzer

    The status bar in the Xcode Project window tells you all about build progress, build errors such as compiler errors, or warnings.

  2. Choose Build

    Running the Static Analyzer

    The Build Results window appears. Line numbers appear in the text because I chose that option in the Editing pane of Xcode's Preferences window.

    You see four potential memory leaks in the Build Results window, as shown in Figure 3-1: two in Vacation.m and two in Destination.m.

    Build results for the Static Analyzer.

    Figure 3-1. Build results for the Static Analyzer.

  3. Return to the Project Window, which shows the Static Analyzer Results in the Text Editor (as shown in Figure 3-2).

    While you can work in either the Build Results window or the Text Editor, I prefer working in the Text Editor.

  4. Click the first error message (right after Line 13).

    Figure 3-3 shows how you got into this predicament. The object you created on Line 11, europeText, is no longer referenced after Line 12, when you use it as an argument in initWithCountry::. It still has a retain count of 1, so even if all the other objects that use it do release it, it continues to take up precious memory, even though it isn't being used in main, for the simple reason that it hasn't been released.

  5. Open the Destination.m file.

    When you look at Destination.m, you see the same sorry story. Figure 3-4 warns you of a potential leak.

  6. Click the error message on Line 28.

    Figure 3-5 shows that the Transaction object you created on Line 25 is never referenced after you send it the spend: message and add it to the transactions array.

    The Static Analyzer results in the Project Window.

    Figure 3-2. The Static Analyzer results in the Project Window.

    The text objects are no longer referenced.

    Figure 3-3. The text objects are no longer referenced.

    A potential leak in Destination. m.

    Figure 3-4. A potential leak in Destination. m.

    A lonely Transaction.

    Figure 3-5. A lonely Transaction.

Plugging the Leaks

To plug the leaks Xcode's Static Analyzer has so kindly pointed out to you, add the code shown in bold in Listings 3-14 through 3-19 to Budget.m, Transaction.m, CashTransaction.m, CreditCardTransaction.m, Destination.m, and main in Vacation.m.

Example 3-14. Budget.m

#import "Budget.h"

@implementation Budget

- (id) initWithAmount: (double) aBudget withExchangeRate:
   (double) anExchangeRate {

  if (self = [super init]) {
    exchangeRate = anExchangeRate;
    budget = aBudget;
  }
  return self;
}
- (void) spendDollars: (double) dollars {

  budget -= dollars;
}
- (void) chargeForeignCurrency: (double) foreignCurrency {
  transaction = foreignCurrency*exchangeRate;
  budget -= transaction;
}
- (double) returnBalance {

  return budget;
}

- (void) dealloc {

  [super dealloc];
}
@end

Example 3-15. Transaction.m

#import "Transaction.h"
#import "Budget.h"

@implementation Transaction

- (void) spend {
}

- (id) initWithAmount: (double) theAmount forBudget:
   (Budget*) aBudget {
  if (self = [super init]) {
    budget = aBudget;
    [budget retain];
    amount = theAmount;
  }
   return self;
}
- (void) dealloc {

  [budget release];
  [super dealloc];
}
@end

Example 3-16. CashTransaction.m

#import "CashTransaction.h"
#import "Budget.h"

@implementation CashTransaction
- (id) initWithAmount: (double) theAmount forBudget:
   (Budget*) aBudget  {

  if (self = [super initWithAmount:theAmount
   forBudget:aBudget]) {
    name = @"Cash";
  }
  return self;
}
- (void) spend {
  [budget spendDollars:amount];
}
- (void) dealloc {

  [super dealloc];
}
@end

Example 3-17. CreditCardTransaction.m

#import "CreditCardTransaction.h"
#import "Budget.h"

@implementation CreditCardTransaction

- (id) initWithAmount: (double) theAmount forBudget:
   (Budget*) aBudget {

  if (self = [super initWithAmount: theAmount forBudget:
   aBudget]) {
    name = @"Credit Card";
  }
    return self;
}
- (void) spend {

  [budget chargeForeignCurrency:amount];
}
- (void) dealloc {

[super dealloc];
}
@end

Example 3-18. Destination.m

#import "Destination.h"
#import "CashTransaction.h"
#import "CreditCardTransaction.h"
#import "Budget.h"
#import "Transaction.h"

@implementation Destination

- (id) initWithCountry: (NSString*) theCountry andBudget:
   (double) budgetAmount withExchangeRate: (double)
   theExchangeRate{
  if (self = [super init]) {
    transactions = [[NSMutableArray alloc]
   initWithCapacity:10];
    theBudget = [[Budget alloc]  initWithAmount:budgetAmount
   withExchangeRate:theExchangeRate];
    exchangeRate = theExchangeRate;
    country = theCountry;
    [country retain];
    NSLog(@"I'm off to %@", theCountry);
  }
  return self;
}
- (void) updateExchangeRate: (double) newExchangeRate {

exchangeRate = newExchangeRate;
}
- (void) spendCash: (double)amount {

Transaction *aTransaction = [[CashTransaction alloc]
   initWithAmount:amount forBudget:theBudget ];
[transactions addObject:aTransaction];
[aTransaction spend];
[aTransaction release];
}
- (void) chargeCreditCard: (double) amount {

Transaction *aTransaction = [[CreditCardTransaction alloc]
   initWithAmount:amount forBudget:theBudget ];
[transactions addObject:aTransaction];
[aTransaction spend];
[aTransaction release];
}
- (double ) leftToSpend {

return  [theBudget returnBalance];
}
- (void) dealloc {

[transactions release];
[theBudget release];
[country release];
[super dealloc];
}
@end

Example 3-19. main in Vacation.m

#import <Foundation/Foundation.h>
#import "Destination.h"

int main (int argc, const char * argv[]) {

  NSString* europeText = [[NSString alloc]
   initWithFormat:@"%@", @"Europe"];
  Destination* europe = [[Destination alloc]
   initWithCountry:europeText andBudget:1000.00
   withExchangeRate:1.25];
  [europeText release];
  NSString* englandText = [[NSString alloc]
   initWithFormat:@"%@", @"England"];
  Destination* england = [[Destination alloc]
   initWithCountry:englandText andBudget:2000.00
   withExchangeRate:1.50];
  [englandText release];

  for (int n = 1; n <  2; n++) {
    double transaction = n*100.00;
    NSLog (@"Sending a %.2f cash transaction", transaction);
    [europe spendCash:transaction];

    NSLog(@"Remaining budget %.2f", [europe leftToSpend]);
    NSLog(@"Sending a %.2f cash transaction", transaction);
    [england spendCash:transaction];
    NSLog(@"Remaining budget %.2f", [england leftToSpend]);
  }

  int n = 1;
  while (n < 4) {
    double transaction = n*100.00;
    NSLog (@"Sending a %.2f credit card transaction",
   transaction);
    [europe chargeCreditCard:transaction];
    NSLog(@"Remaining budget %.2f", [europe leftToSpend]);
    NSLog (@"Sending a %.2f credit card transaction",
   transaction);
    [england chargeCreditCard:transaction];
    NSLog(@"Remaining budget %.2f", [england leftToSpend]);
    n++;
  }

  [england release];

  return 0;
  }

To fix the problems discovered by the Static Analyzer, you need to release aTransaction in the spendCash: and chargeCreditCard: methods in Destination.m (as shown earlier in Listing 3-18). You also need to release europeText and englandText in main (as shown in Listing 3-19).

Notice that in the Destination methods cashTransaction: and CreditCardTransaction:, you release the Transaction object when you're done with it. A bit later on in this chapter, the section "Considering objects in arrays" explains why that's safe, even though you've added it to the array.

Although the Static Analyzer is a giant step forward, it can't catch everything. You still need to be methodical about releasing objects for which you've increased the retain count in the Transaction and Destination objects' dealloc methods.

Two comments about the dealloc methods. First, as you can see, you need to send your superclass a dealloc message after you release the objects that you need to release in the subclass. Remember, the object that creates an object or retains the object needs to release it, so you may find yourself releasing the same object in both a subclass's and a superclass's dealloc method. That's fine, as long as the object was created or retained by the class that releases it.

You also add dealloc methods for those classes that (presently) do not have any objects they need to release when they are de-allocated. You do that to keep focused on how important it is to release objects. In fact, in the file templates that you use for iPhone classes, when you create a new class file that's derived from anything other than NSObject, the template has a default dealloc method that just invokes its superclass's dealloc method.

Tip

One final point: If you have a dealloc method that does release objects, when its superclass is NSObject, you really don't need to invoke it from dealloc. It is, however, not a bad habit to always invoke your superclass's dealloc method. This keeps you from getting into trouble when you factor your code — separate sections with different responsibilities into different objects. You may find yourself creating a new superclass for a class that previously was based on NSObject, and always invoking its superclass's dealloc method keeps you from having to remember to add the code to invoke it in your (now) subclass's dealloc method.

The program still functions the same way as it did before you made the changes, which underlies why it's so easy to postpone doing memory management until you need it. But while it doesn't seem to add any (observable) functionality early on, correctly managing memory saves you many hours of anguish later when your program expands to the point where memory becomes an issue, which (too) often happens much sooner than you might expect.

Note

If you want to trace the de-allocation process, put an NSLog statement in your dealloc method to see when objects are being de-allocated. You can also send an object the retainCount message to find out its current retain count. (It returns an unsigned int.)

Attending to Memory Management Subtleties — Arrays and Autorelease

Although memory management is generally straightforward, there are a few subtleties that may not be so obvious — only a few mind you, but they're important. The following (two) subtleties deserve special attention:

  • Objects in arrays

  • Autorelease and the autorelease pool

Considering objects in arrays

Look at the dealloc method in Destination.m:

- (void) dealloc {

  [transactions release];
  [theBudget release];
  [country release];
  [super dealloc];
}

Notice you release the transactions array. What happens to all the objects you added to that particular array? As you might expect, the rules are that if you want to use an object, you must send it a retain message, and if you do, then at some point you must release it. The array follows those rules, and when you add an object to an array, the array object sends the object that was just added a retain message. When the array is de-allocated, it sends release messages to all its objects. If you want to use the object after the array is de-allocated, you need to send it a retain message before the array is de-allocated.

In addition, if you remove an object from a mutable array — which is the only kind that you can add and remove objects from (refer to Chapter 4 of this minibook for more on this topic) — the object that has been removed receives a release message. So, if an array is the only owner of an object, then (by standard rules of memory management) the object is de-allocated when it's removed. If you want to use the object after its removal, you need to send it a retain message before you remove it from the array.

Understanding autorelease

At the end of the discussion of using accessors to get data from objects in Chapter 1 of this minibook, you used the following code:

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  // insert code here...

[pool drain];

This code creates an autorelease pool that is a way to manage memory for objects when it is not possible for the object creator to easily release them.

The memory management rules require you to release objects when you're done with them, and often that's pretty easy, as shown in the following example:

NSString* englandText = [[NSString alloc]
  initWithFormat:@"%@", @"England"];

Destination* england = [[Destination alloc]
  initWithCountry:englandText andBudget:2000.00
  withExchangeRate:1.50];

  [englandText release];

In main, the string object is created and then used as an argument in the Destination object's initWithCountry::: method. After control is returned to main, you can safely release that object because, as far as you are concerned, you are done with it; if Destination needs it, well, it's the Destination object's responsibility to retain it. But what about those circumstances where the creator never gets control back? For example, what if you were to create a new method called returnCountry that created a copy of the country string and returned it back to the invoker?

- (NSString*) returnCountry {

  return [country copy];
}

You might want to do that if the receiver could possibly modify it. The problem here is that control is never returned back to returnCountry, so returnCountry never has a chance to release the copy it made.

To deal with the problem of control never being returned to a creator of an object so the creator can release it, Cocoa has the concept of an autorelease pool, and the statement

NSAutoreleasePool * pool =
                        [[NSAutoreleasePool alloc] init];

creates one of those pools to be used by main. The pool is nothing more than a collection of objects that will be released sometime in the future. When you send autorelease to an object, the object is added to an NSAutoreleasePool. When that pool is "cleared" (which happens on a regular basis), all the objects in the pool are sent a release message.

Note

As glamorous as it sounds, the autorelease pool is just an array, and knowing what you know, you could write and manage one yourself, but why bother?

So, you can now write a returnCountry method that manages memory correctly.

- (NSString*) returnCountry {

  return [[country copy] autorelease];
}

Now, memory management works just right because the returnCountry method creates a new string, autoreleases it, and returns it to the object that requested it. If that object wants to continue to use the string, that object has to send a retain message to the string because the string gets a release message in the future.

So when is that release message sent? If you're using an AppKit or UIKit application, the release message is sent in the main event loop that you return to after your program handles the current event, such as a mouse click or touch. With a Foundation Command Line Tool, the release message is sent when you destroy or drain the pool.

[pool drain];

That's as far as I'm going with how the autorelease pool works — anything else is beyond the scope of this book. Besides, I assume that you're using Cocoa (the runtime system) for your application, which automatically takes care of managing the autorelease pool for you — both creating the pool and releasing it periodically.

Using the autorelease pool

You want to avoid using the autorelease pool on the iPhone when possible. The memory allocated for an autoreleased object remains allocated for some period of time after you're done with it and can be an issue in more memory-intensive applications. But autorelease could be used "behind your back" at times.

For example, Objective-C has a concept called class methods. This method belongs to the class object (as opposed to an instance object of the class), and class methods used to create new instances are called factory or convenience methods. The objects it creates are autoreleased. The ones you probably will be most concerned with are in the NSString class (although there are more, even in the NSMutableArray class), such as the following:

stringWithContentsOfFile
stringWithContentsOfURL
stringWithCString
stringWithFormat:
stringWithString:

So, instead of using

NSString *newText = [NSString stringWithFormat:
@"Yo ", name];

You're using

NSString *newText = [[NSString alloc] initWithFormat:
@"Yo ", name];

and doing the release yourself.

Notice these methods are of the form stringWith, as opposed to init.... This naming convention is a handy way to differentiate a class method that uses autorelease from the init methods that use alloc.

If you do need to continue to use an autoreleased object, just like with any other object you receive, you need to send it a retain message. In doing so, you become responsible for managing that object, and you must send a release message at some point.

Warning

In iPhone programming, Apple recommends that you avoid using autorelease in your own code and that you also avoid class methods that return autoreleased objects. As I mention earlier, the memory allocated for an autoreleased object remains allocated for some period of time after you're done with it and can be an issue in more memory-intensive applications. This book doesn't cover these class methods, although you can find many examples of them being used.

These methods occur most commonly when creating an object using a class method, which saves you the trouble of doing an alloc, an init..., and then a release for the object. If you look in the documentation, as illustrated in Figure 3-6, these are under the heading Class Methods. They all have a + instead of a before the return type, which designates them as a class method.

In Figure 3-6, you can see the NSString Class reference. In the Table of Contents, I expanded the disclosure triangle next to Class Methods and then clicked the stringWithFormat: class method, the counterpart to the initWithFormat: instance method. You can see the + in front of the method declaration.

Class methods.

Figure 3-6. Class methods.

Notice that for class methods like these, instead of having their names start with init (for example, initWithFormat: for an NSString), they start with a reference to the class name (stringWithFormat:, for example).

Some Basic Memory Management Rules You Shouldn't Forget

It all comes down to one simple rule:

If you do anything to increase the retain count of an object, it's your responsibility to decrease the retain count by the same amount when you're no longer going to send messages to that object.

That's it. Of course, the wisdom lies in knowing when you've increased the retain count, and when you need to decrease it.

  • You automatically increase the retain count whenever you create an object using alloc or new or any method that contains copy.

  • You should decrease the retain count by sending an object a release message when you no longer need to send the object any messages.

  • Assume that any object you receive whose creation you didn't personally witness dies as soon as you turn your back. It may have been passed as an argument, for example, or perhaps you're using one of those class convenience methods I spoke of earlier — you know, the ones you really shouldn't use on the iPhone.

  • As shown in the section on releasing an object assigned to a property in Chapter 1 of this minibook, assigning an instance variable with a property attribute of retain is the moral equivalent of sending the object the retain message yourself.

At the end of the day, the number of alloc, new, copy, and retain messages should equal (not be close to, equal) the number of release messages.

Tip

Do not make yourself crazy wondering about what is going on outside the little world of your program. If you follow the rules in every object, things work out correctly. This is one of the few times when everyone acting in his or her best interest always works in the best interest of the whole.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.125.169