2012-06-13 14 views
5

Non ho ancora utilizzato ARC se non occupandomi di esso quando entra in un progetto tramite codice di terze parti. Ho letto tutti i documenti ARC ma non ho visto una risposta a questa domanda:ARC, non ARC e ereditarietà

Se ho una classe che è definita in un modulo compilato con -fobjc-arc, posso derivare una nuova classe da questo in un modulo che è NON abilitato per ARC?

Nella mia mente dovrebbe funzionare bene fintanto che la classe derivata non tenta di toccare alcun ivars nella classe radice. Mi sembra che anche avere un metodo dealloc che chiama [super dealloc] andrebbe bene nella classe derivata.

E, il contrario? Posso derivare una classe abilitata ARC da una classe non ARC? Dovrebbe funzionare bene, giusto?

Punti bonus: ci sono dei problemi quando si mischiano codici ARC e non ARC di cui dovrei rendermi conto?

risposta

6

Non ci sono problemi di cui sono a conoscenza. Devi capire che ARC è qualcosa come un preprocessore del codice sorgente, aggiungendo le chiamate per la gestione della memoria durante la compilazione. Quando si arriva alla fase di collegamento, non si può veramente dire codice ARC da codice non ARC. (Questa è probabilmente una semplificazione eccessiva, ma che dovrebbe funzionare per i tuoi scopi.) Se la tua classe derivata ha una corretta gestione della memoria e la super-classe ha una corretta gestione della memoria, il risultato funzionerà correttamente.

L'unica differenza che posso pensare è la gestione delle proprietà weak. Ma non ne so abbastanza su quelli da dire se è possibile arrivare al codice buggy usando una combinazione di codice ARC e MRC con proprietà deboli.

+0

questo sembra essere il consenso. Grazie per l'aiuto. – TomSwift

0

Questo era un commento, ma dopo averci pensato, voglio espandere ciò che ha detto.

Hai provato ad ereditare una classe ARC da una normale sottoclasse? I miei pensieri (senza aver provato neanche) è che questo non funzionerà. Innanzitutto, se la classe ARC ha proprietà pubbliche o ivars che utilizzano parole chiave ARC, ad esempio weak, penso che durante la compilazione si ottengano errori dal file di intestazione. In secondo luogo, non so come funzionerebbe lo dealloc. Devi chiamare lo [super dealloc] o no? Non lo so.

In ogni caso, se la tua superclasse è ARC, perché non dovresti usare ARC in nessuna sottoclasse? Non c'è alcun vantaggio nel farlo.

Posso derivare una classe ARC abilitato da una classe non-ARC? Dovrebbe funzionare bene, giusto?

Stavo per dire che non funzionerà neanche, ma avrei sbagliato. Praticamente tutto deve ereditare da NSObject che è conteggiato con riferimento manuale.

+0

Ha senso derivare da classi abilitate ARC nel codice MRC, ad esempio quando si collega una libreria che utilizza ARC e non è possibile riscrivere il progetto principale. (Lo faccio e sembra funzionare.) Non puoi chiamare '[super dealloc]' sotto ARC, penso che il compilatore inserisca la chiamata lì per te. – zoul

+0

@zoul: sì, questo è lo scenario che abbiamo anche noi; Il modulo di terze parti richiede ARC ma non siamo pronti a utilizzare ARC per i nostri moduli. – TomSwift

+0

Ha anche senso avere una sottoclasse non ARC di una classe ARC. In particolare, se una sottoclasse deve gravare pesantemente su un'API CF, a volte è più semplice farlo dal codice MRR rispetto al codice ARC. – bbum

0

Sì, è possibile implementare l'antenato non ARC dalla classe padre ARC e l'antenato ARC dalla classe padre non ARC.

In realtà, ARC è uno zucchero di sintassi, o si può dire, è solo un preprocessore che analizza il codice sorgente in fase di compilazione e inserisce [release] appropriate e [conserva] chiamate al codice. A livello di runtime non viene modificato nulla (eccetto per le proprietà weak).

0

ARC significa che il compilatore si occupa della gestione della memoria, non-ARC significa che si prende cura di esso, ma in entrambi i casi la gestione della memoria funziona esattamente allo stesso modo:

  • Se un oggetto deve rimanere in vita, la sua mantenere il contatore viene incrementato (che è quello che retain fa)
  • Se un oggetto non è più necessaria, il suo mantenere contatore viene decrementato prima del riferimento ad esso viene perso (che è quello che release fa)
  • Se hai finito con un oggetto, ma non deve ancora morire, ad es poiché è necessario restituirlo come risultato del metodo (e non si desidera restituire un oggetto morto), deve essere aggiunto a un pool di autorelease che diminuirà il conteggio dei ritardi in un secondo momento (è ciò che fa autorelease, è come "chiama release su quell'oggetto in un momento futuro.")
  • Gli oggetti appena creati hanno un conteggio di ritenzione di 1.
  • Se il conteggio di ritenzione raggiunge lo zero, l'oggetto viene liberato.

Se si fa tutto da solo o il compilatore lo fa per te, non ha alcun ruolo. Dopo la compilazione, questi metodi vengono chiamati, anche con ARC, ma con ARC il compilatore ha deciso per te quando viene chiamato il metodo. C'è un po 'di magia extra, ad es. ARC non deve sempre aggiungere oggetti ai pool di autorelease quando li restituisce come risultato del metodo, spesso può essere ottimizzato, ma non devi preoccuparti poiché questa magia viene applicata solo se il chiamante e il metodo chiamato stanno entrambi utilizzando ARCO; se uno di questi non lo è, viene utilizzato un normale autorelease (che funziona ancora in ARC esattamente come prima).

