ETA: vedere la parte in basso per ulteriori informazioni che ho ottenuto dal profiling dell'applicazione.Come posso evitare il rilascio di oggetti durante l'utilizzo di Objective-C ARC con Xcode 4.2?
Ho un'app per iPhone che ho appena convertito per usare ARC, e ora sto ricevendo diversi errori a causa di oggetti zombi. Prima che passassi, li stavo manualmente conservando e tutto andava bene. Non riesco a capire perché ARC non li trattiene. Gli oggetti sono dichiarati come proprietà forti e referenziati mediante notazione a punti. Questo sta accadendo in diversi punti, quindi penso che dovrei avere un fondamentale fraintendimento della gestione della memoria/ARC da qualche parte.
Ecco un esempio particolarmente frustrante. Ho un NSMutableArray di 3 oggetti. Ognuno di questi oggetti ha una proprietà che è anche un NSMutableArray, che in questo caso ha sempre un singolo oggetto. Infine, quell'oggetto ha la proprietà che viene rilasciata. Il motivo per cui è frustrante è che accade solo con il terzo oggetto dell'array originale. I primi 2 oggetti sono sempre completamente a posto. Non ha senso per me come la proprietà di un oggetto possa essere rilasciata quando la stessa proprietà di oggetti simili creati e usati allo stesso modo non lo sono.
La matrice viene memorizzata come una proprietà su un UITableViewController:
@interface GenSchedController : UITableViewController <SectionHeaderViewDelegate>
@property (nonatomic, strong) NSArray *classes;
@end
@implementation GenSchedController
@synthesize classes;
Gli oggetti memorizzati nella matrice classes
sono definiti come:
@interface SchoolClass : NSObject <NSCopying, NSCoding>
@property (nonatomic, strong) NSMutableArray *schedules;
@end
@implementation SchoolClass
@synthesize schedules;
Gli oggetti memorizzati nella schedules
array sono definiti come:
@interface Schedule : NSObject <NSCopying, NSCoding>
@property (nonatomic, strong) NSMutableArray *daysOfWeek;
@implementation Schedule
@synthesize daysOfWeek;
daysOfWeek
è ciò che viene rilasciato. Contiene solo diverse NSString.
Posso vedere che durante il viewDidLoad
tutti gli oggetti sono a posto, senza zombi. Tuttavia, quando tocco una delle celle della tabella e imposto un breakpoint sulla prima riga di tableView:didSelectRowAtIndexPath:
, è già stato rilasciato. La linea specifica che genera l'errore è il @synthesize daysOfWeek;
che si chiama dopo il 3 ° ciclo "for" seguito:
for (SchoolClass *currentClass in self.classes) {
for (Schedule *currentSched in currentClass.schedules) {
for (NSString *day in currentSched.daysOfWeek)
Ma, ancora una volta, questo accade solo l'ultimo Programma dell'ultimo SchoolClass.
Qualcuno può indicarmi la giusta direzione per far funzionare correttamente la mia app con ARC?
Come richiesto, ecco maggiori informazioni. In primo luogo, la traccia dello stack quando viene generata l'eccezione:
#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
e l'eccezione esatta è Class Test[82054:b903] *** -[__NSArrayM respondsToSelector:]: message sent to deallocated instance 0x4e28b80
Ed ecco il codice dove tutto viene creato, caricare da disco:
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;
L'initWithCoder : i metodi sono davvero semplici.In primo luogo per SchoolClass:
- (id)initWithCoder:(NSCoder *)decoder {
self.name = [decoder decodeObjectForKey:@"name"];
self.description = [decoder decodeObjectForKey:@"description"];
self.schedules = [decoder decodeObjectForKey:@"schedules"];
return self;
}
E per 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;
}
Ho provato a fare funzionare un profilo sul app utilizzando il modello zombie, e confrontato l'oggetto che l'eccesso di emissioni di uno degli altri nella array che va bene. Posso vedere che sulla linea for (NSString *day in currentSched.daysOfWeek)
, che entra nei giorni di geek di geek, che fa un retain autorelease
. Quindi, dopo che è tornato dal getter, fa un altro retain
(Presumibilmente per mantenere la proprietà mentre il ciclo viene elaborato), e quindi un release
. Tutto ciò è lo stesso per l'oggetto problema come per l'oggetto sano. La differenza è che immediatamente dopo tale release
, l'oggetto problematico chiama release
AGAIN. Questo in realtà non causa immediatamente un problema, perché il pool di autorelease non è ancora esaurito, ma una volta fatto, il conteggio dei ritiri scende a 0, e poi, naturalmente, la prossima volta che cerco di accedervi, è uno zombie.
Quello che non riesco a capire è PERCHÉ viene chiamato il numero release
in più. A causa dei loop esterni, il numero di volte che currentSched.daysOfWeek
viene chiamato varia, viene chiamato 3 volte sull'oggetto problema e 5 sull'oggetto sano, ma l'ulteriore release
si verifica la prima volta che viene chiamato, quindi non lo sono sicuro come ciò potrebbe influenzarlo.
Questa informazione extra aiuta qualcuno a capire cosa sta succedendo?
Aspetta, hai '@ synthesize' all'interno del ciclo? E come stai determinando che è stato rilasciato se non compare nessuno zombi? – Chuck
potresti pubblicare il codice dove crei questi oggetti? –
@Chuck Penso che '@ systhesize' sia saltato nel debugger quando chiama l'accessor' currentSched.daysOfWeek' nel ciclo for. –