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


1258

Ricevo il seguente avviso dal compilatore ARC:

"performSelector may cause a leak because its selector is unknown".

Ecco cosa sto facendo:

[_controller performSelector:NSSelectorFromString(@"someMethod")];

Perché ricevo questo avviso? Capisco che il compilatore non può verificare se il selettore esiste o no, ma perché ciò causerebbe una perdita? E come posso modificare il mio codice in modo da non ricevere più questo avviso?


3
Il nome della variabile è dinamico, dipende da molte altre cose. C'è il rischio che io chiamo qualcosa che non esiste, ma non è questo il problema.
Eduardo Scoz,

6
@matt perché chiamare un metodo in modo dinamico su un oggetto sarebbe una cattiva pratica? Lo scopo di NSSelectorFromString () non è supportare questa pratica?
Eduardo Scoz,

7
Dovresti / potresti anche testare [_controller respondsToSelector: mySelector] prima di impostarlo tramite performSelector:
mattulous

50
@mattacular Vorrei poter votare in basso: "Quella ... è una cattiva pratica".
ctpenrose,

6
Se sai che la stringa è letterale, usa semplicemente @selector () in modo che il compilatore possa dire qual è il nome del selettore. Se il tuo codice effettivo chiama NSSelectorFromString () con una stringa costruita o fornita in fase di runtime, devi utilizzare NSSelectorFromString ().
Chris Page,

Risposte:


1211

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 NSObjectrispondono methodForSelector:, ma puoi anche usarli class_getMethodImplementationnel runtime Objective-C (utile se hai solo un riferimento di protocollo, come id<SomeProto>). Questi puntatori a funzioni sono chiamati IMPs e sono semplici typedefpuntatori 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 selfe _cmddi 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

  1. Ignora i tipi non-oggetto ( void, int, ecc)
  2. Conserva il valore dell'oggetto, quindi rilascialo quando non viene più utilizzato (presupposto standard)
  3. Rilascia i nuovi valori degli oggetti quando non vengono più utilizzati (metodi nella famiglia init/ copyo attribuiti a ns_returns_retained)
  4. 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 voido 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 someMethodrestituisce 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 IMPmetodologia 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 NSMethodInvocatione 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:

  1. Le prime versioni di Objective-C consentono performSelector:(gestione manuale della memoria)
  2. Objective-C con ARC avvisa per l'uso di performSelector:
  3. 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 selfe _cmdche vengono aggiunti implicitamente quando si chiama un metodo.

2 La chiamata di una NULLfunzione non è sicura in C. La protezione utilizzata per verificare la presenza del controller assicura che abbiamo un oggetto. Sappiamo quindi otterremo un IMPda 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 ide 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.


@wbyoung Se il tuo codice risolve il problema di conservazione, mi chiedo perché i performSelector:metodi non siano implementati in questo modo. Hanno una rigorosa firma del metodo (di ritorno id, prendendo una o due ids), quindi non è necessario gestire alcun tipo di primitiva.
Tricertops,

1
@Andy l'argomento viene gestito in base alla definizione del prototipo del metodo (non verrà mantenuto / rilasciato). La preoccupazione si basa principalmente sul tipo di reso.
wbyoung,

2
L '"Esempio complesso" fornisce un errore Cannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *'quando si utilizza l'ultimo Xcode. (5.1.1) Tuttavia, ho imparato molto!
Stan James,

2
void (*func)(id, SEL) = (void *)imp;non viene compilato, l'ho sostituito convoid (*func)(id, SEL) = (void (*)(id, SEL))imp;
Davyd Geyl,

1
cambia void (*func)(id, SEL) = (void *)imp;in <…> = (void (*))imp;o<…> = (void (*) (id, SEL))imp;
Isaak Osipovich Dunayevsky

1182

Nel compilatore LLVM 3.0 in Xcode 4.2 è possibile sopprimere l'avviso come segue:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self.ticketTarget performSelector: self.ticketAction withObject: self];
#pragma clang diagnostic pop

Se si verifica l'errore in più punti e si desidera utilizzare il sistema macro C per nascondere i pragmi, è possibile definire una macro per semplificare la soppressione dell'avviso:

#define SuppressPerformSelectorLeakWarning(Stuff) \
    do { \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        Stuff; \
        _Pragma("clang diagnostic pop") \
    } while (0)

Puoi usare la macro in questo modo:

SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);

Se hai bisogno del risultato del messaggio eseguito, puoi farlo:

