ETA: see below for more information that I got through profiling the application.
I have an iPhone app that I just converted to use ARC, and now I get some errors due to zombie objects. Before I switched, I manually saved them, and everything was in order. I cannot understand why ARC does not save them. Objects are declared as strong properties and refer to point notation. This happens in several places, so I think I should have a fundamental misunderstanding of ARC / memory management somewhere.
Here is an example that is particularly frustrating. I have an NSMutableArray of 3 objects. Each of these objects has a property that also has an NSMutableArray, which in this case always has one object. Finally, this object has a property that is freed. The reason this is frustrating is because it only happens to the third object from the original array. The first 2 objects are always completely beautiful. It just doesn't make sense to me how a property of one object will be released when the same property of similar objects created and used in the same way will not.
The array is stored as a property in the UITableViewController:
@interface GenSchedController : UITableViewController <SectionHeaderViewDelegate> @property (nonatomic, strong) NSArray *classes; @end @implementation GenSchedController @synthesize classes;
Objects that are stored in the classes array are defined as:
@interface SchoolClass : NSObject <NSCopying, NSCoding> @property (nonatomic, strong) NSMutableArray *schedules; @end @implementation SchoolClass @synthesize schedules;
Objects that are stored in the schedules array are defined as:
@interface Schedule : NSObject <NSCopying, NSCoding> @property (nonatomic, strong) NSMutableArray *daysOfWeek; @implementation Schedule @synthesize daysOfWeek;
daysOfWeek is what daysOfWeek freed. It just contains a few NSStrings.
I see that during viewDidLoad all objects are in order, without zombies. However, when I click one of the cells in the table and set a breakpoint in the first row of tableView:didSelectRowAtIndexPath: it has already been released. The specific line that throws the error is @synthesize daysOfWeek; , which is called after the third "for" loop below:
for (SchoolClass *currentClass in self.classes) { for (Schedule *currentSched in currentClass.schedules) { for (NSString *day in currentSched.daysOfWeek)
But, again, this only happens on the last list of the last SchoolClass.
Can someone point me in the right direction so that my application works correctly with ARC?
As requested, more information here. First, stack trace when an exception is thrown:
#0 0x01356657 in ___forwarding___ ()
And the exact exception is the Class Test[82054:b903] *** -[__NSArrayM respondsToSelector:]: message sent to deallocated instance 0x4e28b80
And here is the code where everything is created, loading from disk:
NSString *documentsDirectory = [FileManager getPrivateDocsDir]; NSError *error; NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:&error]; // Create SchoolClass for each file NSMutableArray *classesTemp = [NSMutableArray arrayWithCapacity:files.count]; for (NSString *file in files) { if ([file.pathExtension compare:@"sched" options:NSCaseInsensitiveSearch] == NSOrderedSame) { NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:file]; NSData *codedData = [[NSData alloc] initWithContentsOfFile:fullPath]; if (codedData == nil) break; NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:codedData]; SchoolClass *class = [unarchiver decodeObjectForKey:@"class"]; [unarchiver finishDecoding]; class.filePath = fullPath; [classesTemp addObject:class]; } } self.classes = classesTemp;
InitWithCoder Methods: It is very simple. First for SchoolClass:
- (id)initWithCoder:(NSCoder *)decoder { self.name = [decoder decodeObjectForKey:@"name"]; self.description = [decoder decodeObjectForKey:@"description"]; self.schedules = [decoder decodeObjectForKey:@"schedules"]; return self; }
And for the schedule:
- (id)initWithCoder:(NSCoder *)decoder { self.classID = [decoder decodeObjectForKey:@"id"]; self.startTime = [decoder decodeObjectForKey:@"startTime"]; self.endTime = [decoder decodeObjectForKey:@"endTime"]; self.daysOfWeek = [decoder decodeObjectForKey:@"daysOfWeek"]; return self; }
I tried running Profile in the application using the Zombies template and compared an object that re-releases one of the others in the array, which is fine. I see that on the for (NSString *day in currentSched.daysOfWeek) line for (NSString *day in currentSched.daysOfWeek) it gets into getOfWeek, which does retain autorelease . Then, after it returns from the getter, it executes another retain (Presumably to preserve ownership of the processing loop), and then a release . All this is the same for the object of the task, as for a healthy object. The difference is that immediately after this release the problem object calls release AGAIN. This actually does not cause a problem right away, because the resource pool is not exhausted yet, but as soon as it does, the number of deductions drops to 0, and then, of course, the next time I try to access it, itβs a zombie.
What I cannot understand is WHY that an extra release added there. Due to external to the loops, the number of times that the currentSched.daysOfWeek call is called changes - it is called 3 times on the problem object and 5 on the healthy object, but an additional release occurs on the first call, so I'm not sure how this will affect it.
Does this additional information help anyone understand what is happening?