Objective-C Lesson 10: Memory Management


Memory on the iPhone is limited. Although each new generation of hardware has increased the amount of physical memory in the device, applications are also taking more memory, and with the introduction of background apps in iOS 4, memory remains a resource to be conserved. iOS automatically quits applications that use too much memory; therefore it’s important to be aware of your memory usage and avoid leaking, which will increase your memory usage, or the equally annoying early release of elements, which will cause your app to crash. These issues are handled by the system in a garbage-collected environment, where the system will automatically release memory as needed, but this adds an overhead and is not available on iOS. On a similar topic, note that iOS does not have a paging system—data cannot be paged out to disk as needed. You can read and write data to disk, but this is not recommended (or even possible) for any object—only for data files, and even then sparingly, because disk access is inevitably slower than memory access.

Reference Counting

Cocoa’s memory management scheme is based off a reference counting system. It’s a simple concept—every object has a value, known as a reference count. When the object is created, the count is set to 1. Certain actions will increment or decrement this count, and when the count hits zero, the object is immediately freed from memory, at which point you cannot access it again.

Messages

Reference counting principally involves three methods—retain, release, and dealloc. The first two change the reference count; the latter frees up the memory of the objects. You generally override the dealloc method, as we have been doing in our classes, to free up the memory occupied by the class’s instance variables (usually by calling their dealloc method). Finally, the method always includes a call to super‘s dealloc method, which invariably calls NSObject‘s dealloc method. It is this specific method that frees up the memory; if you neglect to call this implementation, you get a compiler warning. Also note that you should always override this method, but you should never call it specifically—the system will call that method for you as necessary.

The retain method increments the retain count by one. It is inherited by all classes, and so can be called on any object, any number of times, at any time. The release method decrements the retain count by one.

Keeping track of the retain count requires some diligence on the part of the programmer. If you under-release an object, the memory will be leaked; if you over-release, you crash.

Memory Management Rules

The rules are simple, really.

  • If the method name has init, copy, or new anywhere in the method name (such as initWithName:, mutableCopy, or newCar), the method implies that the object will have been retained, and it is your responsibility to release that object later This also means that you’ll need to store that object in a variable, to be able to release it again.
    NSMutableArray *ma = [someArray mutableCopy];
    myMutableArray = ma;
    [ma release];
  • Any other method name, if the method returns an object, will be autoreleased.

All of Apple’s methods follow these rules, and yours should too.

Memory Management with Properties

When you create a property, you can specify certain parameters that the setters follow. Of note is the retain and copy parameters. Most object properties are declared as retain, which generates a setter like so:

-(void) setText:(NSString *)textValue {
    if (![textValue isEqualToString: text]) {
        [textValue retain];
        [text release];
        text = textValue;
    }
}

This value is retained, and as such you have to balance it out with a release in the dealloc method. The copy parameter does something similar, except that instead of retaining the new value, it copys it.

One thing to point out, as noted by a comment on last week’s post, is that in general you do not want to retain delegate objects. Otherwise, you’ll get a recursive memory coupling. For example, a table view’s delegate is generally the view controller which owns the table view itself. Therefore, if the delegate property retained the delegate, the table view would own its delegate, which would own the table view—you couldn’t release any one of them because they own each other. Make sure to avoid this issue.

Autorelease

The autorelease pool is a medium between directly managing memory, and garbage collection. A lot of methods return autoreleased objects—these objects are added to an autorelease pool, which is created in every program. When the drain method is sent to the pool, all of the objects within are sent a release method. This can be useful in a tight loop:

NSAutoreleasePool *tempPool;
for (i = 0; i < n; i++) {
     tempPool = [[NSAutoreleasePool alloc] init];
     // Create lots of temporary autoreleased objects that take a lot of memory
     [tempPool drain];
}

To autorelease an object, simply send the autorelease method.

Collection Classes

Collection classes, including NSArray, NSDictionary, and NSSet, retain all of the objects that you put into them. When the collection class is deallocated, all of the members in the class are also released.

On any platform, specifically the memory-constrained iOS platform, memory management is an important topic to keep in mind. It requires diligence on the part of the programmer—but the alternative is slow performance and/or crashes, both of which must be avoided. Following the simple rules is the fool-proof way to keep track of your memory management.

Objects (Part 2): Properties


Every object is made up of instance variables (iVars) and methods. Our Fraction class, which we really began to build in the last post, contains two iVars, both NSIntegers, called numerator and denominator. Fraction also has two methods, setNumerator: and setDenominator:. These methods will set a value to the corresponding instance variable. In most cases, you would also have a method, with the same name as the instance variable (yes, this is permitted, and in this case, encouraged), which would return the value of the instance variable. These setter and getter methods are referred to as mutator and accessor methods, respectively. These methods are a major concept of object-oriented programming—data encapsulation, the notion that objects hide their instance variables.

At this moment, our Fraction class defines and implements the setter methods (note that we simply have not needed the getter methods so far). However, this would be difficult to do when we start working with large classes, which have many instance variables. This is also not very scalable; the same code to retrieve values for a small block of data stored on an iPhone should probably not be the same code used to retrieve a long string of data from a powerful desktop Mac or even from the internet. Fortunately, there is a very convenient concept that Objective-C provides, and this construct should be used wherever possible.

Properties

Properties replace the accessor methods for an object. Quite simply, our Fraction class’s interface now looks like this:

#import 
@interface Fraction : NSObject { 
	NSInteger numerator; 
	NSInteger denominator; 
}
@property NSInteger numerator; 
@property NSInteger denominator;
- (void)setNumerator:(NSInteger)value; 
- (void)setDenominator:(NSInteger)value;
- (void)display; 
@end