id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);

Questo metodo può causare perdite di memoria quando l'ottimizzazione è impostata su un valore diverso da Nessuno.
Eric,

4
@Eric No, non è possibile, a meno che tu non stia invocando metodi divertenti come "initSomething" o "newSomething" o "somethingCopy".
Andrey Tarantsov,

3
@Julian Funziona, ma disattiva l'avvertimento per l'intero file: potresti non averne bisogno o volerlo. Avvolgerlo con le pope push-pragmas è molto più pulito e più sicuro.
Emil,

2
Tutto ciò che fa è mettere a tacere il compilatore. Questo non risolve il problema. Se il selettore non esiste, sei praticamente fregato.
Andra Todorescu,

2
Questo dovrebbe essere usato solo se avvolto da una if ([_target respondsToSelector:_selector]) {logica simile o simile.

208

La mia ipotesi al riguardo è questa: poiché il selettore non è noto al compilatore, ARC non può imporre una corretta gestione della memoria.

In effetti, ci sono momenti in cui la gestione della memoria è legata al nome del metodo da una convenzione specifica. In particolare, sto pensando ai costruttori di convenienza rispetto ai metodi di fabbricazione ; il primo restituisce per convenzione un oggetto rilasciato automaticamente; quest'ultimo è un oggetto trattenuto. La convenzione si basa sui nomi del selettore, quindi se il compilatore non conosce il selettore, non può applicare la regola di gestione della memoria corretta.

Se questo è corretto, penso che tu possa usare tranquillamente il tuo codice, a patto di assicurarti che tutto sia a posto per quanto riguarda la gestione della memoria (ad esempio, che i tuoi metodi non restituiscano oggetti che allocano).


5
Grazie per la risposta, approfondirò questo aspetto per vedere cosa sta succedendo. Qualche idea su come posso aggirare l'avviso e farlo scomparire? Odierei avere l'avvertimento nel mio codice per sempre per quella che è una chiamata sicura.
Eduardo Scoz,

84
Quindi ho ricevuto conferma da qualcuno di Apple nei loro forum che questo è davvero il caso. Aggiungeranno una sostituzione dimenticata per consentire alle persone di disabilitare questo avviso nelle versioni future. Grazie.
Eduardo Scoz,

5
Questa risposta solleva alcune domande, come se ARC provasse a determinare quando rilasciare qualcosa in base alla convenzione e ai nomi dei metodi, come può essere il "conteggio dei riferimenti"? Il comportamento che descrivi suona solo marginalmente meglio di completamente arbitrario, se ARC assume che il codice segua una certa convenzione invece di tenere effettivamente traccia dei riferimenti, indipendentemente da quale convenzione venga seguita.
aroth

8
ARC automatizza il processo di aggiunta di conservazioni e rilasci durante la compilazione. Non è la garbage collection (motivo per cui è così incredibilmente veloce e con costi generali bassi). Non è affatto arbitrario. Le regole predefinite si basano su convenzioni ObjC consolidate che sono state costantemente applicate per decenni. Questo evita la necessità di aggiungere esplicitamente __attributea ogni metodo che spiega la sua gestione della memoria. Ma rende anche impossibile per il compilatore gestire correttamente questo modello (un modello che era molto comune, ma è stato sostituito con modelli più robusti negli ultimi anni).
Rob Napier,

8
Quindi non possiamo più avere un ivar di tipo SELe assegnare selettori diversi a seconda della situazione? Ben fatto, linguaggio dinamico ...
Nicolas Miari,

121

Nelle impostazioni di creazione del progetto , in Altre bandiere di avviso ( WARNING_CFLAGS), aggiungi
-Wno-arc-performSelector-leaks

Ora assicurati solo che il selettore che stai chiamando non causi la conservazione o la copia del tuo oggetto.


12
Nota che puoi aggiungere lo stesso flag per file specifici anziché per l'intero progetto. Se guardi in Fasi di compilazione-> Sorgenti di compilazione, puoi impostare i flag del compilatore per file (proprio come vuoi fare per escludere i file da ARC). Nel mio progetto un solo file dovrebbe usare i selettori in questo modo, quindi l'ho escluso e ho lasciato gli altri.
Michael,

111

Come soluzione alternativa fino a quando il compilatore non consente di sovrascrivere l'avviso, è possibile utilizzare il runtime

objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));

invece di

[_controller performSelector:NSSelectorFromString(@"someMethod")];

Dovrai

