title: How can I keep objects from being released when using Objective-C ARC with Xcode 4.2?
body: <p>ETA: See the bottom for some more info I got by Profiling the app.</p>
  
  <p>I have an iPhone app that I just converted to use ARC, and now I'm getting several errors because of zombie objects.  Before I switched, I was manually retaining them, and everything was fine.  I can't figure out why ARC isn't retaining them.  The objects are declared as strong properties, and referenced using dot notation.  This is happening in several places, so I think I must have a fundamental misunderstanding of ARC/memory management somewhere.</p>
  
  <p>Here's an example that's particularly frustrating.  I have an NSMutableArray of 3 objects.  Each of those objects has a property that's also an NSMutableArray, which in this case always has a single object.  Finally, that object has the property that gets released.  The reason it's frustrating is that it only happens with the 3rd object from the original array.  The first 2 objects are always completely fine.  It just doesn't make sense to me how the property of one object would be released when the same property of similar objects created and used in the same way aren't.</p>
  
  <p>The array is stored as a property on a UITableViewController:</p>
  
  <pre><code>@interface GenSchedController : UITableViewController &lt;SectionHeaderViewDelegate&gt;
  
  @property (nonatomic, strong) NSArray *classes;
  
  @end
  
  @implementation GenSchedController
  
  @synthesize classes;
  </code></pre>
  
  <p>The objects that are stored in the <code>classes</code> array are defined as:</p>
  
  <pre><code>@interface SchoolClass : NSObject &lt;NSCopying, NSCoding&gt;
  
  @property (nonatomic, strong) NSMutableArray *schedules;
  
  @end
  
  @implementation SchoolClass
  
  @synthesize schedules;
  </code></pre>
  
  <p>The objects that are stored in the <code>schedules</code> array are defined as:</p>
  
  <pre><code>@interface Schedule : NSObject &lt;NSCopying, NSCoding&gt;
  
  @property (nonatomic, strong) NSMutableArray *daysOfWeek;
  
  @implementation Schedule
  
  @synthesize daysOfWeek;
  </code></pre>
  
  <p><code>daysOfWeek</code> is what is getting released.  It just contains several NSStrings.</p>
  
  <p>I can see that during <code>viewDidLoad</code> that all the objects are fine, with no zombies.  However, when I tap one of the table cells, and set a breakpoint on the first line of <code>tableView:didSelectRowAtIndexPath:</code>, it has already been released.  The specific line that throws the error is the <code>@synthesize daysOfWeek;</code> that is called after the 3rd "for" loop below:</p>
  
  <pre><code>for (SchoolClass *currentClass in self.classes) {
      for (Schedule *currentSched in currentClass.schedules) {
          for (NSString *day in currentSched.daysOfWeek)
  </code></pre>
  
  <p>But, again, this only happens on the last Schedule of the last SchoolClass.</p>
  
  <p>Can anyone point me in the right direction in getting my app to work correctly with ARC?</p>
  
  <p>As requested, here's more info.  First, the stack trace when the exception is thrown:</p>
  
  <pre><code>#0  0x01356657 in ___forwarding___ ()
  #1  0x01356522 in __forwarding_prep_0___ ()
  #2  0x00002613 in __arclite_objc_retainAutoreleaseReturnValue (obj=0x4e28b80) at /SourceCache/arclite_host/arclite-4/source/arclite.m:231
  #3  0x0000d2fc in -[Schedule daysOfWeek] (self=0x4e28680, _cmd=0x220d6) at /Users/Jesse/Documents/Xcode/Class Test/Schedule.m:18
  #4  0x0001c161 in -[SchedulesViewController doesScheduleOverlap:schedule2:withBufferMinutes:] (self=0x692b210, _cmd=0x22d58, schedule1=0x4e28680, schedule2=0x4e27f10, buffer=15) at /Users/Jesse/Documents/Xcode/Class Test/Classes/SchedulesViewController.m:27
  #5  0x0001c776 in -[SchedulesViewController doesScheduleOverlap:schedule2:] (self=0x692b210, _cmd=0x22d9b, schedule1=0x4e28680, schedule2=0x4e27f10) at /Users/Jesse/Documents/Xcode/Class Test/Classes/SchedulesViewController.m:53
  #6  0x0001cf8c in -[SchedulesViewController getAllowedSchedules] (self=0x692b210, _cmd=0x22dca) at /Users/Jesse/Documents/Xcode/Class Test/Classes/SchedulesViewController.m:78
  #7  0x0001d764 in -[SchedulesViewController viewDidLoad] (self=0x692b210, _cmd=0x97cfd0) at /Users/Jesse/Documents/Xcode/Class Test/Classes/SchedulesViewController.m:121
  #8  0x00620089 in -[UIViewController view] ()
  #9  0x0061e482 in -[UIViewController contentScrollView] ()
  #10 0x0062ef25 in -[UINavigationController _computeAndApplyScrollContentInsetDeltaForViewController:] ()
  #11 0x0062d555 in -[UINavigationController _layoutViewController:] ()
  #12 0x0062e7aa in -[UINavigationController _startTransition:fromViewController:toViewController:] ()
  #13 0x0062932a in -[UINavigationController _startDeferredTransitionIfNeeded] ()
  #14 0x00630562 in -[UINavigationController pushViewController:transition:forceImmediate:] ()
  #15 0x006291c4 in -[UINavigationController pushViewController:animated:] ()
  #16 0x000115d5 in -[GenSchedController tableView:didSelectRowAtIndexPath:] (self=0x4c57b00, _cmd=0x9ac1b0, tableView=0x511c800, indexPath=0x4e2cb40) at /Users/Jesse/Documents/Xcode/Class Test/Classes/GenSchedController.m:234
  #17 0x005e7b68 in -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] ()
  #18 0x005ddb05 in -[UITableView _userSelectRowAtPendingSelectionIndexPath:] ()
  #19 0x002ef79e in __NSFireDelayedPerform ()
  #20 0x013c68c3 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ ()
  #21 0x013c7e74 in __CFRunLoopDoTimer ()
  #22 0x013242c9 in __CFRunLoopRun ()
  #23 0x01323840 in CFRunLoopRunSpecific ()
  #24 0x01323761 in CFRunLoopRunInMode ()
  #25 0x01aa71c4 in GSEventRunModal ()
  #26 0x01aa7289 in GSEventRun ()
  #27 0x0057ec93 in UIApplicationMain ()
  #28 0x0000278d in main (argc=1, argv=0xbffff5fc) at /Users/Jesse/Documents/Xcode/Class Test/main.m:16
  </code></pre>
  
  <p>And the exact exception is <code>Class Test[82054:b903] *** -[__NSArrayM respondsToSelector:]: message sent to deallocated instance 0x4e28b80</code></p>
  
  <p>And here's the code where everything gets created, loading from disk:</p>
  
  <pre><code>NSString *documentsDirectory = [FileManager getPrivateDocsDir];
  
  NSError *error;
  NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:&amp;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;
  </code></pre>
  
  <p>The initWithCoder: methods are really straightforward.  First for SchoolClass:</p>
  
  <pre><code>- (id)initWithCoder:(NSCoder *)decoder {
      self.name = [decoder decodeObjectForKey:@"name"];
      self.description = [decoder decodeObjectForKey:@"description"];
      self.schedules = [decoder decodeObjectForKey:@"schedules"];
  
      return self;
  }
  </code></pre>
  
  <p>And for Schedule:</p>
  
  <pre><code>- (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;
  }
  </code></pre>
  
  <p>I tried running a Profile on the app using the Zombies template, and compared the object that over-releases to one of the others in the array that is fine.  I can see that on the line <code>for (NSString *day in currentSched.daysOfWeek)</code>, that it goes into the daysOfWeek getter, which does a <code>retain autorelease</code>.  Then after it returns from the getter, it does another <code>retain</code> (Presumably to hold ownership while the loop is processed), and then a <code>release</code>.  All of that is the same for the problem object as for the healthy object.  The difference is that immediately after that <code>release</code>, the problem object calls <code>release</code> AGAIN.  This actually doesn't cause a problem immediately, because the autorelease pool hasn't drained yet, but once it does, the retain count drops to 0, and then of course the next time I try to access it, it's a zombie.</p>
  
  <p>What I can't figure out is WHY that extra <code>release</code> is getting called there.  Because of the outer for loops, the number of times that <code>currentSched.daysOfWeek</code> gets called does vary - it gets called 3 times on the problem object and 5 on the healthy object, but the extra <code>release</code> occurs the first time it's called, so I'm not sure how that would affect it.</p>
  
  <p>Does this extra info help anyone understand what's happening?</p>