The general form of the property statement is

@property type name

Note that the setter (and if we had them, the getter) methods are no longer needed. We will have the compiler synthesize these methods for us, but placing this line in our implementation, right after the @implementation line:

#import "Fraction.h" @implementation Fraction
@synthesize numerator, denominator 
- (void)setNumerator:(NSInteger)value { 
    numerator = value; 
} 
- (void)setDenominator:(NSInteger)value {
    denominator = value; 
} 
- (void)display { 
    NSString *numeratorString = [[NSString alloc] initWithFormat:@"%d", self.numerator]; 
    NSString *denominatorString = [[NSString alloc] initWithFormat:@"%d", self.denominator]; 
    NSLog(@"%@/%@", numeratorString, denominatorString); 
    [denominatorString release]; 
    [numeratorString release]; 
}
@end 

Of course, the identifiers after the @synthesize have to match the names you declared as properties in the interface. However, the identifiers that you declared after @property do not have the match the instance variables; instance variables will be created at compile time if this happens.

You do not have to change your method calls in your main() routine; the methods still exist. However, there is an easier (and sometimes controversial) way to access these synthesized properties.

The Dot Operator

As previously mentioned, a getter method returns the value of an iVar; a setter sets a value to the iVar.

[myFraction setNumerator: 2] // Set numerator to 2 NSInteger 
numeratorValue = [myFraction numerator]; // returns the value of the numerator, and set to the new variable called numeratorValue

The dot operator, introduced in Objective-C 2.0 (yes, there was a 1.0, but that was a long time ago), allows you to invoke the getter method by writing

iVar.property

and the setter by

iVar.property = value

Therefore, the above example can be re-written as

myFraction.numerator = 2; // Set numerator to 2 
NSInteger numeratorValue = myFraction.numerator; // set value of numerator to numeratorValue 

C programmers might recognize this syntax as being used to access members of a struct—because that is exactly what you are doing. Behind the scenes, an object’s instance variables are stored as a struct. And if you have no idea what this line means, don’t worry—structs are a basic C data type that we may cover in a later lesson.

Property Attributes

In a property declaration, you can specify any number of (non-contradictory) attributes:

@property (attribute1, attribute2, etc.) type name;

A list of possible attributes follows (adapted from Apple’s developer documentation, The Objective-C Programming Language: Declared Properties)

Accessor Method Names

The default names for the getter and setter methods associated with a property are propertyName and setPropertyName: respectively—for example, given a property “foo”, the accessors would be foo and setFoo:. The following attributes allow you to specify custom names instead. They are both optional and may appear with any other attribute (except for readonly in the case of setter=).

  • getter=getterName: Specifies the name of a different (custom) getter method for the property. The method must return a value matching the iVar’s type, and it takes no arguments.
  • setter=setterName: Specifies the name of a different (custom) setter method for the property. The method must return void, and it takes one argument of the same type as the iVar.If you specify that a property is readonly then also specify a setter with setter=, you will get a compiler warning.

Writability

These attributes specify whether or not a property has an associated set accessor. They are mutually exclusive.

  • readwrite: This is the default; this indicates that the property will have read and write capabilities. When synthesized, both the getter and setter will be generated.
  • readonly This indicates that the property can only be read; no data can be assigned to it (through the property). When synthesized, only a getter will be generated; when you try to assign the property a value using dot syntax, a compiler error will be generated.

Setter Semantics

These attributes specify the semantics of the setter. They are mutually exclusive.

  • assign: This is the default; it tells the setter to directly assign the value. This is typically delineated with primitive data types (ints, floats, etc.)
  • retain: Specifies that a retain should be called on the object upon assignment; the previous value is released. This is only valid for objects. This has to do with memory management, a topic that will be fully explored in a later lesson.
  • copy: Specifies that the argument passed in should be copied, and the copy will be assigned. The previous value is released. This is only valid for objects. It is used in the case where the original property (perhaps stored on a shared server across the network) should not be modified by any other code, but when other code requests the value, for potential modification.

Atomicity

Atomicity means that the value is written to a temporary location in memory first, and then written to the intended location. This ensures that there is always a valid copy of the data somewhere. If an assignment was not atomic, and something happened during the writing period, the data could become fatally corrupt—the user would lose data. By writing data atomically, the data will either be the original, or the new value—never in a partially written state.

  • nonatomic: Specifies that the object should not be assigned atomically. By default, accessors are atomic (this is more beneficial in a multi-threaded environment, where multiple threads may be reading/writing value to a piece of memory.

CS193P Material

Properties are a very powerful notion, and used throughout the language. If this explanation was not clear enough, please see Lecture 3 of CS193P. The relevant content begins around the 16 minute mark, and ends with a demo around the 45 minute mark. Of course, feel free to view the whole lecture.

The slides for the lecture are available on the CS193P website; for a direct link, click here.

The code for the Fraction class at this moment can be downloaded by clicking here.

  • Welcome

    My goal is to make CupsOfCocoa into a beautiful source for beginners to the iPhone platform to get started. Subscribe below for more, and stay tuned!

  • Contact Me

    If you need to contact me for any reason, feel free to send me an email.
  • The Giving Spirit

    If you've found this site helpful, would you consider donating a little sum? Any amount is appreciated...Thanks so much!

  • Roadmap

  • Enter your email address to follow this blog and receive notifications of new posts by email.

    Join 220 other followers

  • Back to the Past

    May 2020
    S M T W T F S
     12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31  
  • Time Machine

  • You count!

    • 622,649 views
  • Worldwide Stats

    free counters
%d bloggers like this: