ETA: смотрите внизу для дополнительной информации, которую я получил, профилируя приложение.
У меня есть приложение для iPhone, которое я только что преобразовал, чтобы использовать ARC, и теперь я получаю несколько ошибок из-за зомби-объектов,До того, как я переключился, я их вручную сохранял, и все было хорошо.Я не могу понять, почему ARC не удерживает их.Объекты объявляются как сильные свойства и на них ссылаются с помощью точечной нотации.Это происходит в нескольких местах, поэтому я думаю, что у меня должно быть фундаментальное недопонимание управления ARC / памятью где-то.
Вот пример, который особенно расстраивает.У меня есть NSMutableArray из 3 объектов.Каждый из этих объектов имеет свойство NSMutableArray, которое в этом случае всегда имеет один объект.Наконец, у этого объекта есть свойство, которое освобождается.Причина, по которой это разочаровывает, состоит в том, что это происходит только с 3-м объектом из исходного массива.Первые 2 объекта всегда полностью в порядке.Мне просто не имеет смысла, как свойство одного объекта будет освобождено, если одно и то же свойство похожих объектов, созданных и используемых одинаково, не будет.
Массив хранится как свойство вa UITableViewController:
@interface GenSchedController : UITableViewController <SectionHeaderViewDelegate>
@property (nonatomic, strong) NSArray *classes;
@end
@implementation GenSchedController
@synthesize classes;
Объекты, которые хранятся в массиве classes
, определяются как:
@interface SchoolClass : NSObject <NSCopying, NSCoding>
@property (nonatomic, strong) NSMutableArray *schedules;
@end
@implementation SchoolClass
@synthesize schedules;
Объекты, которые хранятся в массиве schedules
, определяются как:
@interface Schedule : NSObject <NSCopying, NSCoding>
@property (nonatomic, strong) NSMutableArray *daysOfWeek;
@implementation Schedule
@synthesize daysOfWeek;
daysOfWeek
- вот что выпускается.Он просто содержит несколько строк NSS.
Я вижу, что во время viewDidLoad
все объекты в порядке, без зомби.Однако, когда я нажимаю одну из ячеек таблицы и устанавливаю точку останова в первой строке tableView:didSelectRowAtIndexPath:
, она уже освобождается.Конкретной строкой, которая выдает ошибку, является @synthesize daysOfWeek;
, который вызывается после 3-го цикла «for» ниже:
for (SchoolClass *currentClass in self.classes) {
for (Schedule *currentSched in currentClass.schedules) {
for (NSString *day in currentSched.daysOfWeek)
Но, опять же, это происходит только в последнем расписании последнего SchoolClass.
Может ли кто-нибудь указать мне правильное направление для правильной работы моего приложения с ARC?
По запросу, вот дополнительная информация.Во-первых, трассировка стека при возникновении исключения:
#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
И точное исключение: Class Test[82054:b903] *** -[__NSArrayM respondsToSelector:]: message sent to deallocated instance 0x4e28b80
А вот код, в котором все создается, загрузка с диска:
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: методы действительно просты.Сначала для SchoolClass:
- (id)initWithCoder:(NSCoder *)decoder {
self.name = [decoder decodeObjectForKey:@"name"];
self.description = [decoder decodeObjectForKey:@"description"];
self.schedules = [decoder decodeObjectForKey:@"schedules"];
return self;
}
И для расписания:
- (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;
}
Я попытался запустить Profile в приложении, используя шаблон Zombies, и сравнил объект, который перевыпускается, с одним изостальные в массиве это нормально.Я вижу, что в строке for (NSString *day in currentSched.daysOfWeek)
он входит в получатель daysOfWeek, который выполняет retain autorelease
.Затем, после того как он возвращается из получателя, он делает еще один retain
(предположительно, чтобы сохранить владение во время обработки цикла), а затем release
.Все это то же самое для проблемного объекта, что и для здорового объекта.Разница в том, что сразу после этого release
проблемный объект снова вызывает release
.Это на самом деле не вызывает проблемы сразу, потому что пул авто-релиза еще не истощен, но как только это произойдет, количество сохранений упадет до 0, и, конечно, в следующий раз, когда я попытаюсь получить к нему доступ, это зомби.
Что я не могу понять, так это ПОЧЕМУ дополнительный release
вызывается туда.Из-за внешнего цикла for число раз, которое вызывается currentSched.daysOfWeek
, изменяется - оно вызывается 3 раза для проблемного объекта и 5 для здорового объекта, но дополнительный release
происходит при первом вызове, поэтомуЯ не уверен, как это повлияет на это.
Помогает ли эта дополнительная информация кому-либо понять, что происходит?