Finding and Fixing Memory Leaks in iOS Apps

47
Finding & Fixing Memory Leaks in iOS Apps ©2010 Streaming Colour Studios

description

Owen Goss's presentation from FITC Mobile 2010, given September 17, 2010. Learn about how memory management works in iOS, learn the gotchas that lead to so many leaks, and download the sample project that you can use to practice finding and fixing memory leaks in an iOS app: http://streamingcolour.com/presentations/memLeakExampleProject.zip Copyright 2010 Streaming Colour Studios

Transcript of Finding and Fixing Memory Leaks in iOS Apps

Page 1: Finding and Fixing Memory Leaks in iOS Apps

Finding & Fixing Memory Leaksin iOS Apps

©2010 Streaming Colour Studios

Page 2: Finding and Fixing Memory Leaks in iOS Apps

Me: Owen Goss5 years in console games industry

Streaming Colour Studios - Summer 2008

iPhone Games:

Dapple (Feb 2009)

Monkeys in Space (Nov 2009)

LandFormer (Jun 2010)

Chapter on iPhone Debugging

Page 3: Finding and Fixing Memory Leaks in iOS Apps

How many of you...

Page 4: Finding and Fixing Memory Leaks in iOS Apps

How many of you...

Already have an app in the App Store?

Page 5: Finding and Fixing Memory Leaks in iOS Apps

How many of you...

Already have an app in the App Store?

Are working on your first app?

Page 6: Finding and Fixing Memory Leaks in iOS Apps

How many of you...

Already have an app in the App Store?

Are working on your first app?

Are a Mac OS X developer?

Page 7: Finding and Fixing Memory Leaks in iOS Apps

How many of you...

Already have an app in the App Store?

Are working on your first app?

Are a Mac OS X developer?

Have never touched Objective-C before?

Page 8: Finding and Fixing Memory Leaks in iOS Apps

Don’t worry...www.streamingcolour.com/blog

This presentation and the example code I present will all be made available after the conference on my blog.

Page 9: Finding and Fixing Memory Leaks in iOS Apps

The Plan, Man

What’s a Memory Leak

Retain, Release & Stuff

Common Leaks

Finding & Fixing Leaks

- To start, I’ll talk specifically about what a leak is - I’ll give an overview of memory management in iOS- I’ll go through some common ways to introduce leaks- We’ll jump into Xcode with some sample code and actually find and fix a bunch of leaks.

Page 10: Finding and Fixing Memory Leaks in iOS Apps

What is a Memory Leak?

In order to talk about how memory leaks occur, how to fix them, and why they’re bad, we first need to define what they are.

Page 11: Finding and Fixing Memory Leaks in iOS Apps

A memory leak is a piece of allocated memory that nothing points to.

There are two important things here: 1) we’re talking about heap memory, or memory that’s allocated. Think: pointers. 2) We’re talking about a chunk of memory that’s “lost” on the heap. Nothing references it anymore.

Page 12: Finding and Fixing Memory Leaks in iOS Apps

What’s a Leak?

// a = 0x0032ba80MyClass* a = [[MyClass alloc] init];

// OR in C++// MyClass* a = new MyClass();

// OR in C// MyStruct* a = (MyStruct*)malloc(sizeof(MyStruct));

Heap (memory)

The previous slide stated that a leak involves a piece of allocated memory. This is how we allocate memory in Obj-C (and examples in C++ and C). This takes a chunk of heap memory of the size of our object and says “This memory is reserved”. The pointer, a, is just a number that stores the address of the object in memory so we can find it.

Page 13: Finding and Fixing Memory Leaks in iOS Apps

What’s a Leak?0x0...

// Memory leak!a = nil;

But if you change the value of the pointer, to point to say, nil, then the chunk of memory we allocated can no longer be found. However, no one told the OS that it could be freed, so it’s still there with no way to be cleaned up. This is a leak.

Page 14: Finding and Fixing Memory Leaks in iOS Apps

