ETA: См. Внизу дополнительную информацию, которую я получил при профилировании приложения.
У меня есть приложение для iPhone, которое я только что преобразовал для использования ARC, и теперь я получаю несколько ошибок из-за объектов-зомби. До перехода я вручную их сохранял, и все было нормально. Я не могу понять, почему ARC их не сохраняет. Объекты объявляются как сильные свойства и указываются в точечной нотации.Это происходит в нескольких местах, поэтому я думаю, что где-то у меня есть фундаментальное непонимание ARC / управления памятью.
Вот пример, который особенно расстраивает. У меня есть NSMutableArray из 3 объектов. Каждый из этих объектов имеет свойство, которое также является NSMutableArray, которое в этом случае всегда имеет один объект. Наконец, этот объект имеет свойство, которое освобождается. Причина, по которой это расстраивает, заключается в том, что это происходит только с 3-м объектом из исходного массива. Первые 2 объекта всегда в полном порядке. Для меня просто не имеет смысла, как свойство одного объекта будет освобождено, если одно и то же свойство похожих объектов, созданных и используемых таким же образом, не является.
Массив хранится как свойство в 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;
Объекты, хранящиеся в расписаниях Массив
определяется как:
@interface Schedule : NSObject <NSCopying, NSCoding>
@property (nonatomic, strong) NSMutableArray *daysOfWeek;
@implementation Schedule
@synthesize daysOfWeek;
daysOfWeek
- это то, что выпускается. Он просто содержит несколько NSStrings.
Я вижу, что во время 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:]: сообщение, отправленное в освобожденный экземпляр 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;
}
Я попытался запустить Профиль в приложении, используя шаблон Zombies, и сравнил объект с избыточным выпуском с одним из других в массиве, который в порядке. Я вижу, что в строке для (NSString * day в currentSched.daysOfWeek)
он переходит в геттер daysOfWeek, который сохраняет автозапуск
. Затем, после того, как он возвращается из получателя, он выполняет еще одно сохранение
(предположительно для удержания владения во время обработки цикла), а затем выпуск
. Все это то же самое для проблемного объекта, что и для здорового объекта. Разница в том, что сразу после выпуска
проблемный объект снова вызывает выпуск
. На самом деле это не вызывает проблемы сразу, потому что пул автозапуска еще не опустошен, но как только это произойдет, счетчик сохраненных данных упадет до 0, и, конечно же, в следующий раз, когда я попытаюсь получить к нему доступ, это будет зомби.
Я не могу понять, ПОЧЕМУ там вызывается дополнительный выпуск
.Из-за внешних циклов for количество вызовов currentSched.daysOfWeek
действительно варьируется - он вызывается 3 раза для проблемного объекта и 5 раз для исправного объекта, но дополнительный release
возникает при первом вызове, поэтому я не уверен, как это повлияет на него.
Помогает ли эта дополнительная информация понять, что происходит?