Soluzione
Il compilatore lo sta avvertendo per un motivo. È molto raro che questo avviso venga semplicemente ignorato ed è facile aggirare il problema. Ecco come:
if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);
O più tersamente (anche se difficile da leggere e senza la guardia):
SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);
Spiegazione
Quello che sta succedendo qui è che stai chiedendo al controller il puntatore alla funzione C per il metodo corrispondente al controller. Tutti NSObject
rispondono methodForSelector:
, ma puoi anche usarli class_getMethodImplementation
nel runtime Objective-C (utile se hai solo un riferimento di protocollo, come id<SomeProto>
). Questi puntatori a funzioni sono chiamati IMP
s e sono semplici typedef
puntatori a funzione ed ( id (*IMP)(id, SEL, ...)
) 1 . Questo potrebbe essere vicino alla firma del metodo effettivo del metodo, ma non corrisponderà sempre esattamente.
Una volta che hai il IMP
, devi lanciarlo in un puntatore a funzione che includa tutti i dettagli di cui ARC ha bisogno (inclusi i due argomenti nascosti impliciti self
e _cmd
di ogni chiamata al metodo Objective-C). Questo è gestito nella terza riga (il (void *)
lato destro indica semplicemente al compilatore che sai cosa stai facendo e di non generare un avviso poiché i tipi di puntatore non corrispondono).
Infine, si chiama il puntatore a funzione 2 .
Esempio complesso
Quando il selettore accetta argomenti o restituisce un valore, dovrai cambiare un po 'le cose:
SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
func(_controller, selector, someRect, someView) : CGRectZero;
Ragionamento per avvertimento
Il motivo di questo avviso è che con ARC il runtime deve sapere cosa fare con il risultato del metodo che stai chiamando. Il risultato potrebbe essere qualsiasi cosa: void
, int
, char
, NSString *
, id
, ecc ARC ottiene normalmente queste informazioni dal intestazione del tipo di oggetto che si sta lavorando. 3
Ci sono davvero solo 4 cose che ARC prenderebbe in considerazione per il valore di ritorno: 4
- Ignora i tipi non-oggetto (
void
, int
, ecc)
- Conserva il valore dell'oggetto, quindi rilascialo quando non viene più utilizzato (presupposto standard)
- Rilascia i nuovi valori degli oggetti quando non vengono più utilizzati (metodi nella famiglia
init
/ copy
o attribuiti a ns_returns_retained
)
- Non eseguire alcuna operazione e presumere che il valore dell'oggetto restituito sia valido nell'ambito locale (fino a quando la maggior parte del pool di release interno viene svuotato, attribuito a
ns_returns_autoreleased
)
La chiamata methodForSelector:
presuppone che il valore restituito del metodo che sta chiamando sia un oggetto, ma non lo mantiene / rilascia. Quindi potresti finire per creare una perdita se il tuo oggetto dovrebbe essere rilasciato come nel n. 3 sopra (cioè, il metodo che stai chiamando restituisce un nuovo oggetto).
Per i selettori che stai tentando di chiamare quel ritorno void
o altri non oggetti, potresti abilitare le funzionalità del compilatore a ignorare l'avvertimento, ma potrebbe essere pericoloso. Ho visto Clang passare attraverso alcune iterazioni su come gestisce i valori di ritorno che non sono assegnati alle variabili locali. Non c'è motivo per cui con ARC abilitato non sia possibile conservare e rilasciare il valore dell'oggetto che viene restituito methodForSelector:
anche se non si desidera utilizzarlo. Dal punto di vista del compilatore, dopo tutto è un oggetto. Ciò significa che se il metodo che stai chiamando someMethod
restituisce un non oggetto (incluso void
), potresti finire con un valore del puntatore di immondizia che viene mantenuto / rilasciato e si blocca.
Argomenti aggiuntivi
Una considerazione è che questo avverrà con lo stesso avvertimento performSelector:withObject:
e potresti riscontrare problemi simili senza dichiarare come quel metodo consuma i parametri. ARC consente di dichiarare i parametri consumati e se il metodo consuma il parametro, probabilmente alla fine invierai un messaggio a uno zombi e a un arresto anomalo. Ci sono modi per aggirare questo problema con il casting a ponte, ma in realtà sarebbe meglio usare semplicemente la IMP
metodologia del puntatore e funzione sopra. Poiché i parametri consumati raramente rappresentano un problema, è improbabile che ciò si verifichi.
Selettori statici
È interessante notare che il compilatore non si lamenterà dei selettori dichiarati staticamente:
[_controller performSelector:@selector(someMethod)];
Il motivo è che il compilatore è effettivamente in grado di registrare tutte le informazioni sul selettore e l'oggetto durante la compilazione. Non è necessario formulare ipotesi su nulla. (L'ho verificato un anno fa guardando la fonte, ma non ho un riferimento in questo momento.)
repressione
Nel tentativo di pensare a una situazione in cui sarebbe necessaria la soppressione di questo avvertimento e una buona progettazione del codice, sto diventando vuoto. Qualcuno per favore condividi se ha avuto un'esperienza in cui era necessario mettere a tacere questo avviso (e quanto sopra non gestisce le cose correttamente).
Di Più
È possibile creare NSMethodInvocation
e gestire anche questo, ma farlo richiede molta più digitazione ed è anche più lento, quindi ci sono poche ragioni per farlo.
Storia
Quando la performSelector:
famiglia di metodi è stata aggiunta per la prima volta a Objective-C, ARC non esisteva. Durante la creazione di ARC, Apple ha deciso di generare un avviso per questi metodi come modo per guidare gli sviluppatori verso l'uso di altri mezzi per definire esplicitamente come gestire la memoria quando si inviano messaggi arbitrari tramite un selettore con nome. In Objective-C, gli sviluppatori sono in grado di farlo utilizzando i cast di stile C su puntatori a funzioni non elaborate.
Con l'introduzione di Swift, Apple ha documentato la performSelector:
famiglia di metodi come "intrinsecamente non sicura" e non sono disponibili per Swift.
Nel tempo, abbiamo visto questa progressione:
- Le prime versioni di Objective-C consentono
performSelector:
(gestione manuale della memoria)
- Objective-C con ARC avvisa per l'uso di
performSelector:
- Swift non ha accesso
performSelector:
e documenta questi metodi come "intrinsecamente non sicuri"
L'idea di inviare messaggi basati su un selettore denominato non è, tuttavia, una funzione "intrinsecamente non sicura". Questa idea è stata utilizzata con successo per molto tempo in Objective-C e in molti altri linguaggi di programmazione.
1 Tutti i metodi Objective-C hanno due argomenti nascosti self
e _cmd
che vengono aggiunti implicitamente quando si chiama un metodo.
2 La chiamata di una NULL
funzione non è sicura in C. La protezione utilizzata per verificare la presenza del controller assicura che abbiamo un oggetto. Sappiamo quindi otterremo un IMP
da methodForSelector:
(anche se può essere _objc_msgForward
, ingresso nel sistema di inoltro dei messaggi). Fondamentalmente, con la guardia in atto, sappiamo di avere una funzione da chiamare.
3 In realtà, è possibile ottenere informazioni errate se si dichiarano oggetti come id
e non si stanno importando tutte le intestazioni. Potresti finire con arresti anomali nel codice che il compilatore ritiene che vada bene. Questo è molto raro, ma potrebbe succedere. Di solito riceverai solo un avviso che non sa da quale delle due firme del metodo scegliere.
4 Consultare il riferimento ARC sui valori di restituzione mantenuti e sui valori di restituzione non mantenuti per maggiori dettagli.