#import <objc/message.h>


8
ARC riconosce le convenzioni sul cacao e quindi aggiunge conservazioni e versioni basate su tali convenzioni. Poiché C non segue queste convenzioni, ARC ti costringe a utilizzare tecniche di gestione della memoria manuale. Se si crea un oggetto CF, è necessario CFRelease (). Se dispatch_queue_create (), è necessario dispatch_release (). In conclusione, se si desidera evitare gli avvisi ARC, è possibile evitarli utilizzando gli oggetti C e la gestione manuale della memoria. Inoltre, è possibile disabilitare ARC in base al file utilizzando il flag del compilatore -fno-objc-arc su quel file.
jluckyiv,

8
Non senza casting, non puoi. Varargs non è lo stesso di un elenco di argomenti tipizzato in modo esplicito. In genere funzionerà per coincidenza, ma non considero "per coincidenza" corretto.
bbum,

21
Non farlo [_controller performSelector:NSSelectorFromString(@"someMethod")];e objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));non sono equivalenti! Dai un'occhiata alle mancate corrispondenze del metodo Signature e alla grande debolezza nella digitazione debole di Objective-C stanno spiegando il problema in profondità.
0xced

5
@ 0xced In questo caso, va bene. objc_msgSend non creerà una mancata corrispondenza della firma del metodo per nessun selettore che avrebbe funzionato correttamente in performSelector: o le sue varianti poiché prendono sempre solo gli oggetti come parametri. Finché tutti i parametri sono puntatori (incl. Oggetti), doppi e NSInteger / long e il tipo restituito è nullo, puntatore o lungo, allora objc_msgSend funzionerà correttamente.
Matt Gallagher,

88

Per ignorare l'errore solo nel file con il selettore di esecuzione, aggiungere un #pragma come segue:

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

Ciò ignorerebbe l'avvertimento su questa linea, ma lo consentirebbe comunque per tutto il resto del progetto.


6
Mi risulta che puoi anche riattivare l'avviso immediatamente dopo il metodo in questione #pragma clang diagnostic warning "-Warc-performSelector-leaks". So che se spengo un avviso, mi piace riaccenderlo nel più breve tempo possibile, quindi non lascio accidentalmente passare un altro avviso imprevisto. È improbabile che questo sia un problema, ma è solo la mia pratica ogni volta che spengo un avviso.
Rob

2
È inoltre possibile ripristinare lo stato di configurazione del compilatore precedente utilizzando #pragma clang diagnostic warning pushprima di apportare eventuali modifiche e #pragma clang diagnostic warning popripristinare lo stato precedente. Utile se stai disattivando i carichi e non vuoi avere molte riattivazioni di linee pragma nel tuo codice.
DeanWombourne,

Ignorerà solo la seguente riga?
hfossli,

70

Strano ma vero: se accettabile (cioè il risultato è nullo e non ti dispiace lasciare una volta il ciclo di runloop), aggiungi un ritardo, anche se questo è zero:

[_controller performSelector:NSSelectorFromString(@"someMethod")
    withObject:nil
    afterDelay:0];

Questo rimuove l'avvertimento, presumibilmente perché rassicura il compilatore che nessun oggetto può essere restituito e in qualche modo mal gestito.


2
Sai se questo in realtà risolve i relativi problemi di gestione della memoria o ha gli stessi problemi ma Xcode non è abbastanza intelligente da avvisarti con questo codice?
Aaron Brager,

Questa semanticamente non è la stessa cosa! Usando performSelector: withObject: AfterDelay: eseguirà il selettore nella prossima esecuzione del runloop. Pertanto, questo metodo restituisce immediatamente.
Florian,

10
@Florian Ovviamente non è lo stesso! Leggi la mia risposta: dico se accettabile, perché il risultato è nullo e il ciclo di scorrimento. Questa è la prima frase della mia risposta.
opaco

34

Ecco una macro aggiornata basata sulla risposta fornita sopra. Questo dovrebbe permetterti di racchiudere il tuo codice anche con una dichiarazione di ritorno.

#define SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(code)                        \
    _Pragma("clang diagnostic push")                                        \
    _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")     \
    code;                                                                   \
    _Pragma("clang diagnostic pop")                                         \


SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(
    return [_target performSelector:_action withObject:self]
);

6
returnnon deve essere all'interno della macro; return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]);funziona e sembra anche più sano.
uasi,

31

Questo codice non implica flag di compilazione o chiamate di runtime dirette:

SEL selector = @selector(zeroArgumentMethod);
NSMethodSignature *methodSig = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation invoke];

NSInvocationconsente di impostare più argomenti in modo che diversamente performSelectorfunzionerà con qualsiasi metodo.


3
Sai se questo in realtà risolve i relativi problemi di gestione della memoria o ha gli stessi problemi ma Xcode non è abbastanza intelligente da avvisarti con questo codice?
Aaron Brager,

1
Si potrebbe dire che risolve i problemi di gestione della memoria; ma questo perché sostanzialmente ti consente di specificare il comportamento. Ad esempio, puoi scegliere di lasciare che l'invocazione mantenga o meno gli argomenti. Per quanto ne so attualmente, tenta di correggere i problemi di mancata corrispondenza della firma che potrebbero apparire fidandosi di sapere cosa stai facendo e di non fornire dati errati. Non sono sicuro che tutti i controlli possano essere eseguiti in fase di esecuzione. Come menzionato in un altro commento, mikeash.com/pyblog/… spiega bene cosa possono fare le discrepanze.
Mihai Timar,

20

Bene, molte risposte qui, ma poiché questo è un po 'diverso, combinando alcune risposte ho pensato di inserirle. Sto usando una categoria NSObject che controlla per assicurarsi che il selettore ritorni vuoto, e sopprime anche il compilatore avvertimento.

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "Debug.h" // not given; just an assert

@interface NSObject (Extras)

// Enforce the rule that the selector used must return void.
- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object;
- (void) performVoidReturnSelector:(SEL)aSelector;

@end

@implementation NSObject (Extras)

// Apparently the reason the regular performSelect gives a compile time warning is that the system doesn't know the return type. I'm going to (a) make sure that the return type is void, and (b) disable this warning
// See http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown

- (void) checkSelector:(SEL)aSelector {
    // See http://stackoverflow.com/questions/14602854/objective-c-is-there-a-way-to-check-a-selector-return-value
    Method m = class_getInstanceMethod([self class], aSelector);
    char type[128];
    method_getReturnType(m, type, sizeof(type));

    NSString *message = [[NSString alloc] initWithFormat:@"NSObject+Extras.performVoidReturnSelector: %@.%@ selector (type: %s)", [self class], NSStringFromSelector(aSelector), type];
    NSLog(@"%@", message);

    if (type[0] != 'v') {
        message = [[NSString alloc] initWithFormat:@"%@ was not void", message];
        [Debug assertTrue:FALSE withMessage:message];
    }
}

- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    // Since the selector (aSelector) is returning void, it doesn't make sense to try to obtain the return result of performSelector. In fact, if we do, it crashes the app.
    [self performSelector: aSelector withObject: object];
#pragma clang diagnostic pop    
}

- (void) performVoidReturnSelector:(SEL)aSelector {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self performSelector: aSelector];
#pragma clang diagnostic pop
}

@end

'V' deve essere sostituito da _C_VOID? _C_VOID è dichiarato in <objc / runtime.h>.
Rik Renich,

16

Per amor dei posteri, ho deciso di gettare il mio cappello sul ring :)

Di recente ho visto sempre più ristrutturazioni dal paradigma target/ selector, a favore di cose come protocolli, blocchi, ecc. Tuttavia, c'è un rimpiazzo di sostituzione performSelectorche ho usato alcune volte ora:

[NSApp sendAction: NSSelectorFromString(@"someMethod") to: _controller from: nil];

Sembrano essere un sostituto pulito, sicuro e quasi identico per ARC performSelectorsenza doversi preoccupare troppo objc_msgSend().

Tuttavia, non ho idea se ci sia un analogo disponibile su iOS.


6
Grazie per aver compreso questo .. E 'disponibile in iOS: [[UIApplication sharedApplication] sendAction: to: from: forEvent:]. L'ho esaminato una volta, ma mi sembra imbarazzante usare una classe relativa all'interfaccia utente nel mezzo del tuo dominio o servizio solo per fare una chiamata dinamica .. Grazie per averlo incluso!
Eduardo Scoz,

2
Ew! Avrà un sovraccarico maggiore (poiché deve verificare se il metodo è disponibile e camminare sulla catena del risponditore se non lo è) e avere un comportamento di errore diverso (camminare sulla catena del risponditore e restituire NO se non riesce a trovare nulla che risponde al metodo, anziché semplicemente arrestarsi in modo anomalo). Inoltre non funziona quando si desidera il idfrom-performSelector:...
tc.