The good newsNo shared memory means you can’t leak system-wide memory

You can’t leak OS-wide memory on the iPhone.

Page 15: Finding and Fixing Memory Leaks in iOS Apps

The good newsOn exit ➟ app memory cleaned up

All memory you allocate during execution of your app is freed by the system when your app shuts down. This includes any leaks you introduce.

Page 16: Finding and Fixing Memory Leaks in iOS Apps

The worst-caseYou cause your own app to crash

In the worst-case, your leaks will cause you to run out of free memory and your app will crash. Obviously this is a bad thing, but you can’t kill the OS with your leaks.

Page 17: Finding and Fixing Memory Leaks in iOS Apps

Retain, Release& Stuff

Give a quick overview of memory management in iOS: retain & releaseStuff because we’ll talk about hidden retains, and some stuff like autorelease

Page 18: Finding and Fixing Memory Leaks in iOS Apps

iOS ➟ No Garbage Collection

For the Mac OS X/scripting language programmers in the room.

Page 19: Finding and Fixing Memory Leaks in iOS Apps

But...

Objects are reference counted

um... sort of...if done right

Page 20: Finding and Fixing Memory Leaks in iOS Apps

NSObject has a property: retainCount

Since all NS* and UI* classes in iOS are derived from NSObject, they also have retainCount. Any classes you create that subclass NSObject have a retainCount.But what does retainCount do?

Page 21: Finding and Fixing Memory Leaks in iOS Apps

if retainCount == 0 the object will be freed

The iOS memory manager uses the retainCount to determine when to free allocated memory. If the retainCount becomes 0, the memory is freed.

Page 22: Finding and Fixing Memory Leaks in iOS Apps

retain/release

[objectA retain] //objectA.retainCount++

[objectA release] //objectA.retainCount--

At its most basic, calling retain on an object pointer increases the retainCount by 1. Calling release decreases it by one. It gets more complicated, as we’ll see, but this is at the root of it all. This is what you MUST understand about memory in iOS.

Page 23: Finding and Fixing Memory Leaks in iOS Apps

retain/release

A

A

A

A

A.retainCount

1

2

1

0

B

B

B

B

C

C

Here we see some object A has been allocated in memory. Object B retains it with some pointer. Then object C also retains it with its pointer. The retain count is 2 once both B & C hold pointers to it. In order for A to get cleaned up, both B & C must release their pointers to A.

Page 24: Finding and Fixing Memory Leaks in iOS Apps

If you retain, you must release

So remember: retain increases the retainCount by one, release decreases the retainCount by 1. When the retainCount is 0, the object is freed. If you take one thing away from this talk, it is this. Think of it like parentheses that must always match.

Page 25: Finding and Fixing Memory Leaks in iOS Apps

“Hidden” retain/release

Here’s where things get a little more complicated. We saw before that calling retain on a pointer to an NSObject increases the retainCount, but there are calls in Obj-C that increase the retainCount “secretly”, or at least, quietly.

Page 26: Finding and Fixing Memory Leaks in iOS Apps

alloc

// objectA.retainCount = 1MyClass* objectA = [[MyClass alloc] init];

...

// objectA.retainCount = 0// free the memory[objectA release];

Let’s start at the beginning. The way you allocated memory for an object, in general, in Objective-C is with the “alloc” method. This is like calling new() in C++ or malloc() in C, but it’s reference counted. When you make an alloc call, it does an IMPLICIT retain on the pointer. Objects that are alloc’ed must be released at some point.

Page 27: Finding and Fixing Memory Leaks in iOS Apps

retain Property Attribute

@interface MyClass : NSObject {NSObject* objectA;

}

@property (nonatomic, retain) NSObject* objectA;

@end

Here we have a class called MyClass that has a property declaration for an NSObject instance called objectA. Note the retain property attribute.

Page 28: Finding and Fixing Memory Leaks in iOS Apps

retain Property Attribute// @property (nonatomic, retain) NSObject* objectA;