L'unica cosa di cui ci si deve preoccupare è il mantenimento dei cicli. Sia che si utilizzi ARC o meno, il conteggio dei riferimenti non può gestire i cicli di conservazione. Nessuna differenza qui.

Insidie? Attento con il bridging gratuito. A NSString * e a CFStringRef sono in effetti la stessa cosa, ma ARC non conosce il mondo CF, quindi mentre ARC si occupa dello NSString, è necessario occuparsi dello CFString. Quando usi ARC, devi dire a ARC come fare il bridge.

CFStringRef cfstr = ...; 
NSString * nsstr = (__bridge_transfer NSString *)cfstr; 
// NSString * nsstr = [(NSString *)cfstr autorelease]; 

codice sopra significa "ARC, si prega di prendere proprietà di tale oggetto CFString e prendersi cura di rilasciarlo non appena si è fatto con esso". Il codice si comporta come il codice mostrato nel commento qui sotto; così attento, dovrebbe avere un conteggio resti di almeno uno e ARC lo rilascerà almeno una volta, solo non ancora. Il contrario:

NSString * nsstr = ...; 
CFStringRef cfstr = (__bridge_retained CFStringRef)cftr; 
// CFStringRef cfstr = (CFStringRef)[nsstr retain]; 

codice sopra significa "ARC, per favore mi dia proprietà di tale NSString, mi prenderò cura di rilasciarlo una volta ho finito con esso". Certo, devi mantenere questa promessa! A un certo punto dovrai chiamare lo CFRelease(cfstr) altrimenti perderai memoria.

Infine c'è (__bridge ...) che è solo un cast di tipo, non viene trasferita alcuna proprietà. Questo tipo di cast è pericoloso in quanto può creare puntatori penzolanti se si tenta di mantenere il risultato del cast intorno. Solitamente lo si usa quando si alimenta un oggetto ARC a una funzione che si aspetta un oggetto CF, poiché ARC sicuramente manterrà vivo l'oggetto finché la funzione non ritorna, ad es.questo è sempre sicuro:

doSomethingWithString((__bridge CFStringRef)nsstr); 

Anche se ARC è stato permesso di rilasciare nsstr in qualsiasi momento, senza il codice di sotto della linea mai accessi più, sarà certamente non rilasciarlo prima che questa funzione è tornato e argomenti della funzione sono da definizione garantita solo per rimanere in vita fino a quando la funzione non ritorna (nel caso in cui la funzione desideri mantenere la stringa attiva, deve mantenerla e quindi ARC non la deallocerà dopo averla rilasciata poiché il conteggio dei ritiri non diventerà zero).

La cosa maggior parte delle persone sembrano lottare con sta passando ARC oggetti come void * contesto, come a volte si deve con API più vecchio, ma che è in realtà morto semplice:

- (void)doIt { 
    NSDictionary myCallbackContext = ...; 
    [obj doSomethingWithCallbackSelector:@selector(iAmDone:) 
     context:(__bridge_retained void *)myCallbackContext 
    ]; 
    // Bridge cast above makes sure that ARC won't kill 
    // myCallbackContext prior to returning from this method. 
    // Think of: 
    // [obj doSomethingWithCallbackSelector:@selector(iAmDone:) 
    // context:(void *)[myCallbackContext retain] 
    // ]; 
} 

// ... 

- (void)iAmDone:(void *)context { 
    NSDictionary * contextDict = (__bridge_transfer NSDictionary *)context; 
    // Use contextDict as you you like, ARC will release it 
    // prior to returning from this method. Think of: 
    // NSDictionary * contextDict = [(NSDictionary *)context autorelease]; 
} 

E devo vero e proprio grande per te che non è così scontato a prima vista. Si prega di prendere in considerazione questo codice:

@implementation SomeObject { 
    id _someIVAR; 
} 

- (void)someMethod { 
    id someValue = ...; 
    _someIVAR = someValue; 
} 

Questo codice non è la stessa in ARC e non ARC. In ARC tutte le variabili sono forti di default, quindi in ARC questo codice si comporta proprio come questo codice avrebbe:

@interface SomeObject 
    @property (retain,nonatomic) id someIVAR; 
@end 

@implementation SomeObject 

- (void)someMethod { 
    id someValue = ...; 
    self.someIVAR = someValue; 
} 

Assegnazione someValue manterrà esso, l'oggetto rimane vivo! Nel settore non-ARC il codice si comporterà come questo:

@interface SomeObject 
    @property (assign,nonatomic) id someIVAR; 
@end 

@implementation SomeObject 

- (void)someMethod { 
    id someValue = ...; 
    self.someIVAR = someValue; 
} 

Nota la proprietà è diverso, come Ivar a non-ARC non sono né strong o weak, non sono nulla, sono solo puntatori (in ARC che è chiamato __unsafe_unretained e la parola chiave qui è non sicuro).

Quindi, se si dispone di codice che utilizza direttamente ivars e non utilizza le proprietà con setter/getter per accedervi, passare da non ARC a ARC può causare cicli di conservazione nel codice utilizzato per la gestione della memoria. D'altra parte, passando da ARC a non-ARC, un codice come questo può causare puntatori penzolanti (puntatori agli oggetti precedenti ma poiché l'oggetto è già morto, questi puntano verso il nulla e il loro utilizzo ha risultati imprevedibili), come oggetti che prima essere tenuto in vita prima che possa morire inaspettatamente.