在使用Objective-C ARC和Xcode 4.2时,如何防止对象被释放?

时间:2022-09-07 07:45:11

ETA: See the bottom for some more info I got by Profiling the app.

埃塔:更多关于我通过分析这个应用程序得到的信息,请看底部。

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.

我有一个iPhone应用程序,我刚刚转换成使用ARC,现在由于僵尸对象我有几个错误。在切换之前,我手动保留了它们,一切都很好。我不知道为什么ARC没有保留它们。对象被声明为强属性,并使用点表示法引用。这在很多地方都发生过,所以我认为我一定对ARC/memory management有根本的误解。

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.

这里有一个特别令人沮丧的例子。我有3个对象的NSMutableArray。每个对象都有一个属性,也是一个NSMutableArray,在本例中,它总是只有一个对象。最后,该对象具有释放的属性。令人沮丧的原因是它只发生在原始数组的第3个对象上。前两个对象总是很好。在我看来,当以同样的方式创建和使用的相似对象的相同属性不存在时,如何释放一个对象的属性是没有意义的。

The array is stored as a property on a UITableViewController:

数组作为UITableViewController的属性存储:

@interface GenSchedController : UITableViewController <SectionHeaderViewDelegate>

@property (nonatomic, strong) NSArray *classes;

@end

@implementation GenSchedController

@synthesize classes;

The 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;

The objects that are stored in the schedules array are defined as:

在schedule数组中存储的对象定义为:

@interface Schedule : NSObject <NSCopying, NSCoding>

@property (nonatomic, strong) NSMutableArray *daysOfWeek;

@implementation Schedule

@synthesize daysOfWeek;

daysOfWeek is what is getting released. It just contains several NSStrings.

《周末》即将上映。它只包含几个nsstring。

I can see that during viewDidLoad 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 tableView:didSelectRowAtIndexPath:, it has already been released. The specific line that throws the error is the @synthesize daysOfWeek; that is called after the 3rd "for" loop below:

我可以看到在viewDidLoad所有的对象都是好的,没有僵尸。但是,当我点击其中一个表单元格,并在tableView的第一行:didSelectRowAtIndexPath:设置断点时,它已经被释放了。抛出错误的具体行是@synthesize daysOfWeek;在下面第三个for循环之后调用:

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 Schedule of the last SchoolClass.

但是,这只发生在最后一堂课的最后一节课上。

Can anyone point me in the right direction in getting my app to work correctly with ARC?

有谁能告诉我正确的方向,让我的应用能正确地使用ARC?

As requested, here's more info. First, the stack trace when the 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 Class Test[82054:b903] *** -[__NSArrayM respondsToSelector:]: message sent to deallocated instance 0x4e28b80

而真正的例外是类测试[82054:b903] *** -[__NSArrayM respondsToSelector:]:发送给deallocate实例0x4e28b80的消息

And here's the code where everything gets 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;

The initWithCoder: methods are really straightforward. First for SchoolClass:

initWithCoder:方法非常简单。第一个SchoolClass:

- (id)initWithCoder:(NSCoder *)decoder {
    self.name = [decoder decodeObjectForKey:@"name"];
    self.description = [decoder decodeObjectForKey:@"description"];
    self.schedules = [decoder decodeObjectForKey:@"schedules"];

    return self;
}

And for 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 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 for (NSString *day in currentSched.daysOfWeek), that it goes into the daysOfWeek getter, which does a retain autorelease. Then after it returns from the getter, it does another retain (Presumably to hold ownership while the loop is processed), and then a release. All of that is the same for the problem object as for the healthy object. The difference is that immediately after that release, the problem object calls release 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.

我尝试使用僵尸模板在应用程序上运行一个概要文件,并将过度释放的对象与数组中的其他对象进行比较。我可以在line上看到(currentSched.daysOfWeek中的NSString *day),它进入了daysOfWeek getter,它执行一个retain autorelease。然后,在从getter返回后,它执行另一个retain(大概是在处理循环时保持所有权),然后执行一个release。所有这些对于问题对象和健康对象都是一样的。不同之处在于,在发布之后,问题对象再次调用release。这实际上不会立即导致问题,因为autorelease池还没有耗尽,但是一旦它耗尽,保留计数就会降到0,当然下次我尝试访问它时,它是一个僵尸。

What I can't figure out is WHY that extra release is getting called there. Because of the outer for loops, the number of times that currentSched.daysOfWeek gets called does vary - it gets called 3 times on the problem object and 5 on the healthy object, but the extra release occurs the first time it's called, so I'm not sure how that would affect it.

我搞不懂的是为什么这个额外的版本会被调用。因为循环的外部,currentSched的次数。daysOfWeek被调用的功能是不同的——它在问题对象上被调用3次,在健康对象上被调用5次,但是额外的释放是在第一次调用时发生的,所以我不确定这会如何影响它。

