performSelector può causare una perdita di perché selettore è sconosciuto

Ricevo il seguente messaggio di avviso con l’ARCO compilatore:

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

Ecco quello che sto facendo:

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

Perché ricevo questo avviso? Capisco che il compilatore non è in grado di controllare se il selettore esiste o non esiste, ma perché vorrei che la causa di una perdita? E come posso modificare il mio codice in modo che non si ottiene questo avviso più?

  • PUÒ causare una perdita. Per evitare il messaggio di avviso, si dovrebbe passare/store selettori come le stringhe, ad eccezione, per il momento, si sta assegnando come un’azione. Se la riga sopra è dove si assegna come un’azione, sto anche chiedendo perché non basta usare @selector(someMethod:) ??
  • Il nome della variabile è dinamico, dipende da un sacco di altre cose. C’è il rischio che mi chiamano qualcosa che non esiste, ma non è questo il problema.
  • Che… è cattiva pratica. Selettore di nomi non dovrebbe essere dinamico come il metodo sono legati al grado di fare ciò che si desidera. Pertanto, il rischio che si potrebbe chiamare qualcosa che non esiste è ciò che l’avviso è di circa.
  • che cosa si intende per conservazione “eccetto che per il momento si sta assegnando come un’azione”. Sto memorizzare il valore come stringa perché viene dalla configurazione e la modifica durante l’esecuzione dell’app, in modo da utilizzare @selector(metodo:) non è possibile.
  • perché chiamare dinamicamente un metodo su un oggetto per essere cattiva pratica? Non è lo scopo di NSSelectorFromString() a sostegno di questa pratica?
  • Hmm credo che tu abbia ragione. Questo vuol cancellare l’avviso? SEL mySelector = NSSelectorFromString(@”someMethod”); if (mySelector != nil) { [_controller performSelector:mySelector]; }
  • Si potrebbe/dovrebbe anche verificare [_controller respondsToSelector:mySelector] prima di impostare via performSelector:
  • Grazie Matt. Buon punto sul respondsToSelector. Appena provato, e non di eliminare l’avviso, purtroppo. 🙂
  • Vorrei poter votare in basso: “… è cattiva pratica.”
  • Se si conosce la stringa letterale, basta usare @selector() in modo che il compilatore è in grado di dire qual è il nome del selettore è. Se il tuo codice è chiamata NSSelectorFromString() con una stringa che è costruito o forniti in fase di runtime, quindi è necessario utilizzare NSSelectorFromString().
  • Curiosamente, non si ottiene questo messaggio di avviso per tutti i gusti di performSelector. Le versioni definite off di classe NSObject vs protocollo di NSObject non sembrano provocare questo messaggio.
  • oppositori in tutto il mondo…
  • sì, e molti hanno cercato di trasformare in modo significativo la dinamica linguaggio Objective-C in una rigida statico per anni. Contento che finalmente hanno spostato in un’altra lingua.

 

