I'm working on an iphone application and having some trouble with memory leaks. I've read some docs about garbage collection that it make it sound simple but I must be missing something. I've got a viewController that needs access to an array which may need to repopulated from time to time. Here is a simplified version of what I have:
//AppDelegate.m
- (NSMutableArray *)getMathFacts {
//Some database stuff
NSMutableArray * arr = [[NSMutableArray alloc] init];
while (sqlite3_step(math_fact_statement) == SQLITE_ROW) {
[arr addObject:[[NSNumber numberWithInt:sqlite3_column_int(math_fact_statement, 0)] autorelease]];
}
return arr;
}
//ViewController.h
#interface ReviewViewController : UIViewController {
NSMutableArray *reviewArr;
}
#property (retain, nonatomic) NSMutableArray *reviewArr;
//ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[self loadMathFacts];
}
- (void)loadMathFacts {
self.reviewArr = [appDelegate getMathFacts];
}
- (void)loadAllMathFacts {
self.reviewArr = [appDelegate getAllMathFacts];
}
-(IBAction) getAll {
[reviewArr release];
[self loadAllMathFacts]
}
GetAllMathFacts is similar to getMathFacts, it just has a different SQL statement.
When I run this checking for Leaks it is like a sieve. It seems like something simple, but I feel like I've tried everything and it just moves the leak around.
Any advice would be appreciated.
Thanks
The iPhone OS actually doesn't have garbage collection. What you're doing with retain/release is called reference counting.
The solution to your problem is probably to make getMathFacts return an autoreleased object (change return arr; to return [arr autorelease];), because the definition of the property reviewArr is probably something like #property (retain) NSArray *reviewArr;, which means every time you call self.reviewArr = something;, something is retained, which means after you set reviewArr in loadMathFacts and loadAllMathFacts, reviewArr is retained one time too much.
In getMathFacts, you do a
NSMutableArray * arr = [[NSMutableArray alloc] init];
that array is owned by you. It has a retain count of 1. Later when
- (void)loadMathFacts {
self.reviewArr = [appDelegate getMathFacts];
}
that same array is now retained by reviewArr and the retain count goes to 2.
When you then do a
-(IBAction) getAll {
[reviewArr release];
[self loadAllMathFacts]
}
in the first release statement, your array is now getting released once and the retain count goes to 1. In [self loadAllMathFacts]
- (void)loadAllMathFacts {
self.reviewArr = [appDelegate getAllMathFacts];
}
self.reviewArr will release the the array, before retaining a new array. After this release the retain count is down to 0. I don't see a leak here. Maybe in -getAllMathFacts?
Now, one thing I would change to make things look a little better is this:
- (void)loadMathFacts {
NSMUtableArray array = [appDelegate getMathFacts];
self.reviewArr = array;
[array release];
}
- (void)loadAllMathFacts {
NSMUtableArray array = [appDelegate getAllMathFacts];
self.reviewArr = array;
[array release];
}
-(IBAction) getAll { // you don't need to release in here
[self loadAllMathFacts]
}
Plus you should rename your get method calls to use "new" instead of "get" since the convention is that if you return something that is owned by the caller, it should have new or copy in the name of the method.
As another answerer said, you could use autorelease, although I'd rather use autorelease in other situations. But if you were to do it with autorelease this is how I'd do it:
//AppDelegate.m
- (NSMutableArray *)getMathFacts {
//Some database stuff
NSMutableArray * arr = [[NSMutableArray alloc] init];
while (sqlite3_step(math_fact_statement) == SQLITE_ROW) {
[arr addObject:[[NSNumber numberWithInt:sqlite3_column_int(math_fact_statement, 0)] autorelease]];
}
return [arr autorelease];
}
do the same with -getAllMathFacts. You should still change the code to be more like above, except you don't have to release after doing the self.reviewArray, and you don't have to change the name of the methods. What you have to remember is that if even autoreleased objects can leak if you call retain on them and then forget about them. The nice thing about autorelease is that the object is kept around for the duration of a runloop, long enough to hand it to some other object and let them decide whether they want to retain it or let it expire.
I hope I haven't missed anything. Go through my logic, and feel free to poke holes in it or ask questions. I can easily have missed something.
By the way, self.reviewArr when you have:
#property (retain, nonatomic) NSMutableArray *reviewArr;
does the following:
- (void)setReviewArr:(NSMutableArray*)array
{
NSMutableArray* oldReviewArr = reviewArr;
reviewArr = [array retain];
[oldReviewArr release];
}
As of Xcode 3.2 there is support for running the Clang analyzer. To do this choose Build->Build & Analyze. This will run the analyzer which is really a wonderful tool for finding reference counting issues.
For Xcode prior to 3.2, this might help: Using Clang Static Analyzer from within XCode
Related
I have a property of
#property (nonatomic, strong) NSMutableArray *timesArray;
It is used to populate the data in my UITableView. When I want to clear my view, I do this:
- (void)clearView {
self.nameField.text = #"";
self.noteField.text = #"";
if ([_timesArray count] > 0) {
[self.timesArray removeAllObjects];
[self.customTableView reloadData];
}
}
The removeAllObjects causes a crash. I am not sure why. I looked around and a lot of posts talk about an object being overreleased. How is that happening if I'm using ARC and not calling retain/release on any objects. My _timesArray just holds NSDate objects I get from a UIDatePicker.
My stack trace looks like:
My insertPill looks like:
- (void)insertPill:(id)sender {
//[[NSNotificationCenter defaultCenter] postNotificationName:InsertPillNotification object:self];
[self clearView];
}
If I don't removeAllObjects, and just do:
NSMutableArray *emptyArray = [[NSMutableArray alloc] initWithCapacity:0];
self.timesArray = emptyArray;
This works. But I'd still like to know why by removing the objects it does not work.
Edit: I initialize the array in viewDidLoad:
_timesArray = [[NSMutableArray alloc] initWithCapacity:0];
When I want to add a new object to the array, I do this:
NSMutableArray *tempUnsortedArray = [[NSMutableArray alloc] initWithArray:_timesArray];
[tempUnsortedArray addObject:_datePicker.date];
self.timesArray = tempUnsortedArray;
I'm not sure if the way I'm adding data the array is causing the issue or not.
You're getting a doesNotRecognizeSelector: exception. This probably means that the object you think is a NSMutableArray is not really one. It is probably an NSArray. Where are you assigning the object?
To start debugging the issue, po the object before calling removeAllObjects. What type of object is it reported as?
Otherwise it could be possible that there is a non NSObject element in timesArray.
#property (nonatomic, strong) NSMutableArray *timesArray;
if ([_timesArray count] > 0)
It seems, you syntesize your property like this:
#syntesize timesArray = _timesArray;
You chacking count of _timesArray, but removing object from timesArray. I never set new name for my properties and don't sure how it works, but I dont like it.
I have the following questions about allocating properties in Objective-C,
if I have the following property:
#property (nonatomic, retain) NSArray *arr;
#synthize arr=_arr;
should I allocate this array like:
arr=[[NSArray alloc]init];
and if I should to, where is the best place to to allocate it?
I know I should release it, but retaining a property will increase its retain count by 1 and allocating it will increase it by another 1, am I right.
You can allocate the array in two ways:
Set the ivar directly with a retained value, like this:
_arr = [[NSArray alloc] init];
Set the property with an autoreleased value like this:
self.arr = [NSArray array];
You can do this in your class's init method, although you've not said what superclass this is using so I'm not sure how the init method should look. If it's an NSObject, it will look like this:
- (id)init
{
if ((self = [super init]))
{
self.arr = [NSArray array];
}
return self;
}
But if it's a UIViewController, you'll need to use initWithNibName:bundle, like this:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)bundleOrNil
{
if ((self = [super itWithNibName:nibNameOrNil bundle:bundleOrNil]))
{
self.arr = [NSArray array];
}
return self;
}
Then you need to release it in your dealloc method, like this:
- (void)dealloc
{
[_arr release];
[super dealloc];
}
However, you should bear in mind that NSArrays can't be changed once they're created, so you probably either want to initialise it with some objects, like this:
self.arr = [NSArray arrayWithObjects:object1, object2, nil];
Or if you want to add objects to it later, you should define it as an NSMutableArray, like this:
#property (nonatomic, retain) NSMutableArray *arr;
And init it with:
self.arr = [NSMutableArray array];
That will let you add objects to it later.
By the way, if you get errors in your dealloc method, it probably means that your project is using ARC, which is a new technology in iOS 5 that means you don't need to write release and retain statements any more.
If you aren't using ARC I suggest you do so because it will save you having to worry about this retain/release stuff - any iOS developer who is just starting out now should really use ARC for every project, but because it was only introduced in iOS 5, a lot of the Objective-C books don't cover it.
I've tried numerous different attempts at it but always ends in leaks or errors. So here is my code, with the allocating and releasing bits taken out. I'd like to know how people suggest i should go about doing this?
.h
#import "MatchingColors.h"
#interface MagicSchemeView : UIViewController {
NSMutableArray *colors;
}
.m
colors = [MatchingColors monochromaticWithH:h S:s B:b WithComplementary:NO];
Then in MatchingColors.m:
+(NSMutableArray *)monochromaticWithH:(float)h S:(float)s B:(float)b WithComplementary:(BOOL)complementary {
return result;
}
Like i say, my attempts at allocating and releasing here seem to be going wrong. Ideas?
This should work
A place for the [colors release]; would be after you're done with it. Which would be as soon as you know you don't need it, or would be done on dealloc. Make sure dealloc is a last resort to put this release.
.m:
colors = [[MatchingColors monochromaticWithH:h S:s B:b WithComplementary:NO] retain];
+(NSMutableArray *)monochromaticWithH:(float)h S:(float)s B:(float)b WithComplementary:(BOOL)complementary
{
NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];
// Create the result here
return result;
}
Anyone able to help re what is the source of the leaky object for this code:
My application compiles without any ANALYZE issues. When I run PROFILER and look at Memory Leaks I see leaks appears. One of these leaks is in my WeekendEvent object. There are 3 Leaked Block row items in instruments per the below (I've noted in the code where these point to):
Malloc +1
Retain +2
Release +1
Question - I assume this implies there is a release, however where would this leak be from. The segments of my code that clicking through on instruments highlights is below. To me it seems OK in that:
the WeekendEvent I create I release after passing into the addWeekendEvent method
in the addWeekendEvent it just adds it to a NSMutableArray, and hence I thought the arrange does any memory management for it's object it contains
I do release the NSMutableArray in the dealloc too
Key Source Code Bits & What Instuments Highlights
// ------Weekends Method (plural)-----
WeekendEvent *weEvent = [[WeekendEvent alloc] init]; // [INSTRUMENTS - 87.5%]
[week addWeekendEvent:weEvent]; // [INSTRUMENTS - 12.5%]
[weEvent release];
//------Weekend *.h ------------
#interface Weekend : NSObject {
NSMutableArray* _events;
}
- (void)addWeekendEvent:(WeekendEvent*)weEvent;
#property (nonatomic, retain) NSMutableArray* events;
#end
//------Weekend *.m -------------
- (void)addWeekendEvent:(WeekendEvent*)weEvent {
[self.events addObject:weEvent];
}
- (void) dealloc {
[_events release];
[super dealloc];
}
EDIT: Some additional code re how the "week" variable above was created/used - so in the Weekends Method the code I posted was within a for loop - the code with the for loop shown therefore was:
for (Weekend *week in self.items) {
// do pass "week.start" to some methods (which is an NSDate) - don't think this would be relevant though?
WeekendEvent *weEvent = [[WeekendEvent alloc] init]; // [INSTRUMENTS - 87.5%]
[week addWeekendEvent:weEvent]; // [INSTRUMENTS - 12.5%]
[weEvent release];
}
// Note - self.items I checked is "released" in the dealloc method
EDIT 2 - Just to confirm, it is an "WeekendEvent" instance that Instruments highlights in it's "leaked objects" column. Just in case this wasn't clear.
EDIT 3 - Re how I setup the items variable - key code bits are:
#interface Weekends : NSObject {
NSMutableArray* _items;
}
#property (nonatomic, retain) NSMutableArray* items;
#synthesize items = _items;
- (void) dealloc {
[_items release];
[super dealloc];
}
The memory management in the code you show is correct, assuming that the rest of your Weekend class looks something like this:
#synthesize events = _events;
- (id)init {
if ((self = [super init]) == nil) { return nil; }
_events = [[NSMutableArray alloc] initWithCapacity:0];
return self;
}
Also, the instruments results match all of your code:
Malloc +1 == WeekendEvent *weEvent = [[WeekendEvent alloc] init];
Retain +2 == [week addWeekendEvent:weEvent];
Release +1 == [weEvent release];
Based on that logic, the most likely candidate is that your week object is not properly released. You haven't shown the code that explains how it was created, but I do notice that the code you did post is for a Weekend class. Are you sure week is not of a different type?
I'm getting this error when trying to see the contents of a NSMutableArray:
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_PROTECTION_FAILURE at address: 0x00000021
0x94d5a688 in objc_msgSend ()
ViewController.h:
#interface PeopleViewController : UITableViewController {
NSMutableArray *people;
}
#property (nonatomic, retain) NSMutableArray *people;
ViewController.m:
#implementation PeopleViewController
#synthesize people;
In viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
// initialize our people array with an autoreleased object
people = [NSMutableArray array];
... Populate the people array with Person objects.
}
When I'm at the point where I'm modifying the contents of a cell in the tableview, I'm unable to access the people array in gdb when typing 'po self.people':
Person *person = [[Person alloc] init];
person = [self.people objectAtIndex: indexPath.row]; // <--- 'po self.people' called
cell.textLabel.text = person.personName;
Any ideas why I can't access it?
The line
people = [NSMutableArray array];
returns an autoreleased array that will be released on the next iteration of the current run loop. You should retain that:
people = [[NSMutableArray array] retain];
and of course release it in your dealloc method.
However: Apple engineers have often mentioned in conferences to avoid autoreleased instances like this whenever possible in the iPhone, for performance reasons. Try using alloc/init instead:
people = [[NSMutableArray alloc] initWithCapacity:1];
with the corresponding release in the dealloc method. In this case you don't even need to retain (init returns an instance with a retain count of 1, which is what you need).
And justin's comment is correct: you should do this instead:
Person *person = [people objectAtIndex:indexPath.row];
cell.textLabel.text = person.personName;
and this should work.
is indexPath.row > [people count]?
Also, why are you doing this:
Person *person = [[Person alloc] init]
You're allocating memory, and then pointing to completely different memory.
You can avoid having to fuss with retaining properties by using the self notation to call the accessor and setter methods created by the #synthesize directive.
When you set the people property directly in viewDidLoad it sets the property but does nothing for memory management. However, if you set it with self.people you actually call the synthesized setter method that because of the retain setting of the #property directive will automatically retain the assigned array.
As an aside, I would recommend always using -[NSMutableArray initWithCapacity:] instead of a bare init. It is the actual initializer for the class. You can call it with just '1' if you don't know how big it will be. In the past, I have seen odd problem arise from just using bare init.