@implementation MyClass

@synthesize objectA;...

// objectA now points to objectB, and// objectB.retainCount++self.objectA = objectB;// Equiv to: [self setObjectA:objectB];

...@end

Note the use of the “self” keyword. This says that you want to use the property’s accessors to set the value of objectA. This is the same as calling [self setObjectA:objectB]. This is NOT just doing an assignment!

Page 29: Finding and Fixing Memory Leaks in iOS Apps

self.objectA = objectB;

// @property (nonatomic, retain) NSObject* objectA;

// It’s doing something like this...- (void)setObjectA:(NSObject*)objectB {

[objectA release];objectA = objectB;[objectA retain];

}

When you do this, two things happen: 1) if objectA was holding a reference to some object, that object gets an IMPLICIT release called on it. 2) an IMPLICIT retain is done on the new object (objectB)!

Page 30: Finding and Fixing Memory Leaks in iOS Apps

retain Property Attribute

// @property (nonatomic, retain) NSObject* objectA;

// objectB.retainCount++self.objectA = objectB;

// NO RETAIN IS DONE!objectA = objectB;

If you assign an object pointer to another WITHOUT the self keyword, the accessor of the property is NOT used, and therefore NO retain is done! When you omit the self keyword, only an assignment is done.

Page 31: Finding and Fixing Memory Leaks in iOS Apps

retain Property Attribute

// @property (nonatomic, retain) NSObject* objectA;

// objectA.retainCount--self.objectA = nil;

// NO RELEASE IS DONE!// Potential Memory Leak!objectA = nil;

Similarly, using the self keyword to set an object to nil does an IMPLICIT release on the object. But watch out, setting a pointer to nil without the self keyword COULD cause a memory leak.

Page 32: Finding and Fixing Memory Leaks in iOS Apps

autorelease pools

Autorelease pools are collections of objects. They exist in a stack of pools. When you call the autorelease method on an object, the object gets added to the top autorelease pool. Read the Apple documentation on Autorelease pools for more information.

Page 33: Finding and Fixing Memory Leaks in iOS Apps

autorelease pools

on autorealese pool dealloc:

But an autorelease pool is an object itself. When it gets deallocated, it calls release on every object inside it. iOS creates an autorelease pool every time through the run loop, and releases it at the end (as well as at other points during execution, like touch events - see Apple docs).

Page 34: Finding and Fixing Memory Leaks in iOS Apps

autorelease pools

- (MyClass*) getNewMyClass {// mc.retainCount = 1MyClass* mc = [[MyClass alloc] init];

// When the current autorelease pool gets// dealloc’ed, the retainCount will decreasereturn [mc autorelease];

}

Here’s a good way to use autorelease in an iOS app: returning an allocated object. The calling code can choose to retain the returned object if it wants to keep it long-term. But if it only needs it right now, it can just use it and ignore it, and the autorelease pool will clean it up later. Calling code doesn’t need to worry about it.

Page 35: Finding and Fixing Memory Leaks in iOS Apps

NSMutableArray (e.g.)

NSMutableArray* array = [NSMutableArray array];

// objectA.retainCount = 1MyClass* objectA = [[MyClass alloc] init];// objectA.retainCount = 2[array addObject:objectA];

Lots of container class methods will retain your objects for you. This is almost always the desired behaviour. Just remember that in order to decrease your retainCount, your object either needs to be removed from the array, or the array needs to be destroyed. Read the docs! Know what retains, what doesn’t.

Page 36: Finding and Fixing Memory Leaks in iOS Apps

NSMutableArray (e.g.)

NSMutableArray* array = [NSMutableArray array];

// objectA.retainCount = 1MyClass* objectA = [[MyClass alloc] init];// objectA.retainCount = 2[array addObject:objectA];

Lesson: Read the documentation!

Lots of container class methods will retain your objects for you. This is almost always the desired behaviour. Just remember that in order to decrease your retainCount, your object either needs to be removed from the array, or the array needs to be destroyed. Read the docs! Know what retains, what doesn’t.

