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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8395452/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 22:41:05  来源:igfitidea点击:

How can I keep objects from being released when using Objective-C ARC with Xcode 4.2?

objective-ciosxcodememory-managementautomatic-ref-counting

提问by Elezar

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

ETA:有关我通过分析应用程序获得的更多信息,请参阅底部。

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/内存管理有根本的误解。

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 的属性,在这种情况下,它始终只有一个对象。最后,该对象具有被释放的属性。令人沮丧的原因是它只发生在原始数组中的第三个对象上。前两个对象总是完全没问题。当以相同方式创建和使用的类似对象的相同属性不是时,如何释放一个对象的属性对我来说没有意义。

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 classesarray are defined as:

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

@interface SchoolClass : NSObject <NSCopying, NSCoding>

@property (nonatomic, strong) NSMutableArray *schedules;

@end

@implementation SchoolClass

@synthesize schedules;

The objects that are stored in the schedulesarray are defined as:

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

@interface Schedule : NSObject <NSCopying, NSCoding>

@property (nonatomic, strong) NSMutableArray *daysOfWeek;

@implementation Schedule

@synthesize daysOfWeek;

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

daysOfWeek是什么被释放。它只包含几个 NSStrings。

I can see that during viewDidLoadthat 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.

但是,同样,这只会发生在最后一个 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

确切的例外是 Class Test[82054:b903] *** -[__NSArrayM respondsToSelector:]: message sent to deallocated instance 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: 方法非常简单。学校班级第一名:

- (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 releaseAGAIN. 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.

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

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

我想不通的是为什么额外的人release会在那里被调用。由于外部 for 循环,currentSched.daysOfWeek被调用的次数确实有所不同 - 它在问题对象上被调用 3 次,在健康对象上被调用 5 次,但是额外的release发生在第一次被调用时,所以我不确定如何那会影响它。

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

这些额外信息是否有助于任何人了解正在发生的事情?

采纳答案by Elezar

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:

我已更改为 2 行:

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!

显然,这将对所需的保留/释放调用产生影响,但我不明白为什么它最终会对最终保留计数产生影响......很想听听!

回答by bearMountain

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 instanceerror 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 时,我也遇到了一个message sent to deallocated instance错误——这个错误没有出现在我的 ARC 之前的代码中。解释是这样的:在我的前 ARC 代码中,我有内存泄漏。我保留了一个对象,然后从未释放它。后来我从一个弱指针(委托指针)引用。当我切换到 ARC 时,内存管理被清理,所以一旦我不再有指向它被释放的对象的强指针。因此,当我尝试使用不安全指针访问它时,它崩溃了。

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

只需遵循所有权并​​绘制对象图 - 这将帮助您追踪错误。