Objective-C |
Modified: |
DEFINITIONS
RESOURCES
Programming in Objective C - Lessons and examples by author Stephen Kochan
Memory Management - Apple's official guide
The Instruments Application - Application tuning and tracking memory leaks
Clang Static Analyzer - Requires Snow Leopard and Xcode 3.2
Zombie - enabling zombie detection
1. GETTING STARTED
- Xcode on the Mac
Example 1 - prog1
Xcode can create projects for different target applications.
A command line application is the simplest, avoiding a user interface.
- Open Xcode.
- File | New Project
- Mac OS X | Command Line Utility | Type Foundation
- Navigate to a directory for creating the project.
- prog1 ( project name )
- A window listing project files should open as at right.
- prog1.m is automatically created.
- Click Build and Go icon to compile and execute.
- To view output in debugger console window:
Run | Console
Example 2 - prog2
Objective C requires a top-level class from which all classes derive.
For iOS, NSObject is the preferred top-level class, predefined in the Foundation.h file.
Below is a simple example that implements and uses a Counter class in the typical Objective C fashion.
- Create a new project named prog2
- Copy and paste prog2.m below in place of the one Xcode generated.
- Create a new file Counter.h by:
File | New File | Other | Empty File
Counter.h
- Repeat for Counter.m
- Copy and paste the respective contents below.
- Click the Build and Go icon.
2.1 OBJECTS/METHODS/MESSAGES
main
Function main() is executed first.
- Counter *aCounter defines a variable that can reference a Counter object.
- [Counter new] is a message to Counter class to create a new Counter object; returning a reference to the created object.
[[Counter alloc] init] is the same message.
- Counter *aCounter = [Counter new]; assigns aCounter the reference to the created object.
- [aCounter inc]; sends the inc message to the object referenced by aCounter.
- [nil inc]; sends the inc message to nil; valid but does nothing other than explain why some messages are not received.
prog2.m #import "Counter.h"
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Counter *aCounter = [Counter new];
[aCounter inc];
[aCounter inc];
[aCounter print];
[aCounter release];
[pool drain];
return 0;
}
- [aCounter release]; decrements the number of references to aCounter by 1. More on this later in memory management.
- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; we will discuss later in relation to memory management.
2.2 CLASSES/GETTERS and SETTERS
A class consists of two main parts:
interface defines what elements (methods, instance variable, etc.) are visible outside the class.
implementation defines what the elements do and how.
Classes inherit from some top-level class, here NSObject.
Counter has one instance variable, count.
To provide access to instance variable outside the class definition and promote loose coupling, instance variables often have two methods, a setter and getter:
- setCount setter of the value of instance variable count
- count getter of the value of instance variable count
|
|
| #import "Counter.h" int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Counter *aCounter = [Counter new]; [aCounter inc]; [aCounter inc]; [aCounter print]; [aCounter release]; [pool drain]; return 0; } |
2.2.5 MESSAGES
Objects are sent messages (method calls).
[aCounter inc];
sends aCounter object the inc message.
2.3 INITIALIZATION
Though the count instance variable was initialized to zero by default, better to implement an init method to ensure proper initialization.
init is called by new:
Counter *aCounter = [Counter new];
or directly by:
Counter *aCounter = [[Counter alloc] init];
[super init] calls the super-class initialization; must do first to initialize the object by the super (parent) class init method.
|
|
2.5 PARAMETER PASSING
Pass-by-value is used in the majority of cases by object oriented languages.
Using Objective C, you should expect everything, primitive types and objects, to be pass-by-value. Otherwise, you're not probably using Objective C.
Objective C can pass-by-value or simulate pass-by-reference. Remember, C is always there.
C is pass-by-value and simulate pass-by-reference. Simulated pass-by-reference in C requires that:
the address of the actual parameter be passed by the caller function (i.e. & operator),
the formal parameter be dereferenced in the called function (i.e. * operator).
Pass-by-reference allows the actual parameter's value to be changed.
Here, parameter a value is change from 4 to 5.
Pass-by-value prevents changing (i.e. side-effecting) the value of the actual parameter.
Below, simulated pass-by-reference is used to implement a swap operation on int actual parameters, the values of the actuals are changed.
|
Pass-by-Reference
|
#import <Foundation/Foundation.h>
@interface Swap : NSObject {}
-(void) swap: (int *) x with: (int *) y;
@end
@implementation Swap
-(void) swap: (int *) x with: (int *) y {
}
@end
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool =
[[NSAutoreleasePool alloc] init];
Swap *s = [Swap new];
int a=4, b=5;
[ s swap: &a with: &b];
NSLog(@"%d %d", a, b);
[pool drain];
return 0;
}
|
Simulated pass-by-reference output is: 5 4
Below, pass-by-value is used to implement a swap operation on int actual parameters, the values of the actuals are unchanged.
|
Pass-by-Value |
#import <Foundation/Foundation.h>
@interface Swap : NSObject {}
-(void) swap: (int) x with: (int) y;
@end
@implementation Swap
-(void) swap: (int) x with: (int) y {
}
@end
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool =
[[NSAutoreleasePool alloc] init];
Swap *s = [Swap new];
int a=4, b=5;
[ s swap: a with: b];
NSLog(@"%d %d", a, b);
[pool drain];
return 0;
}
|
Pass-by-value output is: 4 5
2.6 OBJECTS AS PARAMETERS
Object parameters are almost always pass-by-value. Pass-by-reference is used to return results through the actual parameters.
One reason for pass-by-value is memory management becomes more challenging under pass-by-reference.
The receiver of a message has the formal parameter name self, is pass-by-value.
Pass-by-value of the actual parameter passes the parameter's value, though the value is a reference, it is still pass-by-value.
Pass-by-value prevents changing (i.e side-effecting) the value of the actual parameter.
However, when the value is the address of an object, the value of the object can be changed.
The actual parameter still has the same value, the address of the object.
Below, pass-by-value is used to implement -swap: operation on INT actual parameters.
Pass-by-value passes the value of the actual parameter, a reference to an object.
The actual parameter's value, the object reference, is unchanged.
The object attributes are changed by -swap:
Keep in mind that -swap: has two parameters, both pass-by-value:
- the message receiver with the formal parameter name self,
- the formal parameter with name x.
-(void) swap: (INT *) x;
[a swap: b];
|
#import <Foundation/Foundation.h> Pass-by-value
|
@implementation INT
-(id) init: (int) N {
[super init];
n = N;
return self;
}
-(void) setN : (int) N { n = N; }
-(int) getN { return n; }
-(void) swap: (INT *) x {
}
@end
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool =
[[NSAutoreleasePool alloc] init];
INT *a = [[INT alloc] init: 4];
INT *b = [[INT alloc] init: 5];
[a swap: b];
NSLog(@"%d %d", [ a getN ], [ b getN ]);
[pool drain];
return 0;
}
|
Output is: 5 4
Note: -getN is the accessor method by convention named -n.
4. INHERITANCE
The Counter class will be extended by dec (a decrement method).
self is the receiver when sending a message to the current object (this in Java and C++).
| #import "DecCounter.h" int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; DecCounter *aCounter = [DecCounter new]; [aCounter inc]; [aCounter print]; [aCounter dec]; [aCounter dec]; [aCounter print]; [pool drain]; return 0; } |
2009-11-27 12:50:03.421 Counter[3877:10b] count = 1
2009-11-27 12:50:03.422 Counter[3877:10b] count = -1
|
|
All Counter methods and instance variables are inherited.
By default, instance variables are protected, directly accessible by inherited classes.
|
|
4.5 POLYMORPHISM
Example
To demonstrate polymorphism, a who method has been added to both the Counter and DecCounter.
who method is invoked in print.
Polymorphism determines which who method is invoked, dependent upon whether the object is a Counter or DecCounter.
|
|
|
|
| #import "DecCounter.h" int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Counter *counter = [Counter new]; DecCounter *decCounter = [DecCounter new]; [counter inc]; [counter print]; [decCounter dec]; [decCounter print]; [pool drain]; return 0; } |
4.6 OVER-RIDING AND PRIVATE METHODS
Suppose we need a modulus 10 counter (Mod10Counter) based on the Counter class.
Over-riding the inc method of Counter allows specializing a inc method that will be accessed by Mod10Counter objects.
There is also a private method that should not be visible (callable) outside Mod10Counter class implementation.
In the following, inc is public and mod10 is private.
The interface of a private method is defined within the same file as the implementation.
|
|
|
|
4.7 PUBLIC/PRIVATE/PROTECTED INSTANCE VARIABLES
Can specify access to instance variables by:
- @protected - directly accessed in the class and subclass. Default.
- @private - directly accessed in the defining class but nowhere else.
- @public - directly accessed anywhere.
Counter.h #import <Foundation/Foundation.h>
@interface Counter : NSObject {@private
int count;
}
-(void) print;
-(void) setCount: (int) n;
-(int) count;
-(void) inc;
@end
4.8 CATEGORIES - Chapter 22 of COCOA PROGRAMMING
DecCounter inherited Counter class methods and instance variables, adding a dec method.
Inheritance
DecCounter.h #import "Counter.h"
@interface DecCounter : Counter { }
-(void) dec;
@end
DecCounter.m #import "DecCounter.h"
@implementation DecCounter;
-(void) dec { [self setCount: [self count] - 1]; }
@endUse
prog2.m #import "DecCounter.h"
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Counter *counter = [Counter new];
[counter inc];
[counter print];
[counter dec];
[counter print];
[pool drain];
return 0;
}Note that a parent class object should not be the receiver of a child class message or assigned to a child.
[counter dec];
compiles with a warning that counter does not respond to dec and crashes when run.
DecCounter *decCounter = [Counter new];
[ decCounter dec ];compiles but crashes, a Counter object does not have a dec message.
Why not add the dec method to Counter?
Counter source code may not be available.
Category extends a class definition by adding methods only, not instance variables, to an existing class.
The key benefit is that a parent class object can be the receiver of a category message (i.e. method).
[counter dec]; compiles cleanly.
And we don't need the Counter source.
Category
DecCounter.h #import "Counter.h"
@interface Counter (DecCounter)
-(void) dec;
@end
DecCounter.m #import "DecCounter.h"
@implementation Counter (DecCounter);
-(void) dec { [self setCount: [self count] - 1]; }
@endUse
prog2.m #import "DecCounter.h"
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Counter *counter = [Counter new];
[counter inc];
[counter print];
[counter dec];
[counter print];
[pool drain];
return 0;
}
Objects inherit from a top-level class such as NSObject.
description method, by default, returns the class name and object address in memory.
Over-riding the description method allows programmer defined descriptions, useful for debugging.
For example, the following displays the Counter object description to the debugging console:
Counter *counter = [[Counter alloc] init]; NSLog(@" %@ ", counter );
using the following description definition prints:
Counter: 0
A description method should return an NSString object of the values of instance variables and the class name, as in the following Counter with instance variable count:
-(NSString *) description {
NSString *result;
result = [[NSString alloc] initWithFormat:@"Counter: %i ", count];
[result autorelease];
return result;
}The above can serve as a model description method, however there are memory management issues which are discussed next.
Variables automatically allocated on stack on method entry:
parameters
local variables
deallocated automatically by adjusting the stack on method exit.
Objects allocated on heap by:
new
alloc
malloc
deallocated manually at appropriate time.
Mac OS X supports garbage collection but the current iOS does not, for performance. Running the garbage collection could cause the UI to stall.
Common Human Errors
Heap
Program variables reference allocated heap memory (result of new, alloc etc.)
Program variables are allocated on the stack but may reference heap memory.
The following is a linked list.
Garbage Collection using Reference Counting
Garbage collection automatically recovers freed memory, avoiding most of the human errors.
Memory is allocated in cells, with reference to other memory cells added as program executes.
Each cell has a reference count reflecting the number of referencing cells.
When variable is no longer visible, run-time system automatically calls garbage collector.
void decrement( Cell c) {
c.count--;
if(c.count == 0) {
decrement(c.right);
decrement(c.left);
delete c;
}
}When a variable reference is released, the reference count is decremented.
Using the algorithm above, the garbage collector follows references to decrement each reference count.
Problems
Human Reference Counting
Reference counting is used to determine when an object storage can be reclaimed.
Each object has a retain count field for reference counting.
alloc allocates an object and sets the retain count = 1.
retain increments the retain count.
release decrements the retain count.
autorelease decrements the retain count, often at end of application or UI event loop.
dealloc called when retain count = 0.
retainCount returns the retain count of an object. Widely viewed by developers to be unreliable.
Rules for the programmer
- Objects created by alloc, new, copy or mutableCopy have a retain count of 1 and should be released.
NSString *s = [[ NSString alloc] stringWithString: @"Hello World"];
[s release];
- Objects obtained by any other method will be automatically released, unless retained.
NSString *s = @"Hello World";
or
NSString *s = [ @"Hello World" retain];
[s release];
Example
Reconsider the Counter example, which releases allocated memory.
Counter.h #import <Foundation/Foundation.h>
@interface Counter : NSObject {
int count;
}
-(void) print;
-(void) setCount: (int) n;
-(int) count;
-(void) inc;
@end
Counter.m #import "Counter.h"
@implementation Counter;
-(void) print { NSLog(@"count = %i ", count ); }
-(void) setCount: (int) n { count = n; }
-(int) count { return count; }
-(void) inc { count++; }
-(void) dealloc {
NSLog(@"dealloc Counter");
[super dealloc];
}
@end
prog2.m #import "Counter.h"
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Counter *aCounter = [[Counter alloc] init];
[aCounter inc];
[aCounter inc];
[aCounter print];
[aCounter release];
[pool drain];
return 0;
}
Counter *aCounter = [ [Counter alloc] init];
Allocates storage for a Counter object which includes instance variables and a reference count, automatically set to 1 when the object is created.
[aCounter release];
Decrements the reference count, which when 0, the object -dealloc method is called and memory is reclaimed.
Example
Counter objects do not have instances of other objects, only memory for an int so releasing a Counter object deallocates all memory.
However, an object that contains an object is responsible for deallocating those objects.
@interface TwoCounters : NSObject {
Counter * one, * two;
}
-(id) init;
-(void) dealloc;
@end
TwoCounters * a = [TwoCounters new];
-(id) init {
[super init];
one = [Counter new];
two = [Counter new];
return self;
}Allocates storage for a TwoCounters object which in turn allocates two Counter objects, the reference count of all objects is 1.
[a release];
-(void) dealloc {
[super dealloc];
}Decrements the reference (retain) count of the TwoCounter object.
When 0, -dealloc called, the TwoCounters object memory is reclaimed.
one and two instance variables' memory is reclaimed.
However, the Counter objects referenced by one and two remain.
[a release] calls the TwoCounters object's -dealloc method, if one exists, when retain count is 0.
[a release]; -(void) dealloc {
[one release];
[two release];
[super dealloc];
}Now the TwoCounters -dealloc releases the one and two Counter objects.
a variable remains but with a dangling reference to deallocated memory.
Any use of a is incorrect, and will, if we're lucky, cause a noticeable failure if we try to use it.
prog2.5.m #import "Counter.h" @interface TwoCounters : NSObject { Counter * one, * two; } -(id) init; -(void) dealloc; @end@implementation TwoCounters -(id) init { [super init]; one = [Counter new]; two = [Counter new]; return self; } -(void) dealloc { [one release]; [two release]; [super dealloc]; } @end int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; TwoCounters *a = [TwoCounters new]; [a release]; [pool drain]; return 0; }
6. ARRAYS - AGGREGATE DATA
NSMutableArray *array; array = [NSMutableArray new];
[array addObject: @"String 1"];
[array addObject: @"String 2"];
[array addObject: [[NSNumber alloc] initWithInt: 42]];
for( (id) element in array)
NSLog(@" %@ ", element) ;
Outputs: String 1 String 2 42
Example
Counters is an aggregate of multiple DecCounter's in an array.
NSMutableArray class allows array elements to be changed.
description method returns a NSString describing the Counters object.
NSLog(@"%@", counters) calls the method, due to the %@ format.
|
|
Assuming counters references an object as in the following diagram:
NSLog(@"%@", counters);
[counters print];produces the debugging console output of:
2009-11-27 19:53:09.334 Counter[4531:10b] Array
2009-11-27 19:53:09.335 Counter[4531:10b] count = 4
2009-11-27 19:53:09.336 Counter[4531:10b] count = 7
2009-11-27 19:53:09.336 Counter[4531:10b] count = 8
2009-11-27 19:53:09.337 Counter[4531:10b] count = 6
2009-11-27 19:53:09.338 Counter[4531:10b] count = 4
6.1 MORE MEMORY MANAGEMENT
release
prog3.m has a memory leak.
Counters object memory was allocated but never reclaimed, until the program terminated.
Counters *counters = [[Counters alloc] init: @"Array"];
[counters release]; decrements the retain count to 0; Counters object memory is reclaimed.
| #import "Counters.h" int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Counters *counters = [[Counters alloc] init: @"Array"]; NSLog(@"%@", counters); [counters print]; [counters release]; |
| @interface Counters : NSObject { NSString *name; NSMutableArray *array; } @end |
dealloc
Still a memory leak.
[counters release];
reclaimed the Counters object memory for instance variables name and array but not the memory referenced by the instance variables name and array.
The object user allocates using alloc or new and should release the object.
retain claims the object for a user and should be released.
However, releasing memory referenced by an object should not be the responsibility of the user of the object.
dealloc over-rides the parent method to release memory used by an object.
Called when an object with a retain count of 1 is sent a release.
dealloc is called when the main() method sends [counters release].
-(void) dealloc {
[name release];
[array release];
[super dealloc];
}
Still a memory leak.
[array release]; reclaimed the NSMutableArray.
When objects are added to a NSMutableArray, the retain count of each object is incremented.
When NSMutableArray is released, each object reference is decremented.
Problem is each DecCounter object reference was incremented twice:
- DecCounter *dc = [DecCounter new];
- [array addObject: dc];
-(id) init: (NSString *) theName {
int i;
[super init];
name = theName;
array = [NSMutableArray new];
for(i=0; i < 5; i++) {
DecCounter *dc = [DecCounter new];
[dc setCount: (random() % 10 + 1)];
[array addObject: dc];
}
return self;
}Solution can be implemented in either the init or dealloc but not both.
Here, each array entry is manually released, then the array is released which releases each entry again.
-(void) dealloc {
[name release];for( DecCounter *dc in array)
[dc release];[array release];
[super dealloc];
}
finally is deallocated to:
autorelease
Still a memory leak.
-(NSString *) description {
NSString *result;
result = [[NSString alloc] initWithFormat:@"%@ ",name];
return result;
}description method allocates and returns result, an NSString object.
The calling method would normally retain a returned object, incrementing the retain count, then release the object when no longer needed.
However, the reference count would still be 1 due to the alloc above.
Releasing the object immediately decrements the reference count, so:
[result release];
return result;returns a dangling reference to the caller.
The following delays the object release allowing the calling method to execute a retain.
[result autorelease];
return result;We assume that the calling method will retain the returned object if it wants and eventually release the object, decrementing the retain count; or allow the object to be released automatically at the end of the event loop.
|
autorelease adds an object to the autorelease pool, consisting of a list of objects that will be sent a release message when the pool is released or drained.
Because the autorelease does not decrement the retain count until the pool is released, the object can continue to be accessed.
It is not a good idea to use autorelease in place of release since memory is occupied until the pool is released, which may not be until the end of the application.
Allocating an object (retain count=1), then releasing (retain count=0) and autoreleasing the object will cause a memory fault when the pool is released.
When the autorelease pool is released, each object is sent a release message. Those with retain count = 0 are deallocated, the rest will survive.
Cocoa's (and iOS's) event loop releases the autorelease pool at the end of the loop.
When using Foundation classes, the pool is managed programmatically as below, while Cocoa automatically performs pool management.
| #import "Counters.h" int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Counters *counters = [[Counters alloc] init: @"Array"]; [counters autorelease]; NSLog(@"%@", counters); [pool drain]; return 0; |
retain
No memory leak now but a potential dangling reference.
Keep in mind that alloc or new sets the retain count to 1.
Assume that any object passed as a parameter, the caller has retained.
init receives a NSString reference parameter but does not retain.
name = theName; Problem that can result:
Should the caller release the theName object, the object could be reclaimed, creating a dangling reference.
Solution is to:
retain the reference parameter
The following init incorporates two solutions:
- Prevent a dangling reference due to the parameter object, theName, not being retained
- Prevent a memory leak due to allocating DecCounter (retain count=1) and adding to the array (increment retain count).
-(id) init: (NSString *) theName {
int i;
[super init];[theName retain];
name = theName;
array = [NSMutableArray new];
for(i=0; i < 5; i++) {
DecCounter *dc = [DecCounter new];
[dc setCount: (random() % 10 + 1)];
[array addObject: dc];[dc release];
}
return self;
}
|
Xcode Leak analysis tools
Static leak analysis can identify some errors, usually forgetting to release an object.
In Xcode, Build | Build and Analyze
Below a potential leak was detected, two allocations but only one release.
Below displays obvious leaks but misses those due to aliases.
GETTER/SETTER Issues
Non-objects such as int instance variables do not need to be managed.
Setters of objects must manage those objects.
|
After [e2 setName: title]; |
Setter problem that results:
[title release]; Caller releasing the theName object, the object could be reclaimed, creating a dangling reference.
Following could fail because name instance variable has dangling reference:
[e1 print]; Adding retain fixes the dangling reference problem, recording 3 references to the same @"An Example" string, two from -init: and one from the pool allocation by @"An Example".
-(id) init: (NSString *) theName {
[retain theName];
name=theName;
}But creates a memory leak if different theName objects are used in multiple -setName: calls to the same .
prog5.m Example *e1 = [[NSString alloc] initWithFormat:@"%s", "One"];
[e1 setName: "Another"];
-dealloc releases only the most recent of the two retains.
-(void) dealloc {
[name release];
[super dealloc];
}Solution is to:
retain the reference parameter
release the previous reference of the instance variable
theName retain count is first incremented, then name retain count is decremented.
prog5.m Example *e1 = [[NSString alloc] initWithFormat:@"%s", "One"];
[e1 setName: [[NSString alloc] initWithFormat:@"%s", "Another"]];
-(void) setName: (NSString *) theName
{
[theName retain];
[name release];
name = theName;
}-(void) dealloc {
[name release];
[super dealloc];
}If both referenced the same object, name retain count would be unchanged.
If each reference different objects, name retain count correctly reflects the number of references to each object.
Note that @"One" and @"Another" are autoreleased.
Getters of objects have simple behavior, no need for memory management:
-(NSString *) name
{
return name;
}
@properties/@synthesize
Automatically generated setters can behave as described above, retaining the setter object.
@interface Example {
NSString *name;
}
@properties (retain, readwrite) NSString *name;
-(id) init: (NSString *) theName;
-(void) print;
@end@implementation Example
@synthesize name;-(void) dealloc {
[name release];
[super dealloc];
}The -dealloc releases the last object retained by the setter.
retainCount
Releasing objects having a retain count 0 aborts execution.
The following code displays 1 for the retain count of an allocated object.
NSObject *nsO = [[NSObect alloc] init]; NSLog(@"%d", [nsO retainCount]);
7. Exceptions
Error handling is critical for handling out of the ordinary conditions, such as an attempt to open a file that does not exist.
Exceptions are a formal language construct for controlling out of the ordinary flow of execution.
Exceptions consist of four main elements:
- defining an exception
- trying code that contains an exception
- throwing an exception
- catching an exception
The following illustrates the four and the results (at right).
|
#import <Foundation/Foundation.h> int main (int argc, const char * argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSException *e = [NSException exceptionWithName: @"GenericException" reason: @"An example exception" userInfo: nil]; NSLog(@"1"); @try { NSLog(@"2"); @throw e; NSLog(@"3"); } @catch (NSException *ex ) { ) { NSLog( @"%@: ", ex); NSLog(@"4"); } @finally { NSLog(@"5"); } NSLog(@"6"); [pool release]; return 0; } |
1 2 An example exception: 4 5 6
|
Exceptions can be thrown by methods to the caller.
Advantages
- standard approach for reporting failures
- method can report any failure
- throw follows call stack to caller
- can subclass NSException to create more specific handlers
Disadvantages
- compiler does not check that method throws an exception so throw is hidden
- throw is implemented using jumps so stack allocations are not deallocated (by my interpretation)
- trickier to release memory allocations made by a method (or not if exception), can check retainCount before releasing an object.
Generally, release objects in @finally.
|
#import <Foundation/Foundation.h> @interface Example : NSObject {} -(void) foo; @end @implementation Example -(void) foo { NSException *e = [NSException exceptionWithName: @"GenericException" reason: @"An example exception" userInfo: nil]; @throw e; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; Example *example = [[Example alloc] init]; NSLog(@"1"); @try { NSLog(@"2"); [example foo]; NSLog(@"3"); } @catch (NSException *ex ) { NSLog( @"%@: ", ex); NSLog(@"4"); } @finally { NSLog(@"5"); [example release]; } NSLog(@"6"); [pool release]; return 0; } |
1 2 An example exception: 4 5 6 |
re-throwing
Exceptions can be re-thrown, passing up the call chain.
Notice that @finally is executed, though statements following @throw are not.
|
#import <Foundation/Foundation.h> @interface Example : NSObject {} -(void) foo; @end @implementation Example -(void) foo { NSException *e = [NSException exceptionWithName: @"GenericException" reason: @"An example exception" userInfo: nil]; @throw e; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; Example *example = [[Example alloc] init]; NSLog(@"1"); @try { NSLog(@"2"); [example foo]; NSLog(@"3"); } @catch (NSException *ex ) { NSLog(@"%@: ", ex); @throw; NSLog(@"4"); } @finally { NSLog(@"5"); [example release]; } NSLog(@"6"); [pool release]; return 0; } |
1 2 An example exception: 5 *** Terminating app due to uncaught |
Uncaught exception
Throwing without catching an exception results in program termination.
|
#import <Foundation/Foundation.h> @interface Example : NSObject {} -(void) foo; @end @implementation Example -(void) foo { NSException *e = [NSException exceptionWithName: @"GenericException" reason: @"An example exception" userInfo: nil]; @throw e; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; Example *example = [[Example alloc] init]; NSLog(@"1"); [example foo]; NSLog(@"2"); [example release]; [pool release]; return 0; } |
1 *** Terminating app due to uncaught |
Subclassing exceptions
Subclassing NSException defines specific types of exceptions to be thrown.
Multiple @catch statements, should be arranged from most specific to most general (NSException).
#import <Foundation/Foundation.h>
@interface MyException : NSException {}
@end
@implementation MyException
@end
@interface Example : NSObject {}
-(void) foo;
@end
@implementation Example
-(void) foo {
MyException *e =
[MyException
exceptionWithName: @"MyException"
reason: @"An example MyException"
userInfo: nil];
@throw e;
}
@end
int main (int argc, const char * argv[]) {
NSAutoreleasePool *pool =
[[NSAutoreleasePool alloc] init];
Example *example = [[Example alloc] init];
NSLog(@"1");
@try {
NSLog(@"2");
[example foo];
NSLog(@"3");
} @catch (MyException *ex ) {
NSLog( @"%@: ", ex);
NSLog(@"4 MyException");
} @catch (NSException *ex ) {
NSLog( @"%@: ", ex);
NSLog(@"4 NSException");
} @finally {
NSLog(@"5");
[example release];
}
NSLog(@"6");
[pool release];
return 0;
}
|
1 2 An example MyException: 4 MyException 5 6 |