35

Per anni ho seguito un grande schema chiamato Target-Action che si presenta così:Il modello di progettazione Target-Action è diventato una cattiva pratica sotto ARC?

Un oggetto chiama un selettore specificato su un oggetto di destinazione specificato quando arriva il momento della chiamata. Questo è molto utile in molti casi diversi in cui è necessario un semplice callback a un metodo arbitrario.

Ecco un esempio:

- (void)itemLoaded { 
    [specifiedReceiver performSelector:specifiedSelector]; 
} 

Sotto ARC ora scopre che facendo qualcosa di simile ad un tratto è diventata pericolosa.

Xcode lancia un avvertimento che va in questo modo:

performSelector può causare una perdita perché il suo selettore è sconosciuta

Naturalmente il selettore è sconosciuto poiché, come parte del progetto di destinazione-Azione pattern puoi specificare il selettore che desideri per ottenere una chiamata quando succede qualcosa di interessante.

Ciò che mi infastidisce di più di questo avviso è che dice che può esserci una potenziale perdita di memoria. Dal mio punto di vista, ARC non modifica le regole di gestione della memoria, ma invece automatizza semplicemente l'inserimento di messaggi di conservazione/rilascio/autorelease nelle posizioni corrette.

Un'altra cosa da notare qui: -performSelector: ha un valore di ritorno id. ARC analizza le firme dei metodi per capire attraverso l'applicazione delle convenzioni di denominazione se il metodo restituisce un oggetto conteggio di ritenzione +1 o meno. In questo caso ARC non sa se il selettore è un factory -newFooBar o semplicemente chiama un metodo worker unsuspicious (che è quasi sempre il caso di Target-Action comunque). In realtà, ARC avrebbe dovuto riconoscere che non mi aspetto un valore di ritorno, e quindi dimenticare qualsiasi potenziale valore di ritorno conteggiato di +1. Guardandolo da quel punto di vista posso vedere da dove proviene ARC, ma ancora c'è troppa incertezza su ciò che questo significa in pratica.

Ciò significa ora sotto ARC qualcosa può andare storto che non accadrà mai senza ARC? Non vedo come questo potrebbe produrre una perdita di memoria. Qualcuno può fornire esempi di situazioni in cui ciò è pericoloso da fare e in quale caso viene creata esattamente una perdita?

ho davvero Googled l'inferno fuori di internet, ma non ha trovato alcun sito che spiega il motivo per cui .

risposta

24

Il problema è che performSelector ARC non sa cosa il selettore che sarà eseguito, lo fa.Si consideri il seguente:

id anotherObject1 = [someObject performSelector:@selector(copy)]; 
id anotherObject2 = [someObject performSelector:@selector(giveMeAnotherNonRetainedObject)]; 

Ora, come può ARC sapere che i primi restituisce un oggetto con un conteggio di conservare 1 ma il secondo restituisce un oggetto che viene autoreleased? (Sto solo definendo un metodo chiamato giveMeAnotherNonRetainedObject qui che restituisce qualcosa di autoreleased). Se non è stato aggiunto in nessuna versione, allora lo anotherObject1 potrebbe fuoriuscire qui.

Ovviamente nel mio esempio i selettori da eseguire sono effettivamente noti, ma immaginate di essere stati scelti in fase di esecuzione. ARC davvero non poteva fare il suo lavoro inserendo il numero giusto di retain s o release qui perché semplicemente non sa cosa farà il selettore. Hai ragione sul fatto che ARC non sta piegando alcuna regola e aggiunge semplicemente le chiamate di gestione della memoria corrette, ma è esattamente ciò che lo non può fare qui.

Hai ragione che il fatto che stai ignorando il valore restituito significa che sarà OK, ma in generale ARC è solo schizzinoso e monitore. Ma immagino sia per questo che si tratta di un avvertimento e non di un errore.

Edit:

Se sei veramente sicuro che il codice è ok, si può solo nascondere l'avviso in questo modo:

#pragma clang diagnostic push 
#pragma clang diagnostic ignored "-Warc-performSelector-leaks" 
[specifiedReceiver performSelector:specifiedSelector]; 
#pragma clang diagnostic pop 
+1

Hai completamente ragione. Ma nel mio caso ignoro completamente il valore di ritorno, che in una conseguenza logica significa che non ci può essere un problema in questa posizione. Ho persino provato a lanciare a vuoto, a ponte, __unsafe_unretained, ecc. - l'unico modo per sbarazzartene è spostarsi sui blocchi (che provoca una pletora di altri problemi) o disabilitare l'avviso nelle impostazioni delle fasi di costruzione. –

+0

Vedere la mia modifica per disattivare l'avviso solo per un bit di codice. – mattjgalloway