2
@tc. Non "cammina sulla catena del risponditore" a meno che non to:sia nullo, cosa che non lo è. Va direttamente all'oggetto target senza alcun controllo in anticipo. Quindi non c'è "più spese generali". Non è un'ottima soluzione, ma il motivo che dai non è il motivo. :)
matt,

15

La risposta di Matt Galloway su questa discussione spiega il perché:

Considera quanto segue:

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

Ora, come può ARC sapere che il primo restituisce un oggetto con un conteggio di mantenimento di 1 ma il secondo restituisce un oggetto che è stato rilasciato automaticamente?

Sembra che sia generalmente sicuro eliminare l'avviso se si ignora il valore restituito. Non sono sicuro di quale sia la migliore pratica se hai davvero bisogno di ottenere un oggetto conservato da performSelector - diverso da "non farlo".


14

@ c-road fornisce il collegamento corretto con la descrizione del problema qui . Di seguito puoi vedere il mio esempio, quando performSelector provoca una perdita di memoria.

@interface Dummy : NSObject <NSCopying>
@end

@implementation Dummy

- (id)copyWithZone:(NSZone *)zone {
  return [[Dummy alloc] init];
}

- (id)clone {
  return [[Dummy alloc] init];
}

@end

void CopyDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy copy];
}

void CloneDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy clone];
}

void CopyDummyWithLeak(Dummy *dummy, SEL copySelector) {
  __unused Dummy *dummyClone = [dummy performSelector:copySelector];
}

void CloneDummyWithoutLeak(Dummy *dummy, SEL cloneSelector) {
  __unused Dummy *dummyClone = [dummy performSelector:cloneSelector];
}

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    Dummy *dummy = [[Dummy alloc] init];
    for (;;) { @autoreleasepool {
      //CopyDummy(dummy);
      //CloneDummy(dummy);
      //CloneDummyWithoutLeak(dummy, @selector(clone));
      CopyDummyWithLeak(dummy, @selector(copy));
      [NSThread sleepForTimeInterval:1];
    }} 
  }
  return 0;
}

L'unico metodo che causa la perdita di memoria nel mio esempio è CopyDummyWithLeak. Il motivo è che ARC non lo sa, che copySelector restituisce l'oggetto trattenuto.

Se esegui Memory Leak Tool puoi vedere la seguente immagine: inserisci qui la descrizione dell'immagine ... e non ci sono perdite di memoria in nessun altro caso: inserisci qui la descrizione dell'immagine


6

Per rendere la macro di Scott Thompson più generica:

// String expander
#define MY_STRX(X) #X
#define MY_STR(X) MY_STRX(X)

#define MYSilenceWarning(FLAG, MACRO) \
_Pragma("clang diagnostic push") \
_Pragma(MY_STR(clang diagnostic ignored MY_STR(FLAG))) \
MACRO \
_Pragma("clang diagnostic pop")

Quindi usalo in questo modo:

MYSilenceWarning(-Warc-performSelector-leaks,
[_target performSelector:_action withObject:self];
                )

FWIW, non ho aggiunto la macro. Qualcuno l'ha aggiunto alla mia risposta. Personalmente, non userei la macro. Il pragma è lì per aggirare un caso speciale nel codice e i pragmi sono molto espliciti e diretti su ciò che sta accadendo. Preferisco tenerli in posizione piuttosto che nasconderli, o astrarli dietro una macro, ma sono solo io. YMMV.
Scott Thompson,

@ScottThompson È giusto. Per me è facile cercare questa macro attraverso la mia base di codice e in genere aggiungo anche un avviso non silenziato per affrontare il problema sottostante.
Ben Flynn,

6

Non eliminare gli avvisi!

Non ci sono meno di 12 soluzioni alternative per armeggiare con il compilatore.
Mentre sei intelligente al momento della prima implementazione, pochi ingegneri sulla Terra possono seguire i tuoi passi e questo codice alla fine si romperà.

Percorsi sicuri:

Tutte queste soluzioni funzioneranno, con un certo grado di variazione rispetto al tuo intento originale. Supponiamo che parampossa esserlo nilse lo desideri:

Percorso sicuro, stesso comportamento concettuale:

// GREAT
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

Percorso sicuro, comportamento leggermente diverso:

(Vedi questa risposta)
Usa qualsiasi thread al posto di [NSThread mainThread].