Does this extra info help anyone understand what's happening?

这些额外的信息能帮助任何人理解发生了什么吗?

2 个解决方案

#1


0  

ARC is all about object ownership. Do you have a strong pointer to the object you are referencing? If so, the object is retained.

ARC是关于对象所有权的。你有指向你正在引用的对象的强指针吗?如果是,对象将被保留。

When I converted my project to ARC I got a message sent to deallocated instance error as well - an error that wasn't show up in my pre-ARC code. The explanation was this: in my pre-ARC code I had a memory leak. I retained an object and then never released it. I was later referencing from a weak pointer (delegate pointer). When I switched to ARC, the memory management was cleaned up and so once I no longer had a strong pointer to the object it was released. So, when I tried to access it with the unsafe pointer, it crashed.

当我将项目转换为ARC时,我得到了一个发送到deallocation实例错误的消息——这个错误在我的前ARC代码中没有出现。解释是这样的:在我的前弧代码中,我有一个内存泄漏。我保留了一个对象,但从未释放它。我后来引用了一个弱指针(委托指针)。当我切换到ARC时,内存管理被清理了,所以一旦我不再有指向对象的强指针,它就被释放了。当我试图用不安全指针访问它时,它崩溃了。

Just follow the ownership and draw object graphs - this will help you to track down the bug.

只要跟随所有者并绘制对象图——这将帮助您跟踪错误。

#2


0  

So, I've figured out how to keep this from happening, although I'm still confused on why it makes a difference. In each place where the extra release was happening, it was in a loop. In those places, I took the property declaration out of the for loop, assigned it to a local var that is used in the for loop, and it works fine, now! So, a line that used to be:

所以,我已经找到了避免这种情况发生的方法,尽管我仍然搞不清楚为什么它会产生影响。在发生额外发布的每个地方,它都是一个循环。在这些地方,我将属性声明从for循环中取出,并将它分配给用于for循环的本地var,现在它可以正常工作了!所以,这句话曾经是:

for (NSString *day in schedule1.daysOfWeek)

I've changed to 2 lines:

我改了两行:

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

Obviously that's going to make a difference in the retain/release calls that will be needed, but I don't see why it ultimately makes a difference in the final retain count... If anyone can shed some insight on why this helps, I'd love to hear it!

显然,这将对保留/发布调用产生影响,但我不明白为什么它最终会对保留计数产生影响……如果有人能对这一点有所帮助,我很想听听!

#1


0  

ARC is all about object ownership. Do you have a strong pointer to the object you are referencing? If so, the object is retained.

ARC是关于对象所有权的。你有指向你正在引用的对象的强指针吗?如果是,对象将被保留。

When I converted my project to ARC I got a message sent to deallocated instance error as well - an error that wasn't show up in my pre-ARC code. The explanation was this: in my pre-ARC code I had a memory leak. I retained an object and then never released it. I was later referencing from a weak pointer (delegate pointer). When I switched to ARC, the memory management was cleaned up and so once I no longer had a strong pointer to the object it was released. So, when I tried to access it with the unsafe pointer, it crashed.

当我将项目转换为ARC时,我得到了一个发送到deallocation实例错误的消息——这个错误在我的前ARC代码中没有出现。解释是这样的:在我的前弧代码中,我有一个内存泄漏。我保留了一个对象,但从未释放它。我后来引用了一个弱指针(委托指针)。当我切换到ARC时,内存管理被清理了,所以一旦我不再有指向对象的强指针,它就被释放了。当我试图用不安全指针访问它时,它崩溃了。

Just follow the ownership and draw object graphs - this will help you to track down the bug.

只要跟随所有者并绘制对象图——这将帮助您跟踪错误。

#2


0  

So, I've figured out how to keep this from happening, although I'm still confused on why it makes a difference. In each place where the extra release was happening, it was in a loop. In those places, I took the property declaration out of the for loop, assigned it to a local var that is used in the for loop, and it works fine, now! So, a line that used to be:

所以,我已经找到了避免这种情况发生的方法,尽管我仍然搞不清楚为什么它会产生影响。在发生额外发布的每个地方,它都是一个循环。在这些地方,我将属性声明从for循环中取出,并将它分配给用于for循环的本地var,现在它可以正常工作了!所以,这句话曾经是:

for (NSString *day in schedule1.daysOfWeek)

I've changed to 2 lines:

我改了两行:

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

Obviously that's going to make a difference in the retain/release calls that will be needed, but I don't see why it ultimately makes a difference in the final retain count... If anyone can shed some insight on why this helps, I'd love to hear it!

显然,这将对保留/发布调用产生影响,但我不明白为什么它最终会对保留计数产生影响……如果有人能对这一点有所帮助,我很想听听!