Как я могу предотвратить высвобождение объектов при использовании ARC Objective-C с Xcode 4.2? - PullRequest
6 голосов
/ 06 декабря 2011

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 происходит при первом вызове, поэтомуЯ не уверен, как это повлияет на это.

Помогает ли эта дополнительная информация кому-либо понять, что происходит?

Ответы [ 2 ]

0 голосов
/ 18 декабря 2011

Итак, я выяснил, как этого избежать, хотя я все еще не понимаю, почему это имеет значение. В каждом месте, где происходил дополнительный выпуск, это было в цикле. В этих местах я вынул объявление свойства из цикла for, присвоил его локальной переменной, которая используется в цикле for, и теперь она отлично работает! Итак, строка, которая раньше была:

for (NSString *day in schedule1.daysOfWeek)

Я изменил на 2 строки:

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

Очевидно, что это будет иметь значение в вызовах удержания / освобождения, которые будут необходимы, но я не понимаю, почему это в конечном счете влияет на окончательный счет удержания ... Если кто-то может пролить некоторый взгляд на то, почему это помогает, хотелось бы это услышать!

0 голосов
/ 11 декабря 2011

ARC - все о владении объектом. У вас есть сильный указатель на объект, на который вы ссылаетесь? Если это так, объект сохраняется.

Когда я преобразовал свой проект в ARC, я также получил ошибку message sent to deallocated instance - ошибку, которая не отображалась в моем коде до ARC. Объяснение было таким: в моем коде до ARC у меня была утечка памяти. Я сохранил объект, а затем никогда не отпускал его. Позже я ссылался на слабый указатель (указатель делегата). Когда я переключился на ARC, управление памятью было очищено, и, как только я перестал иметь сильный указатель на объект, он был освобожден. Поэтому, когда я попытался получить к нему доступ с помощью небезопасного указателя, он упал.

Просто следите за владением и рисуйте графы объектов - это поможет вам отследить ошибку.

...