Page 37: Finding and Fixing Memory Leaks in iOS Apps

alloc-init vs. other

// array1.retainCount = 1// You must release to free itNSArray* array1 = [[NSArray alloc] init];

// array2 is autoreleased// You must retain to hold on to itNSArray* array2 = [NSArray array];

Most of the built-in classes in iOS have two versions of initializers. One version will start with init*, while the other starts with the class name (e.g. array, string, dictionary). The init function always requires an alloc (and that alloc does the implicit retain). The other returns a new object that is autoreleased.

Page 38: Finding and Fixing Memory Leaks in iOS Apps

All Clear?Moving on...

Page 39: Finding and Fixing Memory Leaks in iOS Apps

Some Common Leaks

Let’s take a look at some common memory leaks and how to fix them. Once we’ve gone through these, we’ll jump into Xcode and the Leaks tool and do it for real.

Page 40: Finding and Fixing Memory Leaks in iOS Apps

Leak

// @property (nonatomic, retain) MyClass* objectA;

- (void)foo {// retainCount = 2self.objectA = [[MyClass alloc] init];

}

-(void)dealloc {// retainCount = 1[objectA release];[super dealloc];

}

Note that the line in foo actually does 2 retains (self. and alloc). This object will leak objectA when it gets dealloc’ed. When the object gets cleaned up, objectA still has a retainCount of 1, but nothing will be pointing to it anymore: leak.

Page 41: Finding and Fixing Memory Leaks in iOS Apps

Leak Fixed// @property (nonatomic, retain) MyClass* objectA;

- (void)foo {// retainCount = 1MyClass* obj = [[MyClass alloc] init];// retainCount = 2self.objectA = obj;// retainCount = 1[obj release];

}

-(void)dealloc {// retainCount = 0[objectA release];[super dealloc];

}2 retains, 2 releases = leak fixed.

Page 42: Finding and Fixing Memory Leaks in iOS Apps

Leak// @property (nonatomic, retain) MyClass* objectA;

- (void)foo {MyClass* obj = [[MyClass alloc] init];self.objectA = obj;[obj release];// retainCount = 1

}

-(void)dealloc {// retainCount = 1[super dealloc];

}

So here we’ve done everything right...but oops! Forgot the release call in the dealloc method. This is probably the most common leak you will encounter in your code, but luckily it’s the easiest to fix.

Page 43: Finding and Fixing Memory Leaks in iOS Apps

Leak Fixed// @property (nonatomic, retain) MyClass* objectA;

- (void)foo {MyClass* obj = [[MyClass alloc] init];self.objectA = obj;[obj release];// retainCount = 1

}

-(void)dealloc {// retainCount = 0[objectA release];[super dealloc];

}

2 retains, 2 releases = leak fixed.

Page 44: Finding and Fixing Memory Leaks in iOS Apps

Build & AnalyzeTry it, it might work for you.*

* or wait for Xcode 4

Build & Analyze is a build option in Xcode that runs the CLANG static analyzer. It can sometimes pick up memory leaks in your code at compile time! I’ve never had much luck getting it to work properly in Xcode 3.x, but give it a try in Xcode 4.

Page 45: Finding and Fixing Memory Leaks in iOS Apps

Leaks InstrumentLeaks will show you the allocation that leaks. It’s up to you to figure out why it wasn’t released.

This is what you need to understand about the Leaks tool. It’s an amazing tool, but it can’t do everything for you. You still need to understand your own code and how memory management works.

Page 46: Finding and Fixing Memory Leaks in iOS Apps

Xcode Time!http://streamingcolour.com/presentations/memLeakExampleProject.zip

Now that we’ve got all that under our belts, I’m going to jump into Xcode, boot up the Leaks Instrument and show you how to find these things for real. Download the project here: http://streamingcolour.com/presentations/memLeakExampleProject.zip