19 Replies
  1. 1195

    Soluzione

    Il compilatore è un avviso su questo per un motivo. È molto raro che questo avviso non dovrebbe essere semplicemente ignorato, ed è facile da aggirare. 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ù stringatamente (anche se di difficile lettura & senza la guardia):

    SEL selector = NSSelectorFromString(@"someMethod");
    ((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);

    Spiegazione

    Quello che succede è che ti stai chiedendo il controller per il C puntatore a funzione per il metodo corrispondente al controller. Tutti NSObjects rispondere a methodForSelector:, ma è anche possibile utilizzare class_getMethodImplementation in Objective-C, runtime (utile se si dispone solo di un protocollo di riferimento, come id<SomeProto>). Questi puntatori a funzione sono chiamati IMPs, e sono semplici typedefed puntatori a funzione (id (*IMP)(id, SEL, ...))1. Questo può essere vicino a il metodo effettivo firma del metodo, ma non sempre corrispondono esattamente.

    Una volta il IMP, è necessario eseguire il cast di un puntatore a funzione che include tutti i dettagli dell’ARCO esigenze (compresi i due implicito, nascosto argomenti self e _cmd di ogni Obiettivo-C metodo di chiamata). Questo viene gestito nella terza riga ((void *) sul lato destro semplicemente indica al compilatore che si sa cosa si sta facendo e di non generare un messaggio di avviso dal momento che i tipi di puntatore non corrispondono).

    Infine, chiamare la funzione di puntatore2.

    Esempio Complesso

    Quando il selettore accetta argomenti o restituisce un valore, bisogna 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 Avviso di

    La ragione di questo avviso è che con l’ARCO, il runtime deve sapere cosa fare con il risultato di un metodo che si sta chiamando. Il risultato potrebbe essere qualsiasi cosa: void, int, char, NSString *, id, ecc. ARCO normalmente ottiene informazioni dal l’intestazione del tipo di oggetto su cui stai lavorando.3

    Ci sono solo 4 cose che ARCO vorresti prendere in considerazione per il valore di ritorno:4

    1. Ignorare non i tipi di oggetto (void, int, ecc)
    2. Mantenere il valore dell’oggetto, quindi rilasciare quando non sono più utilizzati (standard di assunzione)
    3. Il rilascio di nuovi valori oggetto quando non è più utilizzato (metodi in init/copy famiglia o a lui attribuite, con ns_returns_retained)
    4. Fare nulla & assumere oggetto restituito valore sarà valida in ambito locale (fino a più interna rilascio piscina è esaurito, attribuito con ns_returns_autoreleased)

    La chiamata a methodForSelector: si assume che il valore di ritorno del metodo sta chiamando è un oggetto, ma non mantiene la/il rilascio di essa. Così si potrebbe creare un problema se il vostro oggetto è supposto per essere rilasciato, come in #3 di cui sopra (che è il metodo che si chiama restituisce un nuovo oggetto).

    Per i selettori che si sta cercando di chiamata di ritorno void o altri non-oggetti, si può attivare la funzionalità del compilatore per ignorare il messaggio di avviso, ma può essere pericoloso. Ho visto Clang passare attraverso un paio di iterazioni di come gestisce il ritorno a valori che non sono assegnati a variabili locali. Non c’è ragione che con l’ARCO abilitato che non è in grado di trattenere e rilasciare il valore dell’oggetto restituito da methodForSelector: anche se non si desidera utilizzarlo. Dal compilatore punto di vista, è un oggetto, dopo tutto. Che significa che se il metodo che si sta chiamando, someMethod, è la restituzione di un non oggetto void), si potrebbe finire con un bidone della spazzatura valore del puntatore essere conservato/rilasciato e crash.

    Ulteriori Argomenti

    Una considerazione è che questo è lo stesso avviso si verifica con performSelector:withObject: e si potrebbe incorrere in problemi simili con il non dichiarare come metodo consuma parametri. ARCO consente di dichiarare consumato parametri, e se il metodo consuma il parametro, probabilmente alla fine di inviare un messaggio ad uno zombie e crash. Ci sono modi per aggirare questo con ponte casting, ma in realtà sarebbe meglio usare semplicemente il IMP e puntatore a funzione della metodologia di cui sopra. Poiché consumato parametri sono raramente un problema, questo non è probabile a venire.

    Statico Selettori

    È interessante notare che, il compilatore non si lamentano selettori dichiarato staticamente:

    [_controller performSelector:@selector(someMethod)];

    La ragione di questo è perché il compilatore è in realtà in grado di registrare tutte le informazioni circa il selettore e l’oggetto durante la compilazione. Non ha bisogno di fare qualsiasi ipotesi nulla. (Ho controllato questo un anno fa, guardando il sorgente, ma non ho un riferimento per il momento).

    Soppressione

    Cercando di pensare a una situazione in cui la soppressione di questo avviso sarebbe necessario e buona progettazione di codice, io sono venuta in bianco. Qualcuno si prega di condividere se hanno avuto un’esperienza dove la silenziosità di questo avviso era necessario (e non di gestire le cose per bene).

    Più

    È possibile costruire un NSMethodInvocation per gestire anche questo, ma questo richiede un sacco più di digitazione ed è anche più lenta, quindi non c’è motivo di farlo.

    Storia

    Quando il performSelector: famiglia di metodi di prima è stato aggiunto Objective-C, ARCO non esiste. Durante la creazione di ARCO, Apple ha deciso che un avviso dovrebbe essere generati per questi metodi, come un modo per guidare gli sviluppatori verso l’uso di altri mezzi per definire in modo esplicito come la memoria dovrebbe essere gestito durante l’invio di arbitrario di messaggi tramite un nome di un selettore. In Objective-C, gli sviluppatori sono in grado di fare questo utilizzando il linguaggio C getta sul raw puntatori a funzione.

    Con l’introduzione di Swift, Apple ha documentato il performSelector: famiglia di metodi come “intrinsecamente pericoloso” e non sono disponibili a Swift.

    Nel corso del tempo, abbiamo visto questa progressione:

    1. Prime versioni di Objective-C consente performSelector: (manuale di gestione della memoria)
    2. Objective-C con l’ARCO avverte per l’uso di performSelector:
    3. Swift non hanno accesso a performSelector: e documenti di questi metodi come “intrinsecamente pericoloso”

    L’idea di invio di messaggi basato su un selettore non è, tuttavia, un “intrinsecamente pericoloso” caratteristica. Questa idea è stata utilizzata con successo per un lungo periodo di tempo in Objective-C, come pure molti altri linguaggi di programmazione.


    1 Tutti Objective-C metodi sono due nascosti argomenti, self e _cmd che sono implicitamente aggiunto quando si chiama un metodo.

    2 Chiamare un NULL funzione non è sicuro in C. La guardia utilizzato per verificare la presenza del controller garantisce un oggetto. Pertanto sappiamo, otteniamo un IMP da methodForSelector: (anche se può essere _objc_msgForward, l’inoltro dei messaggi di sistema). In sostanza, con il posto di guardia, sappiamo di avere una funzione di chiamata.

    3 in Realtà, è possibile ottenere informazioni errate se dichiarare gli oggetti come id e non sei l’importazione di tutte le intestazioni. È possibile che alla fine si blocca in codice che il compilatore pensa che è bene. Questo è molto raro, ma può accadere. Di solito si ottiene solo un avvertimento che non sa quale dei due firme del metodo da scegliere.

    4 Vedere l’ARCO di riferimento sulla mantenuti i valori di ritorno e unretained valori di ritorno per ulteriori dettagli.

    • Se il codice risolve il fissaggio problema, mi chiedo perché performSelector: metodi non sono implementate in questo modo. Hanno rigoroso metodo di firma (ritorno id, prendere uno o due ids), quindi non di tipo primitivo bisogno di essere gestito.
    • Con MRC non so che sia. Se l’ (dinamico) selettore restituisce mantenuto oggetto o addirittura consuma l’argomento. Io uso i selettori solo per raggiungere il target di+azione modello e non ho mai passare copy o alloc, così ho il silenzio che avviso a livello globale piuttosto che fare il pragma push/pop.
    • il corto di esso è, trasformando l’avviso di sconto a livello globale richiede che tutti i membri del team per capire (e ricordare) che è limitato all’utilizzo di questo metodo solo per i metodi che restituiscono oggetti che non ha bisogno di essere rilasciato. Credo che questo avviso per essere utile perché fa questo per te, e io lo lascio abilitato.
    • Ma se si passa variabile SEL per performSelector non sai cosa semantica di usare manualmente. Quello che sto cercando di dire è che questo non è legato a ARCO, ma MRC così. Sembra che questo è sempre stato un problema (dato che il Prossimo), ma la gente semplicemente ignorato? O utilizzato correttamente per convenzione?
    • Così si possono chiamare performSelector dinamiche di selezione con un solo argomento senza valore di ritorno previsto?
    • a causa del modo in cui il compilatore gestisce vuoto restituisce, questo non causare eventuali problemi ora, ma il futuro sicuro con l’ARCO è per il tipo di ritorno a partita.
    • ma che cosa circa l’argomento, nel mio caso è sempre oggetto. Ho solo paura che l’argomento verrà una perdita in qualche modo. 🙂 Questo problema è correlato agli argomenti o solo per il valore di ritorno? Ora uso NSInvocation che è il modo più sicuro presumo.
    • l’argomento è gestita in base alla definizione del metodo del prototipo (non sarà conservato/rilasciato). La preoccupazione è per lo più basato sul tipo di ritorno.
    • Il “Complesso” dà un errore Cannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *' quando utilizzando le più recenti Xcode. (5.1.1) Ancora, ho imparato un sacco!!!
    • si lavora ancora per me in un nuovo progetto in Xcode. Forse avete più di avviso flag abilitato?
    • void (*func)(id, SEL) = (void *)imp; non compilare, l’ho sostituito con void (*func)(id, SEL) = (void (*)(id, SEL))imp;
    • in Xcode 5.1.1 con la configurazione di default, void (*func)(id, SEL) = (void *)imp; non compilare. Forse avete più tempo di compilazione flag abilitato?
    • Ho creato un nuovo OS X Applicazione Cocoa progetto 5.1.1, SDK 10.9, 64-bit di destinazione, in modo che le impostazioni di default. Non importa, funziona con typecasting, spero di non causare problemi. Grazie mille per l’articolo.
    • cambiare void (*func)(id, SEL) = (void *)imp; per <…> = (void (*))imp; o <…> = (void (*) (id, SEL))imp;
    • Scrivere il codice soppressione. Può essere più facile leggere il codice se si aggiunge solo il #pragma – se questo è tutto il necessario.
    • Incredibile risposta. In pratica si utilizza un typedef potrebbe aumentare la leggibilità di un sacco. Nel mio caso ho messo typedef void(*progress_cb)(id, SEL, NSNumber*); nell’intestazione, e quindi utilizzare progress_cb func = (progress_cb)[target methodForSelector:selector];
    • La soluzione produce EXC_BAD_ACCESS si blocca su per arm64 se func restituisce id. Apple docs dire che il cast è richiesto: developer.apple.com/library/ios/documentation/General/… può essere modificato solo per 5 minuti×i Commenti possono essere modificate solo per 5 minuti×i Commenti possono essere modificate solo per 5 minuti
    • il complesso di esempio viene illustrato come gestire correttamente i tipi di ritorno.
    • grazie per questa risposta completa. Solo una domanda: è questa la soluzione (complesso codice di esempio) cassaforte da utilizzare per l’App Store? c’è il rischio di ottenere la mia app rifiutato a causa di utilizzo di questo codice?
    • Dal momento che si sta solo sopprimere le avvertenze per la chiamata, non vedo alcuna differenza, tranne per la maggiore complessità della PMI relativo codice. Due blocchi di codice che funziona lo stesso, preferisco il codice più semplice… Se il codice funziona, non funziona sempre.
    • Questa non è una risposta molto buona, perché è solo tacere l’avviso con il codice strano. Aspetto la risposta da “SwiftArchitect” per vedere molti modi di fare la stessa cosa.
    • Una giustificazione che posso pensare per la soppressione di un messaggio di avviso, e il caso in cui lo faccio, se è venuta da un 3rd party biblioteca.
    • e come si fa a chiamare un selettore con più argomenti e di essere al sicuro nello stesso tempo?
    • È questo ancora il metodo migliore, in xCode 8 edificio per iOS 10? Credo che io sono sempre errori quando si utilizza il metodo statico per la via (che credo sia diverso da quello che hai detto sopra ha fatto questo cambiamento?) Qual è il modo migliore per procedere in xCode 8 e iOS 10?
    • Ottima risposta, e spiega perché io sono sempre un crash. Succede solo a verificarsi quando il metodo richiamato ha un (void) tipo di ritorno. Modifica la firma per (id) e ritorno nil rimuove il crash. … Sto cercando di indovinare la vostra conservare la spiegazione sta accadendo.
    • Nel mio caso, io non sono sicuro se ho un void ritorno il metodo o un id uno. Ma si può controllare con: [self methodSignatureForSelector:selector].methodReturnLength > 0.
    • Questo è chiaramente la migliore risposta al perché l’avviso esiste. Tuttavia, utilizzando il codice di questa soluzione si nasconde il problema dal compilatore senza risolvere. Se conosci il tipo di ritorno di tutti i selettori possono essere dati è possibile utilizzare con questo codice in modo sicuro, o è possibile utilizzare la risposta data da Scott Thomson. Se si desidera verificare la tua ipotesi e ‘ in fase di runtime, vedi la risposta di Chris Principe che estrae il risultato del tipo di selezione.
    • Qui la spiegazione è completamente sbagliato. L’unico problema è se il selettore è un metodo create (al cacao convenzione di denominazione, come prefisso del nome make... o create...), quindi se un oggetto deve essere restituito con +1 mantenere il conteggio. Se non vi è alcun valore di ritorno non ci può essere alcun problema accadendo. La soluzione più comune è il silenzio, il messaggio di avviso.
    • Che cosa è _controller qui?

  2. 1178

    In LLVM 3.0 compilatore in Xcode 4.2 è possibile eliminare il messaggio di 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 stai ricevendo l’errore in più luoghi, e si desidera utilizzare il C macro di sistema per nascondere il pragma, si può definire una macro per rendere più facile per eliminare l’avviso:

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

    È possibile utilizzare la macro come questa:

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

    Se avete bisogno il risultato della eseguite messaggio, si può fare questo:

    id result;
    SuppressPerformSelectorLeakWarning(
        result = [_target performSelector:_action withObject:self]
    );
    • Questo metodo può causare perdite di memoria quando l’ottimizzazione è diverso da Nessuno.
    • No, non è possibile, a meno che non si sta invocando divertente metodi come “initSomething” o “newSomething” o “somethingCopy”.
    • nella nostra esperienza lo ha fatto al momento. Apple potrebbe aver fatto alcune correzioni di Xcode.
    • Hai ancora problemi di perdite con Xcode 4.5.1?
    • Ho capito che è sufficiente utilizzare #pragma clang diagnostic ignored "-Warc-performSelector-leaks" all’inizio dell’implementazione della classe.
    • Che funziona, ma che non si spegne l’attenzione per tutto il file potrebbe non essere necessario o voluto. Wrappping con il pop e push-pragma sono molto più pulito e più sicuro.
    • Tutto questo non fa altro che silenzi il compilatore. Questo non risolve il problema. Se il selettore non esiste, sei praticamente fregato.
    • Questo dovrebbe essere utilizzato solo quando è avvolto da un if ([_target respondsToSelector:_selector]) { o simile logica.
    • quindi, se il metodo in questione non restituisce un valore, questo è sicuro per l’uso? Grazie.
    • GRAZIE per questo, questo è il genere di quello che ho immaginato, ma bene hanno confermato.
    • Perché si utilizza un do ... while loop?
    • Non ho aggiunto la tecnica per l’utilizzo di macro per nascondere la pragma come vorrei non usare mai che il meccanismo di me. Io preferisco il pragma di essere esplicito e ha commentato piuttosto che offuscato. Detto questo, Il ciclo do-while sarebbe fornire il contesto e l’ambito per il codice nella “Roba” e si trasforma essenzialmente il macro in una dichiarazione piuttosto che un galleggiante blocco di codice.

  3. 208

    La mia idea è questa: dal momento che il selettore è sconosciuto per il compilatore, l’ARCO non è possibile applicare la corretta gestione della memoria.

    Infatti, ci sono momenti in cui la gestione della memoria è legata al nome del metodo da parte di una apposita convenzione. Nello specifico, sto pensando di convenienza costruttori contro fare metodi; l’ex di ritorno dalla convenzione una autoreleased oggetto; quest’ultima mantenuta oggetto. La convenzione si basa sui nomi del selettore, quindi, se il compilatore non conosce il selettore, quindi non è possibile applicare la corretta gestione della memoria regola.

    Se questo è corretto, penso che si può tranquillamente utilizzare il tuo codice, a condizione assicurarsi che tutto è ok, come per la gestione della memoria (ad esempio, che i vostri metodi non restituire gli oggetti che essi allocate).

    • Grazie per la risposta, vado a vedere di più questo per vedere cosa sta succedendo. Qualche idea su come posso ignorare l’avviso, anche se e farla sparire? Io odio avere l’avviso di seduta nel mio codice per sempre per ciò che è sicuro di chiamata.
    • Soluzione alternativa: che ne dite di un wrapper di performSelector che riceve una stringa (il nome del metodo) e la chiamata originale performSelector dall’indicizzazione in un array di SEL oggetti che sono noti per il compilatore (o forse di commutazione tra quelli SELs)? un po ‘ impacciata, forse, ma dovrebbe funzionare…
    • Non ho l’elenco di tutti i SEL oggetti, come quelli che possono essere implementati in sottoclassi. Unico modo sarebbe andare iterare su tutti i metodi e creare un NSArray di tutti i metodi per trovare quello giusto, ma poi cado nella stessa situazione, no?
    • Non so… da quando ObjC è dinamico, penso che avere un SEL potrebbe consentire l’ARCO meccanismo di costruire un po ‘ di codice in giro… si può solo provare e magari il report qui in modo che tutti sanno…
    • Così ho avuto la conferma da parte di qualcuno in Apple nel loro forum che questo è davvero il caso. Che sarà l’aggiunta di un dimenticato di override per permettere alle persone di disattivare questo avviso in versioni future. Grazie.
    • ottima soluzione per uso NSSelectorFromString ma l’avviso del compilatore è lo stesso 🙁
    • Questa risposta solleva alcune domande, come se ARC tenta di prendere decisioni su quando rilasciare qualcosa basato sulla convenzione di metodo e di nomi, come “reference counting”? Il comportamento di cui si descrivono i suoni solo marginalmente migliore di tutto arbitraria, se l’ARCO è il presupposto che il codice segue una certa convenzione anziché effettivamente tenere traccia dei riferimenti, non importa ciò che la convenzione è seguita.
    • ARCO automatizza il processo di aggiunta di conserva e rilascia in fase di compilazione. Non è garbage collection (che è il motivo per cui è così incredibilmente veloce e basso overhead). Non è arbitrario a tutti. Le regole di default sono a base consolidata ObjC convenzioni che sono stati applicati in modo coerente per decenni. Questo evita la necessità di aggiungere in modo esplicito un __attribute per ogni metodo di spiegare la gestione della memoria. Ma rende anche impossibile per il compilatore di gestire correttamente questo modello (un modello che ha usato per essere molto comune, ma è stata sostituita con una più robusta modelli negli ultimi anni).
    • Così non si può più avere un ivar di tipo SEL e assegnare i vari selettori a seconda della situazione? Modo per andare, linguaggio dinamico…
    • Nel mio caso, il selettore è uno dei due possibili valori. Posso utilizzare un’istruzione switch e chiamare ogni selettore in modo esplicito in ogni caso…
    • Certamente si può fare, basta disattivare l’avviso (vedi altra risposta).
    • tutto questo cosa ha fatto sentire arbitrario fino a quando ho effettivamente letto le spec. Mi fa sentire molto più sicuro. clang.llvm.org/docs/AutomaticReferenceCounting.html
    • Perché non chiamare questa inquietante convenzione per nome? Non ci dovrebbero essere problemi nel caso in cui siete impostazione delle proprietà degli oggetti utilizzando i selettori: Come una semplice regola, se si può evitare di performSelector:withObject: ARCO deve essere fine e l’avviso può essere messa a tacere, senza paura.
    • Curiosamente, non si ottiene questo messaggio di avviso per tutti i gusti di performSelector. Le versioni definite off di classe NSObject vs protocollo di NSObject non sembrano provocare questo messaggio.

  4. 120

    Nel progetto Impostazioni di generazione, sotto Altro Avviso Bandiere (WARNING_CFLAGS), aggiungere

    -Wno-arc-performSelector-leaks

    Ora basta assicurarsi che il selettore che si sta chiamando non causare il vostro oggetto da conservare o copiati.

    • Nota è possibile aggiungere la stessa bandiera per i file specifici, piuttosto che l’intero progetto. Se si guarda sotto Build Fasi->Compilare i Sorgenti, è possibile impostare per ogni file Flag di compilazione (proprio come si vuole fare per escludere i file dall’ARCO). Nel mio progetto di un solo file deve utilizzare i selettori in questo modo, quindi ho escluso e lasciato gli altri.
  5. 111

    Come una soluzione fino a quando il compilatore consente di sovrascrivere il messaggio di avviso, è possibile utilizzare il runtime

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

    invece di

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

    Dovrete

    #import <objc/message.h>

    • ARCO riconosce Cacao convenzioni e poi aggiunge trattiene e rilascia base a tali convenzioni. Perché C non seguire tali convenzioni, l’ARCO impone l’utilizzo di memoria manuale di tecniche di gestione. Se si crea un CF oggetto, è necessario CFRelease() su di esso. Se si dispatch_queue_create(), è necessario dispatch_release(). Linea di fondo, se si vuole evitare l’ARCO di avvisi, si possono evitare utilizzando C oggetti e manuale di gestione della memoria. Inoltre, è possibile disattivare l’ARCO su una base per file utilizzando il-fno-objc-arco flag del compilatore su quel file.
    • Non senza fusione, non si può. Varargs non è la stessa come un esplicito il tipo di elenco di argomenti. È in genere necessario lavorare per coincidenza, ma io non considero “per caso”, per essere corretta.
    • Non farlo, [_controller performSelector:NSSelectorFromString(@"someMethod")]; e objc_msgSend(_controller, NSSelectorFromString(@"someMethod")); non sono equivalenti! Hanno un occhiata a Metodo di Firma Incongruenze e Una grande debolezza in Objective-C tipizzazione debole stanno spiegando il problema in profondità.
    • In questo caso, va bene. objc_msgSend non creare una firma del metodo corrispondente per ogni selettore che avrebbe funzionato correttamente in performSelector: o le sue varianti, poiché mai prendere oggetti come parametri. Fintanto che tutti i parametri siano puntatori (incl. gli oggetti), doppie e NSInteger/lungo, e il tuo ritorno è di tipo void, puntatore o lungo, poi objc_msgSend funzionerà correttamente.
  6. 88

    Per ignorare l’errore solo il file con l’esecuzione di selezione, aggiungere un #pragma come segue:

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

    Questo sarebbe ignorare il messaggio di avviso su questa linea, ma permette a tutto il resto del tuo progetto.

    • Ho capito che è anche possibile riattivarlo subito dopo il metodo in questione con #pragma clang diagnostic warning "-Warc-performSelector-leaks". So che se mi disattivare un avviso, mi piace girare per riprendere al più presto possibile, in modo da non accidentalmente lasciato un altro imprevisto avviso di scivolare via. È improbabile che questo è un problema, ma è solo la mia pratica ogni volta che spengo un messaggio di avviso.
    • È inoltre possibile ripristinare la precedente configurazione del compilatore stato utilizzando #pragma clang diagnostic warning push prima di apportare eventuali modifiche e #pragma clang diagnostic warning pop di ripristinare lo stato precedente. Utile se si disattiva carichi e non si vuole avere un sacco di ri-abilitare pragma linee di codice.
    • Sarà solo ignorare la seguente riga?
  7. 69

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

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

    Questo rimuove l’avviso, presumibilmente perché rassicura il compilatore che nessun oggetto può essere restituito, e in qualche modo riprovevole.

    • Sapete se questo effettivamente risolve i relativi problemi di gestione della memoria, o non si hanno gli stessi problemi, ma Xcode non è abbastanza intelligente per avvisare l’utente con questo codice?
    • Questo è semanticamente non è la stessa cosa!!! Utilizzando performSelector:withObject:AfterDelay: si esibirà il selettore nella successiva esecuzione del runloop. Pertanto, questo metodo restituisce immediatamente.
    • Ovviamente non è la stessa cosa! Leggi la mia risposta: io dico che se accettabile, perché il risultato è nullo e il runloop cicli. Che il prima frase della mia risposta.
  8. 34

    Qui è un aggiornamento macro in base alla risposta data in precedenza. Questo dovrebbe consentire di avvolgere il vostro codice, anche con un ritorno economico.

    #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]
    );
    • return non deve essere all’interno della macro; return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]); anche opere e un aspetto più sano.
  9. 31

    Questo codice non coinvolgere i flag di compilazione o diretto runtime chiama:

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

    NSInvocation consente a più argomenti per essere impostato in questo modo, a differenza di performSelector questo funziona su qualsiasi metodo.

    • Sapete se questo effettivamente risolve i relativi problemi di gestione della memoria, o non si hanno gli stessi problemi, ma Xcode non è abbastanza intelligente per avvisare l’utente con questo codice?
    • Si potrebbe dire che risolve i problemi di gestione della memoria; ma questo è perché in pratica consente di specificare il comportamento. Per esempio, si può scegliere di lasciare invocazione conservare gli argomenti o non. Per le mie conoscenze attuali, tenta di correggere la firma di mancata corrispondenza dei problemi che potrebbero verificarsi con la fiducia che si sa cosa si sta facendo e non si forniscono con dati non corretti. Non sono sicuro se tutti i controlli possono essere eseguiti in fase di runtime. Come mentiones in un altro commento, mikeash.com/pyblog/… ben spiega cosa di corrispondenza possono fare.
  10. 20

    Beh, un sacco di risposte, ma dal momento che questo è un po ‘ diverso, unendo un paio di risposte ho pensato di metterlo in. Sto usando un NSObject categoria che i controlli per assicurarsi che il selettore restituisce void, e anche sopprime l’avviso del compilatore.

    #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
    • Dovrebbe ‘v’ essere sostituito da _C_VOID? _C_VOID è dichiarato in <objc/runtime.h>.
    • Suona bene. Io non sto facendo molto con Objective-C più…
  11. 16

    Per i posteri amore, ho deciso di buttare il mio cappello sul ring 🙂

    Di recente ho visto più e più ristrutturazione di distanza dal target/selector paradigma, in favore di cose come i protocolli, blocchi, etc. Tuttavia, c’è una sostituzione drop-in per performSelector che ho usato un paio di volte e ora:

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

    Questi sembrano essere pulito, ARCO di sicurezza, e quasi identica di ricambio per performSelector senza dover molto con objc_msgSend().

    Però, non ho idea se c’è un analogico disponibile su iOS.

    • Grazie per aver compreso questo.. è disponibile in iOS: [[UIApplication sharedApplication] sendAction: to: from: forEvent:]. Ho guardato dentro una volta, ma e ‘ sorta di sente a disagio per l’utilizzo di un interfaccia utente correlate di classe in mezzo al vostro dominio o servizio giusto per fare una chiamata dinamica.. Grazie per aver compreso questo, però!
    • Ew! Ti hanno costi superiori (in quanto è necessario verificare se il metodo è disponibile in passeggiata e risponditore catena se non lo è) e sono di errore diverso comportamento (a piedi il risponditore catena e la restituzione di NO, se riesco a trovare nulla che risponde al metodo, invece di schiantarsi). Inoltre non funziona quando si desidera che il id da -performSelector:...
    • Non “a piedi fino al risponditore catena”, a meno che to: è nulla, non è così. Va dritto al bersaglio oggetto che non ha alcun controllo preventivo. Quindi non c’è “sovraccarico”. Non è una grande soluzione, ma la ragione si dà non è la ragione. 🙂
  12. 15

    Matt Galloway risposta su questo thread spiega il perché:

    Si consideri il seguente:

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

    Ora, come ARCO di sapere che il primo restituisce un oggetto con una mantenere il conteggio di 1, ma il secondo
    restituisce un oggetto che è autoreleased?

    Sembra che è generalmente sicuro per eliminare l’avviso se si sta ignorando il valore di ritorno. Io non sono sicuro di quale sia la pratica migliore è se si ha realmente bisogno per ottenere un mantenuto oggetto da performSelector — altri di “non fare quello”.

  13. 14

    @c-road che offre il giusto link con la descrizione del problema,qui. Qui di seguito potete vedere il mio esempio, quando performSelector causa 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 l’ARCO non sa, che copySelector restituisce mantenuto oggetto.

    Se si esegue Perdita di Memoria Strumento che si può vedere nell’immagine seguente:
    performSelector può causare una perdita di perché selettore è sconosciuto
    …e non ci sono perdite di memoria in qualsiasi altro caso:
    performSelector può causare una perdita di perché selettore è sconosciuto

  14. 6

    Per rendere Scott Thompson macro più generico:

    //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 utilizzarlo come questo:

    MYSilenceWarning(-Warc-performSelector-leaks,
    [_target performSelector:_action withObject:self];
                    )
    • FWIW, io non ho fatto aggiungere la macro. Qualcuno ha aggiunto che la mia risposta. Personalmente, non vorrei usare la macro. Pragma è lì per risolvere un caso speciale nel codice e la pragma sono molto esplicito e diretto su ciò che sta succedendo. Io preferisco tenerli in luogo invece di nascondersi, o di astrazione dietro una macro, ma questo è solo me. YMMV.
    • Che è giusto. Per me è facile per la ricerca di questa macro in tutta la mia base di codice e io di solito anche aggiungere una onu tacere avviso per affrontare il problema di fondo.
  15. 6

    Non sopprimere le avvertenze!

    Ci sono non meno di 12 soluzioni alternative ad armeggiare con il compilatore.

    Mentre l ‘ intelligente al tempo di prima attuazione, qualche ingegnere a Terra in grado di seguire le tue orme, e questo codice finiranno di rompere.

    Percorsi Sicuri:

    Tutte queste soluzioni funziona, con un certo grado di variazione dal tuo intento originale. Si supponga che param può essere nil, se lo si desidera:

    Rotta sicura, concettuale comportamento:

    //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]];

    Rotta sicura, un comportamento leggermente differente:

    (Vedi questo risposta)

    Utilizzare qualsiasi thread in luogo 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]];

    Rotte Pericolose

    Richiede un qualche tipo di compilatore di silenziamento, che è legato alla rottura. Si noti che, al momento attuale, è fatto pausa Swift.

    //AT YOUR OWN RISK
    [_controller performSelector:selector];
    [_controller performSelector:selector withObject:anArgument];
    [_controller performSelector:selector withObject:anArgument withObject:nil];
    • La formulazione è molto sbagliato. I percorsi di sicurezza non sono più sicuro di pericolosa. È probabilmente più pericoloso perché nasconde il messaggio di avviso in modo implicito.
    • Posso correggere il testo per non essere offensivo, ma io sto con la mia parola. L’unica volta che trovo silenziamento avviso è accettabile se non possiedo il codice. Nessun ingegnere può tranquillamente mantenere tacere codice senza capire tutte le conseguenze, il che significa che la lettura di questo argomento, e questa pratica è normale rischioso, soprattutto se si considera il 12, un inglese semplice, robusto alternative.
    • No. Non hai capito il mio punto. Utilizzando performSelectorOnMainThread è non un buon modo per disattivare l’avviso e non ha effetti collaterali. (non risolve la perdita di memoria) extra #clang diagnostic ignored esplicitamente eliminare l’avviso in modo molto chiaro.
    • Vero che l’esecuzione di un selettore di non - (void) metodo è il vero problema.
    • e come si fa a chiamare un selettore con più argomenti e di essere al sicuro nello stesso tempo? @SwiftArchitect
    • Più argomenti: vedi stackoverflow.com/q/8439052/218152

  16. 4

    Perché si utilizza l’ARCO è necessario utilizzare iOS 4.0 o versioni successive. Questo significa che si potrebbe utilizzare i blocchi. Se invece di ricordare il selettore di eseguire invece ha preso un blocco, l’ARCO sarebbe in grado di meglio a tenere traccia di ciò che sta realmente accadendo e che non avrebbe dovuto correre il rischio di accidentale introduzione di una perdita di memoria.

    • In realtà, blocchi rendono molto facile accidentalmente creare conservare ciclo di ARCO non risolvere. Ho ancora il desiderio che c’era un avviso del compilatore quando si implicitamente utilizzato self tramite un ivar (es. ivar invece di self->ivar).
    • Si intende come -Wimplicit-conservare-auto ?
  17. 2

    Invece di utilizzare il blocco di approccio, che mi ha dato alcuni problemi:

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

    Io uso NSInvocation, come questo:

        -(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];
        }
  18. 1

    Se non hai bisogno di passare argomenti, una soluzione semplice è quello di utilizzare valueForKeyPath. Questo è anche possibile, su Class oggetto.

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

    Si potrebbe anche usare un protocollo di qui. Così, la creazione di un protocollo in questo modo:

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

    In classe che deve chiamare il selettore, poi hanno un @proprietà.

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

    Quando è necessario chiamare @selector(doSomethingWithObject:) in un’istanza di MyObject, fare questo:

    [self.source doSomethingWithObject:object];
    • Ehi Wu, grazie, ma il punto di utilizzo il NSSelectorFromString è quando non si conosce il selettore che si desidera chiamare durante il runtime.

Lascia un commento