+0

Due cervelli, lo stesso pensiero. Grande! :-) (digitato la mia risposta allo stesso tempo. contiene ulteriori informazioni sulla disabilitazione dell'avviso) –

3

ARC lancia l'avviso perché non può garantire che il selettore non stia creando un oggetto di cui non è a conoscenza. Si potrebbe teoricamente ricevere qualcosa da quel metodo che ARC non può gestire:

id objectA = [someObject performSelector:@selector(createObjectA)]; 

Forse un giorno si può, ma in questo momento non si può. (Nota se conosce l'oggetto (non è un ID) non lancia questo avvertimento).

Se si sta tentando di eseguire semplicemente un metodo senza ricevere un oggetto, si consiglia di utilizzare objc_msgSend. Ma hai devo includere nella vostra classe:

#include <objc/message.h> 
objc_msgSend(someObject, action); 
+2

Meglio ancora, utilizzare un blocco digitato come un gestore di completamento invece di specificare un selettore arbitraria. –

+0

Mi è stato detto che chiamare objc_msgSend ha molti inconvenienti e può essere pericoloso in alcune situazioni. –

+2

Sì, ho spostato tutte le mie classi personalizzate dal modello "delegato" all'utilizzo dei blocchi. Ma devi stare attento con i blocchi e la gestione della memoria. Con il modello delegato, il controllo può interrompere il ciclo di conservazione utilizzando un riferimento debole al delegato. Ma i blocchi mantengono automaticamente se stessi quando fanno riferimento a iVars. Quindi, qualunque classe stia assegnando il blocco deve assicurarsi che non faccia riferimento a se stesso fortemente se mantiene anche un riferimento al controllo (che memorizza il blocco). Credo che questo sia il motivo per cui Apple non utilizzerà i blocchi di completamento invece dei delegati. –

9

L'avviso dovrebbe leggere come questo:

PerformSelector può causare una perdita perché il suo selettore è sconosciuto. ARC non sa se l'ID restituito ha un conteggio di mantenimento +1 o meno, e quindi non può gestire correttamente la memoria dell'oggetto restituito.

Sfortunatamente, è solo la prima frase.

Ora la soluzione:

Se si riceve un valore di ritorno da un metodo -performSelector, non si può fare nulla circa l'avvertimento in codice, ad eccezione di ignorarlo.

NSArray *linkedNodes = [startNode performSelector:nodesArrayAccessor]; 

La cosa migliore è questo:

#pragma clang diagnostic push 
#pragma clang diagnostic ignored "-Warc-performSelector-leaks" 
NSArray *linkedNodes = [startNode performSelector:nodesArrayAccessor]; 
#pragma clang diagnostic pop 

Lo stesso vale per il caso nella mia domanda iniziale, dove ho completamente ignorare il valore di ritorno. ARC dovrebbe essere abbastanza intelligente da capire che non mi interessa l'ID restituito, e quindi il selettore anonimo è quasi garantito per non essere una fabbrica, un costruttore di convenienze o qualsiasi altra cosa. Sfortunatamente ARC non lo è, quindi vale la stessa regola. Ignora l'avviso.

Può anche essere fatto per l'intero progetto impostando il -Wno-arco performSelector-perdite compilatore bandiera voce "Altri segnali di avvertimento" in impostazioni di generazione del progetto.

In alternativa, è possibile sopprimere l'avviso su una base per file quando si aggiunge questo flag sotto Target> "Fasi di compilazione"> "Fonti di compilazione" sul lato destro accanto al file desiderato.

Tutte e tre le soluzioni sono molto confuse IMHO quindi spero che qualcuno ne abbia una migliore.

4

Come descritto sopra viene visualizzato questo avviso perché il compilatore non sa dove (o se) inserire il mantenimento/rilascio di performSelector: valore restituito.

ma è da notare che se si utilizza [someObject performSelector:@selector(selectorName)] non genererà gli avvertimenti (almeno in Xcode 4.5 con LLVM 4.1), perché il selettore esatto è facile da determinare (lo si imposta in modo esplicito) ed è per questo compilatore è in grado di mettere il conservare/rilascia nella posizione corretta.

Ecco perché verrà visualizzato un avviso solo se si passa il selettore utilizzando il puntatore SEL poiché in tal caso il compilatore non è in grado di determinare in ogni caso cosa fare. Quindi utilizzando il seguente

SEL s = nil; 
if(condition1) SEL = @selector(sel1) 
else SEL = @selector(sel2) 

[self performSelector:s]; 

genererà avviso. Ma refactoring che sia:

if(condition1) [self performSelector:@selector(sel1)] 
else [self performSelector:@selector(sel2)] 

non genererà alcuna avvertenze

Problemi correlati