How can I save objects on release when using Objective-C ARC with Xcode 4.2?

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___ () #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 

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?

+6
source share
2 answers

So, I figured out how to avoid this, although I'm still confused about why this matters. In every place where additional liberation took place, it was in a cycle. In those places, I took the property declaration from the for loop, assigned it to the local var, which is used in the for loop, and now it works fine! So the line that used to be:

 for (NSString *day in schedule1.daysOfWeek) 

I changed to two lines:

 NSArray *daysOfWeek = schedule1.daysOfWeek; for (NSString *day in daysOfWeek) 

Obviously, this will make a difference in the save / release calls that will be needed, but I don’t understand why it ultimately makes a difference in the long run ... If someone can shed some idea of ​​why I like it hear it!

0
source

ARC is all about object property. Do you have a strong pointer to the object you are referring to? If so, the object is saved.

When I converted my project to ARC, I also received a message sent to deallocated instance error - an error that did not appear in my code before ARC. The explanation was this: in my pre-ARC code, I had a memory leak. I saved the object and then did not release it. I later referred to a weak pointer (delegate pointer). When I switched to ARC, memory management was cleared, and therefore, when I no longer had a strong pointer to the object that was released. Therefore, when I tried to access it with an unsafe pointer, it crashed.

Just follow the graphs of ownership and drawing objects - this will help you track the error.

0
source

Source: https://habr.com/ru/post/903164/


All Articles