6

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?

+0

Aspetta, hai '@ synthesize' all'interno del ciclo? E come stai determinando che è stato rilasciato se non compare nessuno zombi? – Chuck

+1

potresti pubblicare il codice dove crei questi oggetti? –

+1

@Chuck Penso che '@ systhesize' sia saltato nel debugger quando chiama l'accessor' currentSched.daysOfWeek' nel ciclo for. –

risposta

0

Quindi, ho capito come evitare che ciò accada, anche se sono ancora confuso sul perché fa la differenza. In ogni luogo in cui si stava verificando la versione extra, era in un ciclo. In quei luoghi, ho preso la dichiarazione di proprietà fuori dal ciclo for, assegnandola a una var locale che è usata nel ciclo for, e ora funziona bene! Quindi, una linea che ha usato essere:

for (NSString *day in schedule1.daysOfWeek) 

ho cambiato a 2 linee:

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

ovviamente, che sta andando a fare la differenza nelle chiamate mantenere/rilascio che saranno necessari, ma Non vedo perché alla fine faccia la differenza nel conteggio dei ritardi finali ... Se qualcuno può far luce sul motivo per cui questo aiuta, mi piacerebbe sentirlo!

+0

Ciò potrebbe accadere se l'oggetto restituito da 'schedule1.daysOfWeek' è stato rilasciato durante il ciclo, ad esempio, se si imposta' daysOfWeek' all'interno del ciclo. La seconda versione assegna daysOfWeek a una variabile forte, quindi viene mantenuta attiva fino alla fine del ciclo; la prima versione no, quindi la vita della matrice è alla stregua di 'schedule1'. –

0

ARC è interamente dedicato alla proprietà dell'oggetto. Hai un forte puntatore sull'oggetto a cui ti stai riferendo? In tal caso, l'oggetto viene mantenuto.

Quando ho convertito il mio progetto in ARC, ho ricevuto anche un errore message sent to deallocated instance - un errore che non era presente nel mio codice pre-ARC. La spiegazione era questa: nel mio codice pre-ARC avevo una perdita di memoria. Ho conservato un oggetto e quindi non l'ho mai rilasciato. In seguito mi sono riferito a un puntatore debole (puntatore delegato). Quando sono passato a ARC, la gestione della memoria è stata ripulita e così, una volta che non ho più avuto un puntatore forte all'oggetto, è stato rilasciato. Quindi, quando ho provato ad accedere con il puntatore non sicuro, si è bloccato.

Basta seguire la proprietà e disegnare i grafici degli oggetti - questo ti aiuterà a rintracciare il bug.

+0

Sì, come ho detto, le proprietà sono tutte definite come forti e uso sempre la notazione a punti per fare riferimento a esse. Ho provato a seguire la proprietà, ma per quanto posso dire, dovrebbero essere ancora di proprietà nel momento in cui vengono rilasciati. Forse mi manca qualcosa nella catena di proprietà, ma se è così, allora credo che la mia domanda è "come faccio a identificare ciò che mi manca nella catena di proprietà?" Puoi chiarire cosa intendi per "disegnare i grafici degli oggetti"? – Elezar

Problemi correlati