// GOOD
[_controller performSelector:selector withObject:anArgument afterDelay:0];
[_controller performSelector:selector withObject:anArgument afterDelay:0 inModes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorInBackground:selector withObject:anArgument];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

Percorsi pericolosi

Richiede un qualche tipo di silenziamento del compilatore, che è destinato a rompersi. Si noti che attualmente, si è rotto in Swift .

// AT YOUR OWN RISK
[_controller performSelector:selector];
[_controller performSelector:selector withObject:anArgument];
[_controller performSelector:selector withObject:anArgument withObject:nil];

3
La formulazione è molto sbagliata. I percorsi sicuri non sono affatto più sicuri che pericolosi. È probabilmente più pericoloso perché nasconde implicitamente l'avvertimento.
Bryan Chen,

Risolverò la formulazione per non offenderla, ma resto fedele alla mia parola. L'unica volta che trovo accettabile l'avviso di silenziamento è se non possiedo il codice. Nessun ingegnere può tranquillamente mantenere il codice silenziato senza comprendere tutte le conseguenze, il che significherebbe leggere questo argomento, e questa pratica è chiaramente rischiosa; specialmente se si considerano le 12 solide alternative in inglese.
SwiftArchitect il

1
No. Non hai capito il mio punto. L'uso nonperformSelectorOnMainThread è un buon modo per mettere a tacere l'avvertimento e ha effetti collaterali. (non risolve la perdita di memoria) L'extra sopprime esplicitamente l'avviso in modo molto chiaro. #clang diagnostic ignored
Bryan Chen

È vero che eseguire un selettore su un non - (void)metodo è il vero problema.
SwiftArchitect,

e come si chiama un selettore con più argomenti attraverso questo ed essere al sicuro allo stesso tempo? @SwiftArchitect
Catalin

4

Perché stai usando ARC devi usare iOS 4.0 o successivo. Questo significa che potresti usare i blocchi. Se invece di ricordare al selettore di eseguirti invece avesse preso un blocco, ARC sarebbe in grado di tracciare meglio ciò che sta realmente accadendo e non dovresti correre il rischio di introdurre accidentalmente una perdita di memoria.


In realtà, i blocchi rendono molto semplice la creazione accidentale di un ciclo di mantenimento che ARC non risolve. Vorrei ancora che ci fosse un avvertimento del compilatore quando si utilizzava implicitamente selftramite un ivar (ad esempio ivarinvece di self->ivar).
tc.

Intendi come -Wimplicit-retain-self?
OrangeDog,

2

Invece di usare l'approccio a blocchi, che mi ha dato alcuni problemi:

    IMP imp = [_controller methodForSelector:selector];
    void (*func)(id, SEL) = (void *)imp;

Userò NSInvocation, in questo modo:

    -(void) sendSelectorToDelegate:(SEL) selector withSender:(UIButton *)button 

    if ([delegate respondsToSelector:selector])
    {
    NSMethodSignature * methodSignature = [[delegate class]
                                    instanceMethodSignatureForSelector:selector];
    NSInvocation * delegateInvocation = [NSInvocation
                                   invocationWithMethodSignature:methodSignature];


    [delegateInvocation setSelector:selector];
    [delegateInvocation setTarget:delegate];

    // remember the first two parameter are cmd and self
    [delegateInvocation setArgument:&button atIndex:2];
    [delegateInvocation invoke];
    }

1

Se non è necessario passare alcun argomento, è necessario utilizzare una soluzione semplice valueForKeyPath. Questo è persino possibile su un Classoggetto.

NSString *colorName = @"brightPinkColor";
id uicolor = [UIColor class];
if ([uicolor respondsToSelector:NSSelectorFromString(colorName)]){
    UIColor *brightPink = [uicolor valueForKeyPath:colorName];
    ...
}

-2

Puoi anche usare un protocollo qui. Quindi, crea un protocollo come questo:

@protocol MyProtocol
-(void)doSomethingWithObject:(id)object;
@end

Nella tua classe che deve chiamare il tuo selettore, hai quindi una proprietà @.

@interface MyObject
    @property (strong) id<MyProtocol> source;
@end

Quando è necessario chiamare @selector(doSomethingWithObject:)un'istanza di MyObject, procedere come segue:

[self.source doSomethingWithObject:object];

2
Ehi Wu, grazie, ma il punto di usare NSSelectorFromString è quando non sai quale selettore vuoi chiamare durante l'esecuzione.
Eduardo Scoz,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.