Hi everybody
I'm facing a problem I cannot resolve... I have a grouped table whose section header and section footer get displayed correctly upon launch thanks to
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
and
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
Yet, I can't figure out how to update them later, I don't know how to change those texts and it's quite frustrating because in my mind it had to be nothing harder than changing a text label or whatever... (some ".text" property...)
I searched all through the documentation with no luck...
In the delegate method, add a method call that returns the section name, e.g.:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
switch (section) {
case firstSectionTag:
return [self firstSectionTitle];
case secondSectionTag:
return [self secondSectionTitle];
// ...
default:
return nil;
}
}
- (NSString *)firstSectionTitle {
// generate first section title programmatically, e.g. "return [NSDate date];"
}
// ...
Then, when you need to update the section title, send an NSNotification that triggers something like the following method:
- (void)refreshTableSectionTitles:(NSNotification *)notification {
[tableView reloadData];
}
it given the information about how to develop the iphone application and games.......
Wednesday, February 9, 2011
Tuesday, February 8, 2011
Rectangles And Points
Rectangles and Points
All drawing in Quartz involves rectangles. In Cocoa, you use the NSRect struct to describe a rectangle's location and dimensions:
(Rects don't automatically draw themselves. These are just diagrams to go with the examples.)
typedef struct {
NSPoint origin;
NSSize size;
} NSRect;
// make a rect at (0,0) which is 20x20
NSRect myRect;
myRect.origin.x = 0;
myRect.origin.y = 0;
myRect.size.width = 20;
myRect.size.height = 20;
The origin field is the "anchor point" of the rect, where the drawing starts. A point is described by NSPoint, which has x and y coordinates:
typedef struct {
float x;
float y;
} NSPoint;
// make three points on the canvas
NSPoint point1;
point1.x = 4;
point1.y = 11;
NSPoint point2;
point2.x = 12;
point2.y = 21;
NSPoint point3;
point3.x = 19;
point3.y = 8;
The size field of a rect is an NSSize, which holds a width and a height. There's no way to depict an instance of NSSize, it has to be part of a rect to be useful.
typedef struct {
float width;
float height;
} NSSize;
Much of 2D drawing in Cocoa is based on these three structs. Remember these are not Objective-C classes. You can't call methods on them directly, but there are functions that go with them.
All measurements in Quartz are float values, which gives you finer control of drawing than integer-based coordinates.
Convenience Functions
Cocoa has a number of functions for creating geometry structs. Most of them are listed in Foundation's NSGeometry.h file.
// make a point at coordinate 20,20
NSPoint newPoint = NSMakePoint ( 20, 20 );
// make a size of 100 wide x 100 high
NSSize newSize = NSMakeSize ( 100, 100 );
// use the previous point and size to make a rect
NSRect newRect = NSMakeRect ( newPoint.x,
newPoint.y,
newSize.width,
newSize.height );
// also can just do this
NSRect newRect = NSMakeRect ( 20, 20, 100, 100 );
Using these functions instead of creating the structs manually makes the code a bit more obvious and makes searching easier.
Coordinates in Quartz
The drawing area of a view in Cocoa is treated as a rect. Quartz calls this drawing area the "bounds." An NSPoint can represent any location in the view bounds.
The standard Quartz coordinate system is based on PDF model, which means drawing in a view starts in the bottom-left. This is what you see in geometry textbooks.
Sometimes it's easier to write drawing code if the origin is in the top-left. This is how things work in web page design, for example. Quartz calls this a flipped coordinate system.
You can easily convert points between standard and flipped views using NSView's convertPoint:fromView: and convertPoint:toView: methods.
Rects as Objects
Because they're not objects, you can't store the geometry structs in an NSArray, NSDictionary, or NSSet directly, but you can wrap them in an NSValue object:
NSRect newRect = NSMakeRect ( 20, 20, 100, 100 );
NSValue * rectObject = [NSValue valueWithRect: newRect];
NSMutableArray * myArray = [NSMutableArray array];
[myArray addObject: rectObject];
NSRect originalRect = [[myArray objectAtIndex: 0] rectValue];
NSValue has similar methods for NSPoint and NSSize. You can also log information about rects using the NSStringFromRect function:
NSRect newRect = NSMakeRect ( 20, 20, 100, 100 );
NSLog (@"%@", NSStringFromRect( newRect ));
Another function, NSRectFromString takes a properly-formatted rect description and returns an NSRect. Both sets of functions also exist for NSPoint and NSSize.
Derived Rects
Cocoa provides functions to create new rects based on existing ones. Here's how to make a rect which has the same dimensions as the original, but is shifted down and to the right (offset).
// create a rect, then get the 5x5 offset
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSOffsetRect ( rect1, 5, 5 );
You can use negative values for the offset if you want to move in the opposite directions.
Here's how to get the intersection area of two rects:
// get the common area between two rects
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSOffsetRect ( rect1, 5, 5 );
NSRect rect3;
rect3 = NSIntersectionRect ( rect1, rect2 );
Here's how to create a rect which encloses two other rects (a union).
// get a combination of two rects
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSOffsetRect ( rect1, 5, 5 );
NSRect rect3;
rect3 = NSUnionRect ( rect1, rect2 );
An inset rect is helpful if you want to create a outer boundry, then create a rect for the content inside:
// get a contracted version of a rect
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSInsetRect ( rect1, 5, 5 );
Comparing Rects and Points
Foundation provides a group of functions to check the equality of points and rects, as well as functions to see if points and rects are inside in other rects.
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSPoint point1 = NSMakePoint ( 8,21 );
BOOL isInRect;
isInRect = NSPointInRect ( point1, rect1 );
Below is a table of the most useful comparison functions. All of these functions return a YES or NO value.
Comparison Functions
NSEqualRects Are rects identical?
NSEqualPoints Are points identical?
NSEqualSizes Are sizes identical?
NSContainsRect Does the first rect contain the other?
NSIntersectsRect Do the rects at least partially overlap?
NSPointInRect Is the point inside the rect?
NSMouseInRect Is the mouse cursor in this rect?
NSIsEmptyRect Is the rect empty (no area)?
These functions are listed in Foundation's NSGeometry.h file.
Drawing
NSRects and NSPoints only describe geometry, they don't actually do drawing. Let's look at some primitive drawing functions in Cocoa's NSGraphics.h file.
NSColor * gray = [NSColor grayColor];
NSColor * white = [NSColor whiteColor];
// fill background
[gray set];
NSRectFill ( [self bounds] );
// fill target rect
NSRect rect1 = NSMakeRect ( 21,21,210,210 );
[white set];
NSRectFill ( rect1 );
The example above uses NSColor. When you call the -set method on a color object, Quartz uses it for all drawing until you set a new one.
Here's how to draw a border around a rect:
NSColor * gray = [NSColor grayColor];
NSColor * white = [NSColor whiteColor];
// fill background
[gray set];
NSRectFill ( [self bounds] );
// draw a border around target rect
NSRect rect1 = NSMakeRect ( 21,21,210,210 );
[white set];
NSFrameRectWithWidth ( rect1, 1 );
You can also call NSFrameRect if you just want to use the default line width.
Drawing Groups
It's often faster to draw an array of rects all at once instead of calling NSRectFill for each one individually.
Below is a more involved example which builds C-style arrays of NSRects and NSColors, then passes both to NSRectFillListWithColors.
// setup basics
[[NSColor grayColor] set];
NSRectFill ( [self bounds] );
int count = 12;
NSRect startingRect = NSMakeRect ( 21,21,50,50 );
// create arrays of rects and colors
NSRect rectArray [count];
NSColor * colorArray[count];
rectArray [0] = startingRect;
colorArray[0] = [NSColor redColor];
// populate arrays
int i;
NSRect oneRect = rectArray[0];
for ( i = 1; i < count; i++ ) { // move 100 pixels to the right oneRect.origin.x += 100; // if the right edge doesn't fit, move down 100 pixels if ( NSMaxX (oneRect) > NSMaxX ([self bounds]) )
{
oneRect.origin.x = startingRect.origin.x;
oneRect.origin.y += 100;
}
rectArray [i] = oneRect;
// increment color
colorArray[i] = [NSColor colorWithCalibratedHue: (i*0.04)
saturation: 1
brightness: 0.9
alpha: 1];
}
// use rect and color arrays to fill
NSRectFillListWithColors ( rectArray, colorArray, count );
// draw a 2 pixel border around each rect
[[NSColor whiteColor] set];
for ( i = 0; i < count; i++) {
NSFrameRectWithWidth ( rectArray[i], 2 );
}
All drawing in Quartz involves rectangles. In Cocoa, you use the NSRect struct to describe a rectangle's location and dimensions:
(Rects don't automatically draw themselves. These are just diagrams to go with the examples.)
typedef struct {
NSPoint origin;
NSSize size;
} NSRect;
// make a rect at (0,0) which is 20x20
NSRect myRect;
myRect.origin.x = 0;
myRect.origin.y = 0;
myRect.size.width = 20;
myRect.size.height = 20;
The origin field is the "anchor point" of the rect, where the drawing starts. A point is described by NSPoint, which has x and y coordinates:
typedef struct {
float x;
float y;
} NSPoint;
// make three points on the canvas
NSPoint point1;
point1.x = 4;
point1.y = 11;
NSPoint point2;
point2.x = 12;
point2.y = 21;
NSPoint point3;
point3.x = 19;
point3.y = 8;
The size field of a rect is an NSSize, which holds a width and a height. There's no way to depict an instance of NSSize, it has to be part of a rect to be useful.
typedef struct {
float width;
float height;
} NSSize;
Much of 2D drawing in Cocoa is based on these three structs. Remember these are not Objective-C classes. You can't call methods on them directly, but there are functions that go with them.
All measurements in Quartz are float values, which gives you finer control of drawing than integer-based coordinates.
Convenience Functions
Cocoa has a number of functions for creating geometry structs. Most of them are listed in Foundation's NSGeometry.h file.
// make a point at coordinate 20,20
NSPoint newPoint = NSMakePoint ( 20, 20 );
// make a size of 100 wide x 100 high
NSSize newSize = NSMakeSize ( 100, 100 );
// use the previous point and size to make a rect
NSRect newRect = NSMakeRect ( newPoint.x,
newPoint.y,
newSize.width,
newSize.height );
// also can just do this
NSRect newRect = NSMakeRect ( 20, 20, 100, 100 );
Using these functions instead of creating the structs manually makes the code a bit more obvious and makes searching easier.
Coordinates in Quartz
The drawing area of a view in Cocoa is treated as a rect. Quartz calls this drawing area the "bounds." An NSPoint can represent any location in the view bounds.
The standard Quartz coordinate system is based on PDF model, which means drawing in a view starts in the bottom-left. This is what you see in geometry textbooks.
Sometimes it's easier to write drawing code if the origin is in the top-left. This is how things work in web page design, for example. Quartz calls this a flipped coordinate system.
You can easily convert points between standard and flipped views using NSView's convertPoint:fromView: and convertPoint:toView: methods.
Rects as Objects
Because they're not objects, you can't store the geometry structs in an NSArray, NSDictionary, or NSSet directly, but you can wrap them in an NSValue object:
NSRect newRect = NSMakeRect ( 20, 20, 100, 100 );
NSValue * rectObject = [NSValue valueWithRect: newRect];
NSMutableArray * myArray = [NSMutableArray array];
[myArray addObject: rectObject];
NSRect originalRect = [[myArray objectAtIndex: 0] rectValue];
NSValue has similar methods for NSPoint and NSSize. You can also log information about rects using the NSStringFromRect function:
NSRect newRect = NSMakeRect ( 20, 20, 100, 100 );
NSLog (@"%@", NSStringFromRect( newRect ));
Another function, NSRectFromString takes a properly-formatted rect description and returns an NSRect. Both sets of functions also exist for NSPoint and NSSize.
Derived Rects
Cocoa provides functions to create new rects based on existing ones. Here's how to make a rect which has the same dimensions as the original, but is shifted down and to the right (offset).
// create a rect, then get the 5x5 offset
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSOffsetRect ( rect1, 5, 5 );
You can use negative values for the offset if you want to move in the opposite directions.
Here's how to get the intersection area of two rects:
// get the common area between two rects
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSOffsetRect ( rect1, 5, 5 );
NSRect rect3;
rect3 = NSIntersectionRect ( rect1, rect2 );
Here's how to create a rect which encloses two other rects (a union).
// get a combination of two rects
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSOffsetRect ( rect1, 5, 5 );
NSRect rect3;
rect3 = NSUnionRect ( rect1, rect2 );
An inset rect is helpful if you want to create a outer boundry, then create a rect for the content inside:
// get a contracted version of a rect
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSInsetRect ( rect1, 5, 5 );
Comparing Rects and Points
Foundation provides a group of functions to check the equality of points and rects, as well as functions to see if points and rects are inside in other rects.
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSPoint point1 = NSMakePoint ( 8,21 );
BOOL isInRect;
isInRect = NSPointInRect ( point1, rect1 );
Below is a table of the most useful comparison functions. All of these functions return a YES or NO value.
Comparison Functions
NSEqualRects Are rects identical?
NSEqualPoints Are points identical?
NSEqualSizes Are sizes identical?
NSContainsRect Does the first rect contain the other?
NSIntersectsRect Do the rects at least partially overlap?
NSPointInRect Is the point inside the rect?
NSMouseInRect Is the mouse cursor in this rect?
NSIsEmptyRect Is the rect empty (no area)?
These functions are listed in Foundation's NSGeometry.h file.
Drawing
NSRects and NSPoints only describe geometry, they don't actually do drawing. Let's look at some primitive drawing functions in Cocoa's NSGraphics.h file.
NSColor * gray = [NSColor grayColor];
NSColor * white = [NSColor whiteColor];
// fill background
[gray set];
NSRectFill ( [self bounds] );
// fill target rect
NSRect rect1 = NSMakeRect ( 21,21,210,210 );
[white set];
NSRectFill ( rect1 );
The example above uses NSColor. When you call the -set method on a color object, Quartz uses it for all drawing until you set a new one.
Here's how to draw a border around a rect:
NSColor * gray = [NSColor grayColor];
NSColor * white = [NSColor whiteColor];
// fill background
[gray set];
NSRectFill ( [self bounds] );
// draw a border around target rect
NSRect rect1 = NSMakeRect ( 21,21,210,210 );
[white set];
NSFrameRectWithWidth ( rect1, 1 );
You can also call NSFrameRect if you just want to use the default line width.
Drawing Groups
It's often faster to draw an array of rects all at once instead of calling NSRectFill for each one individually.
Below is a more involved example which builds C-style arrays of NSRects and NSColors, then passes both to NSRectFillListWithColors.
// setup basics
[[NSColor grayColor] set];
NSRectFill ( [self bounds] );
int count = 12;
NSRect startingRect = NSMakeRect ( 21,21,50,50 );
// create arrays of rects and colors
NSRect rectArray [count];
NSColor * colorArray[count];
rectArray [0] = startingRect;
colorArray[0] = [NSColor redColor];
// populate arrays
int i;
NSRect oneRect = rectArray[0];
for ( i = 1; i < count; i++ ) { // move 100 pixels to the right oneRect.origin.x += 100; // if the right edge doesn't fit, move down 100 pixels if ( NSMaxX (oneRect) > NSMaxX ([self bounds]) )
{
oneRect.origin.x = startingRect.origin.x;
oneRect.origin.y += 100;
}
rectArray [i] = oneRect;
// increment color
colorArray[i] = [NSColor colorWithCalibratedHue: (i*0.04)
saturation: 1
brightness: 0.9
alpha: 1];
}
// use rect and color arrays to fill
NSRectFillListWithColors ( rectArray, colorArray, count );
// draw a 2 pixel border around each rect
[[NSColor whiteColor] set];
for ( i = 0; i < count; i++) {
NSFrameRectWithWidth ( rectArray[i], 2 );
}
uiview aniamtion
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
// Create the application's window and set its background color.
CGRect windowRect = [[UIScreen mainScreen] applicationFrame];
UIWindow *window = [[UIWindow alloc] initWithFrame:windowRect];
[window setBackgroundColor:[UIColor lightGrayColor]];
[self setWindow:window];
// Create an empty view with a different background color so we can
// see something on the screen. The four float constants define the
// x, y, width, and height of the view's frame rectangle.
//
CGRect viewRect1 = { 80.0, 200.0, 160.0, 100.0 };
WiggleView *wiggleView1 = [[WiggleView alloc] initWithFrame:viewRect1];
[wiggleView1 setBackgroundColor:[UIColor darkGrayColor]];
// Create another view half the size of the first one with a
// different background color.
//
CGRect viewRect2 = { 0.0, 340.0, 80.0, 50.0 };
WiggleView *wiggleView2 = [[WiggleView alloc] initWithFrame:viewRect2];
[wiggleView2 setBackgroundColor:[UIColor purpleColor]];
// Add the two views to the subviews array so that wiggleView1 is on top
// of wiggleView2
//
[window addSubview:wiggleView2];
[window addSubview:wiggleView1];
[window makeKeyAndVisible];
// Send wiggle and fly messages to the subviews
//
[wiggleView1 wiggle];
[wiggleView2 fly];
[window release];
[wiggleView1 release];
[wiggleView2 release];
}
//////////////////////////////////////////////////////////////////////////////
#import "WiggleView.h"
#import <QuartzCore/QuartzCore.h>
@implementation WiggleView
// These compiler directives add some nice formatting to the Xcode dropdown
// above (which should read "@implementation WiggleView").
// The first directive causes the menu to draw a line; the second, causes it
// to add the heading, "Transform and Animate."
//
#pragma mark -
#pragma mark Transform and Animate
// Make the view wiggle.!
//
- (void)wiggle
{
// Start of animation block.
[UIView beginAnimations:nil context:NULL];
// Animation settings.
[UIView setAnimationDuration:0.25];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationRepeatCount:12.5];
[UIView setAnimationRepeatAutoreverses:YES];
// Transformation. Rotates the view .075 radians.
[self setTransform:CGAffineTransformMakeRotation(-0.75)];
// End of animation block -- commit the animations.
[UIView commitAnimations];
}
// Mke the view fly.
//
- (void)fly
{
// Start animation block.
[UIView beginAnimations:nil context:NULL];
// Animation settings.
[UIView setAnimationDuration:1.75];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
// Transformations. Translate by 200 pixels on the x axis and -300 pixels
// on the y axis, and scale down from twice its original size. Note that
// because these are matrix transformations, order matters here.
//
[self setTransform:CGAffineTransformMakeScale(2.0, 2.0)];
//[self setTransform:CGAffineTransformMakeTranslation(200.0, -300.0)];
// End animation block.
[UIView commitAnimations];
}
@end
////////////////////////////////////////////////////////////////////////////
How to avoid memory leaks in iPhone applications
Introduction
This article lists some tips to avoid memory leaks in your iPhone apps.
Ownership
Ownership is the overall idea behind how memory management should work on the iPhone. When an object has an owner, they are responsible for releasing the object when they have finished using it. An object can have more than one owner, and when it has no owners, it is set for de-allocation.
Ownership is made when creating an object with
alloc
, new
, or copy
, when calling retain
on an object, or when calling Cocoa functions that have Create or Copy in their name. There are two ways memory is released: either explicitly calling release
on an object, or using the auto-release pool.Behind ownership is a system called reference counting. Most objects in the iPhone SDK are strongly referenced, this means they use reference counting.
When you create an object, it will have a reference count of 1, and calling
retain
on an object will increment the reference count by 1. Callingrelease
will decrement the reference count by 1; when the reference count reaches zero, the object is de-allocated. Calling autorelease
instead of release
means the object will be de-allocated at a later time automatically.Objects can also be weakly referenced, meaning that a reference count isn't kept and the object will need to be de-allocated manually.
When should you use
retain
? When you want to prevent an object from being de-allocated before you use it.Every time you use
copy
, alloc
, retain
, or a Cocoa function that has Create or Copy in its name, you need to have a matching release
orautorelease
.The developer should think about objects in terms of ownership, and not worry about reference counts. If you have matching
retain
and release
calls, it is obvious that you will have matching +1 and -1 additions to the reference count.Note: It may be tempting to use
[object retainCount]
, but the values this returns can be misleading due to the behind the scenes code in the SDK. It is not recommended to manage memory this way.Auto-release
Objects set to auto-release mean that they do not need to be explicitly released because they will be released when an auto-release pool is popped. The iPhone has an auto-release pool that runs on the main thread which usually releases objects at the end of an event loop. When you create your own threads, you must create your own auto-release pool.
On the iPhone, there are convenience constructors; objects created with convenience constructors are set to auto-release.
Examples:
Collapse
NSString* str0 = @"hello";
NSString* str1 = [NSString stringWithString:@"world"];
NSString* str2 = str1;
An allocated object can be set to auto-release like this:
Collapse
NSString* str = [[NSString alloc] initWithString:@"the flash?"];
[str autorelease];
Or like this:
Collapse
NSString* str = [[[NSString alloc] initWithString:@"batman!"] autorelease];
Ownership for auto-released objects is relinquished when the pointer goes out of scope or when an auto-release pool is popped.
The built in auto-release pool is usually popped at the end of an event loop, but this may not be desired when you have a loop that is allocating a lot of memory in each iteration. In that case, you can create an auto-release pool in the loop. Auto-release pools can be nested so the objects allocated in the inner pool will be released when that pool is popped. In the example below, objects will be released at the end of each iteration.
Collapse
for (int i = 0; i < 10; ++i)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString* str = [NSString stringWithString:@"hello world"];
[self ProcessMessage: str];
[pool drain];
}
Note: At the time of writing, the iPhone doesn't support garbage collecting, so
drain
will work the same as release
. drain
is often used in cases where you want to port the program to OSX, or if garbage collecting is added to the iPhone later. drain
provides a hit to the garbage collector that memory is being released.Returning a pointer to an object
When following the rules of ownership, the developer needs to be aware of what functions have ownership of an object. This is an example of returning a pointer to an object and releasing it.
Wrong way
Collapse
- (NSMutableString*) GetOutput
{
NSMutableString* output = [[NSMutableString alloc] initWithString:@"output"];
return output;
}
- (void) Test
{
NSMutableString* obj = [self GetOutput];
NSLog(@"count: %d", [obj retainCount]);
[obj release];
}
In this example,
output
is owned by GetOutput
. Having Test
release obj
violates the rules in the Coccoa Memory Management Guide; this will not leak memory, but it is not a good practice because Test
shouldn't release an object it doesn't own.If
GetOutput
is called, the caller shouldn't need to know if the object returned from GetOutput
is retained or not. It is therefore free to retain and release the returned object without disrupting any other code in the application.Correct way
Collapse
- (NSMutableString*) GetOutput
{
NSMutableString* output = [[NSMutableString alloc] initWithString:@"output"];
return [output autorelease];
}
- (void) Test
{
NSMutableString* obj = [self GetOutput];
NSLog(@"count: %d", [obj retainCount]);
}
In the second example,
output
is set to auto-release when GetOutput
returns. The reference count for output
is decremented, and GetObject
is relinquishing its ownership of output
. The Test
function is now free to retain and release the object, and be sure it won't leak when it's done.In the example,
obj
is set to auto-release, so the Test
function will not have ownership of it when the function ends, but what if it wanted to store the object somewhere else?The object will then need to be retained by a new owner.
Setters
A setter function must retain the object it is storing, which means claiming ownership. If we want to create a setter function, we need to do two things before assigning a new pointer to our member variable.
In the function:
Collapse
- (void) setName:(NSString*)newName
First, we would decrement the reference count of our member variable:
Collapse
[name release];
This will allow the
name
object to be de-allocated if the reference count is zero, but it will allow any other owners of the object to continue to use the object.Then, we will increment the reference count of the new
NSString
object: Collapse
[newName retain];
So,
newName
will not be de-allocated when the setName
selector finishes. The object newName
now points to is different to the one name
points to, with a different reference count.Now we can set
name
to point to the newName
object: Collapse
name = newName;
But what if
name
and newName
are the same object? We can't release it with the possibility of it being de-allocated and then retain it!Simply retain the incoming object before releasing the stored object:
Collapse
[newName retain];
[name release];
name = newName;
Now if the objects are the same, it will up the reference count then subtract from it, causing it to stay the same before assigning it.
Another way to do this is to use the Objective-C properties.
A property for an object is declared like so:
Collapse
@property(nonatomic, retain) NSString *name;
nonatomic
means there is no blocking for multiple threads accessing the data at the same time.atomic
will lock the data for a single thread, but is slower, so it is not used unless necessary.retain
means that we want to retain thenewName
object.
Instead of
retain
, we could use copy
: Collapse
@property(nonatomic, copy) NSString *name;
This would be the same as a function like this:
Collapse
- (void) setName:(NSString*)newName
{
NSString* copiedName = [newName copy];
[name release];
name = copiedName;
[name retain];
[copiedName release];
}
Here,
newName
is copied to copiedName
, now copiedName
has a copy of the string and owns it. name
is released and copiedName
is assigned toname
. name
then retains the string, so both copiedName
and name
own it. Finally, copiedName
releases the object and name
is the only owner of the copied string.Setters like this are imported to retain the member object, if we had a function like this:
Collapse
- (void) Test
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// do something...
name = [self GetOutput];
// do something else...
NSLog(@"Client Name before drain: %@", name);
[pool drain];
NSLog(@"Client Name after drain: %@", name);
}
name
would be undefined after the call to drain
because name
will be released when the pool is drained.If we replaced the assignment with:
Collapse
[self setName:[self GetOutput]];
Then
name
will be owned by the class and be retained for use until release
is called.But when do we release the object?
Since
name
is a member variable, the safest place to release it is in the dealloc
function of the class it belongs to. Collapse
- (void)dealloc
{
[name release];
[super dealloc];
}
Note: As
dealloc
is not always called, relying on objects to be released in dealloc
to trigger something could be dangerous. On exit, the iPhone OS may clear all the application memory before dealloc
is called.When assigning an object with a setter, be careful of lines like this:
Collapse
[self setName:[[NSString alloc] init]];
name
will be set fine, but alloc
does not have a matching release
. A better way would be something like this: Collapse
NSString* s = [[NSString alloc] init];
[self setName:s];
[s release];
Or with
autorelease
: Collapse
[self setName:[[[NSString alloc] init] autorelease]];
Auto-Release pools
Auto-release pools will release objects that are assigned in between their allocation and drain functions.
In the function below, we have a function that has a loop. In this loop, we are assigning a copy of
NSNumber
to magicNumber
. We are also settingmagicNumber
to be auto-released. In this example, we want to drain the pool on each iteration (this can save memory for loops with a large amount of assignments). Collapse
- (void) Test
{
NSString* clientName = nil;
NSNumber* magicNumber = nil;
for (int i = 0; i < 10; ++i)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
magicNumber = [[self GetMagicNumber] copy];
[magicNumber autorelease];
if (i == [magicNumber intValue])
{
clientName = [self GetOutput];
}
[pool drain];
}
if (clientName != nil)
{
NSLog(@"Client Name: %@", clientName);
}
}
The problem is that
clientName
is assigned and released in the local auto-release pool. So when the loop finishes, clientName
has already been released and any further use of clientName
will be undefined.In this case, we can retain
clientName
after we assign it, and release it when we are done: Collapse
- (void) Test
{
NSString* clientName = nil;
NSNumber* magicNumber = nil;
for (int i = 0; i < 10; ++i)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
magicNumber = [[self GetMagicNumber] copy];
[magicNumber autorelease];
if (i == [magicNumber intValue])
{
clientName = [self GetOutput];
[clientName retain];
}
[pool drain];
}
if (clientName != nil)
{
NSLog(@"Client Name: %@", clientName);
[clientName release];
}
}
We have taken ownership of
clientName
from the period between the retain
and release
calls. By adding a pair of retain
and release
calls, we are saying that clientName
will not be released until release
is explicitly called.Collections
When an object is added to a collection, it will be owned by the collection.
In this example, we allocate a string; it now has one owner:
Collapse
NSString* str = [[NSString alloc] initWithString:@"Bruce Wayne"];
We then add it to the array; now it has two owners:
Collapse
[array addObject: str];
We can safely release the string, leaving it to be owned only by the array:
Collapse
[str release];
When a collection is released, it will release all its objects as well.
Collapse
NSMutableArray* array = [[NSMutableArray alloc] init];
NSString* str = [[NSString alloc] initWithString:@"Bruce Wayne"];
[array addObject: str];
[array release];
In the above example, we allocate an array, allocate a string, add the string to the array, and release the array. This leaves the string with one owner, and it will not be de-allocated until we call
[str release]
.Passing in pointers with threads
In this function, we are passing in the string
input
to the function DoSomething
, then releasing input
. Collapse
- (void) Test
{
NSMutableString* input = [[NSMutableString alloc] initWithString:@"batman!"];
[NSThread detachNewThreadSelector:@selector(DoSomething:) toTarget:self withObject:input];
[input release];
}
detatchNewThreadSelector
ups the reference count for the input
object and releases it when the thread finishes. This is why we can releaseinput
right after starting the thread no matter when the function DoSomething
starts or finishes. Collapse
- (void) DoSomething:(NSString*)str
{
[self performSelectorOnMainThread:@selector(FinishSomething:)
withObject:str waitUntilDone:false];
}
performSeclectorOnMainThread
will also retain the object passed in until the selector has finished.Auto-release pools are thread specific, so if we are creating auto-released objects on a new thread, we need to create an auto-release pool to release them.
Collapse
[NSThread detachNewThreadSelector:@selector(Process) toTarget:self withObject:nil];
This calls the function
Process
on a different thread. Collapse
- (void) Process
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSMutableString* output =
[[[NSMutableString alloc] initWithString:@"batman!"] autorelease];
NSLog(@"output: %@", output);
[self performSelectorOnMainThread:@selector(FinishProcess)
withObject:nil waitUntilDone:false];
[pool drain];
}
The object
output
is allocated and set to auto-release inside the auto-release pool, and will be released before the end of the function. Collapse
- (void) FinishProcess
{
NSMutableString* output =
[[[NSMutableString alloc] initWithString:@"superman?"] autorelease];
NSLog(@"output: %@", output);
}
There is an auto-release pool created for the main thread automatically, so in
FinishProcess
, we do not need to create an auto-release pool as this function runs on the main thread.Summary
To avoid memory leaks in your iPhone apps, it is important to keep in mind who owns each object that is allocated, when to relinquish that ownership, and keeping
retain
and release
calls in pairs. If you follow the rules of ownership, your apps will be more stable and you will cut down a lot of bug fixing time.
Subscribe to:
Posts (Atom)