Estou recebendo o seguinte aviso do compilador ARC:
"performSelector may cause a leak because its selector is unknown".
Aqui está o que estou fazendo:
[_controller performSelector:NSSelectorFromString(@"someMethod")];
Por que recebo esse aviso? Entendo que o compilador não pode verificar se o seletor existe ou não, mas por que isso causaria um vazamento? E como posso alterar meu código para não receber mais esse aviso?
ios
objective-c
memory-leaks
automatic-ref-counting
Eduardo Scoz
fonte
fonte
Respostas:
Solução
O compilador está avisando sobre isso por um motivo. É muito raro que esse aviso seja simplesmente ignorado e é fácil contornar isso. Aqui está como:
Ou de maneira mais concisa (embora difícil de ler e sem a guarda):
Explicação
O que está acontecendo aqui é que você está solicitando ao controlador o ponteiro da função C para o método correspondente ao controlador. Todos
NSObject
respondemmethodForSelector:
, mas você também pode usarclass_getMethodImplementation
no tempo de execução Objective-C (útil se você tiver apenas uma referência de protocolo, comoid<SomeProto>
). Esses ponteiros de função são chamadosIMP
s e são simplestypedef
ponteiros de função ed (id (*IMP)(id, SEL, ...)
) 1 . Isso pode estar próximo da assinatura real do método, mas nem sempre corresponde exatamente.Depois de ter o
IMP
, você precisará convertê-lo em um ponteiro de função que inclua todos os detalhes que o ARC precisa (incluindo os dois argumentos ocultos implícitosself
e_cmd
todas as chamadas de método Objective-C). Isso é tratado na terceira linha (o(void *)
lado direito simplesmente informa ao compilador que você sabe o que está fazendo e não gera um aviso, pois os tipos de ponteiro não correspondem).Finalmente, você chama o ponteiro de função 2 .
Exemplo complexo
Quando o seletor recebe argumentos ou retorna um valor, você precisa mudar um pouco as coisas:
Raciocínio para aviso
O motivo desse aviso é que, com o ARC, o tempo de execução precisa saber o que fazer com o resultado do método que você está chamando. O resultado poderia ser qualquer coisa:
void
,int
,char
,NSString *
,id
, etc. ARC normalmente obtém essas informações do cabeçalho do tipo de objeto que você está trabalhando. 3Na verdade, existem apenas quatro coisas que a ARC consideraria para o valor de retorno: 4
void
,int
, etc.)init
/copy
ou atribuídos ans_returns_retained
)ns_returns_autoreleased
)A chamada para
methodForSelector:
assume que o valor de retorno do método que está chamando é um objeto, mas não o retém / libera. Portanto, você pode criar um vazamento se seu objeto for lançado como no item 3 acima (ou seja, o método que você está chamando retorna um novo objeto).Para os seletores que você está tentando chamar de retorno
void
ou outros não-objetos, você pode habilitar os recursos do compilador a ignorar o aviso, mas pode ser perigoso. Eu vi o Clang passar por algumas iterações de como ele lida com valores de retorno que não são atribuídos a variáveis locais. Não há razão para que, com o ARC ativado, ele não possa reter e liberar o valor do objeto retornado,methodForSelector:
mesmo que você não queira usá-lo. Do ponto de vista do compilador, é um objeto, afinal. Isso significa que, se o método que você está chamandosomeMethod
retornar um objeto não (incluindovoid
), você poderá acabar com um valor de ponteiro de lixo sendo retido / liberado e travado.Argumentos adicionais
Uma consideração é que esse é o mesmo aviso que ocorrerá
performSelector:withObject:
e você poderá ter problemas semelhantes ao não declarar como esse método consome parâmetros. O ARC permite declarar parâmetros consumidos e, se o método consumir o parâmetro, você provavelmente enviará uma mensagem para um zumbi e trava. Existem maneiras de contornar isso com a conversão em ponte, mas realmente seria melhor simplesmente usar aIMP
metodologia de ponteiro e de função acima. Como os parâmetros consumidos raramente são um problema, é provável que isso não ocorra.Seletores estáticos
Curiosamente, o compilador não irá reclamar dos seletores declarados estaticamente:
A razão para isso é porque o compilador realmente é capaz de registrar todas as informações sobre o seletor e o objeto durante a compilação. Não precisa fazer suposições sobre nada. (Eu verifiquei isso um ano atrás, olhando a fonte, mas não tenho uma referência no momento.)
Supressão
Ao tentar pensar em uma situação em que a supressão desse aviso seria necessária e um bom design de código, estou ficando em branco. Alguém compartilhe se eles tiveram uma experiência em que o silenciamento desse aviso era necessário (e o acima não lida com as coisas corretamente).
Mais
É possível criar um
NSMethodInvocation
para lidar com isso também, mas isso exige muito mais digitação e também é mais lento, por isso há poucas razões para fazê-lo.História
Quando a
performSelector:
família de métodos foi adicionada pela primeira vez ao Objective-C, o ARC não existia. Ao criar o ARC, a Apple decidiu que um aviso deveria ser gerado para esses métodos como uma maneira de orientar os desenvolvedores a usar outros meios para definir explicitamente como a memória deve ser manipulada ao enviar mensagens arbitrárias por meio de um seletor nomeado. No Objective-C, os desenvolvedores conseguem fazer isso usando projeções de estilo C em ponteiros de função bruta.Com a introdução do Swift, a Apple documentou a
performSelector:
família de métodos como "inerentemente inseguros" e eles não estão disponíveis para o Swift.Com o tempo, vimos essa progressão:
performSelector:
(gerenciamento manual de memória)performSelector:
performSelector:
e documenta esses métodos como "inerentemente inseguros"A idéia de enviar mensagens com base em um seletor nomeado não é, no entanto, um recurso "inerentemente inseguro". Essa idéia foi usada com sucesso por um longo tempo no Objective-C, bem como em muitas outras linguagens de programação.
1 Todos os métodos de Objective-C tem dois argumentos ocultos,
self
e_cmd
que são adicionados implicitamente quando você chamar um método.2 A chamada de uma
NULL
função não é segura em C. O guarda usado para verificar a presença do controlador garante que temos um objeto. Nós, portanto, sei que nós vamos ter umaIMP
demethodForSelector:
(embora possa ser_objc_msgForward
, a entrada no sistema de encaminhamento de mensagens). Basicamente, com a guarda no lugar, sabemos que temos uma função a chamar.3 Na verdade, é possível obter informações incorretas se você declarar objetos
id
e não estiver importando todos os cabeçalhos. Você pode acabar com falhas no código que o compilador acha que está bem. Isso é muito raro, mas pode acontecer. Geralmente, você recebe um aviso de que não sabe qual das duas assinaturas de método deve escolher.4 Consulte a referência ARC sobre valores de retorno acumulados e valores de retorno não acumulados para mais detalhes.
fonte
performSelector:
métodos não são implementados dessa maneira. Eles possuem assinatura de método rigorosa (retornandoid
, tendo um ou doisid
s), portanto, nenhum tipo primitivo precisa ser tratado.Cannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *'
ao usar o Xcode mais recente. (5.1.1) Ainda assim, aprendi muito!void (*func)(id, SEL) = (void *)imp;
não compila, substituí-o com #void (*func)(id, SEL) = (void (*)(id, SEL))imp;
void (*func)(id, SEL) = (void *)imp;
para<…> = (void (*))imp;
ou<…> = (void (*) (id, SEL))imp;
No compilador LLVM 3.0 no Xcode 4.2, você pode suprimir o aviso da seguinte maneira:
Se você estiver recebendo o erro em vários locais e quiser usar o sistema de macro C para ocultar os pragmas, poderá definir uma macro para facilitar a supressão do aviso:
Você pode usar a macro assim:
Se você precisar do resultado da mensagem executada, poderá fazer o seguinte:
fonte
pop
epush
é muito mais limpo e seguro.if ([_target respondsToSelector:_selector]) {
lógica ou similar.Meu palpite sobre isso é o seguinte: como o seletor é desconhecido para o compilador, o ARC não pode aplicar o gerenciamento de memória adequado.
De fato, há momentos em que o gerenciamento de memória está vinculado ao nome do método por uma convenção específica. Especificamente, estou pensando em construtores de conveniência versus métodos make ; o primeiro retorno por convenção de um objeto liberado automaticamente; o último um objeto retido. A convenção é baseada nos nomes do seletor; portanto, se o compilador não conhecer o seletor, não poderá impor a regra de gerenciamento de memória adequada.
Se isso estiver correto, acho que você pode usar seu código com segurança, desde que tudo esteja correto quanto ao gerenciamento de memória (por exemplo, que seus métodos não retornem objetos que eles alocam).
fonte
__attribute
a todos os métodos que explicam seu gerenciamento de memória. Mas também torna impossível para o complier lidar adequadamente com esse padrão (um padrão que costumava ser muito comum, mas foi substituído por padrões mais robustos nos últimos anos).SEL
e atribuir seletores diferentes, dependendo da situação? Caminho a percorrer, linguagem dinâmica ...No seu Build Build Settings , em Other Warning Flags (
WARNING_CFLAGS
), adicione-Wno-arc-performSelector-leaks
Agora, verifique se o seletor que você está chamando não faz com que seu objeto seja retido ou copiado.
fonte
Como uma solução alternativa até que o compilador permita substituir o aviso, você pode usar o tempo de execução
ao invés de
Você terá que
fonte
[_controller performSelector:NSSelectorFromString(@"someMethod")];
eobjc_msgSend(_controller, NSSelectorFromString(@"someMethod"));
não é equivalente! Dê uma olhada nas incompatibilidades de assinatura de método e Uma grande fraqueza na digitação fraca do Objective-C está explicando o problema em profundidade.Para ignorar o erro apenas no arquivo com o seletor de execução, adicione um #pragma da seguinte maneira:
Isso ignoraria o aviso nessa linha, mas ainda o permitiria durante o restante do seu projeto.
fonte
#pragma clang diagnostic warning "-Warc-performSelector-leaks"
. Sei que, se eu desligar um aviso, gosto de ligá-lo o mais rápido possível, para não deixar escapar acidentalmente outro aviso imprevisto. É improvável que isso seja um problema, mas é apenas minha prática sempre que desativo um aviso.#pragma clang diagnostic warning push
antes de fazer alterações e#pragma clang diagnostic warning pop
restaurar o estado anterior. Útil se você estiver desativando cargas e não quiser ter muitas linhas pragma de reativação no seu código.Estranho, mas verdadeiro: se aceitável (ou seja, o resultado é nulo e você não se importa de deixar o ciclo de execução rodar uma vez), adicione um atraso, mesmo que seja zero:
Isso remove o aviso, presumivelmente porque assegura ao compilador que nenhum objeto pode ser retornado e, de alguma forma, mal gerenciado.
fonte
Aqui está uma macro atualizada com base na resposta dada acima. Este deve permitir que você agrupe seu código mesmo com uma declaração de retorno.
fonte
return
não precisa estar dentro da macro;return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]);
também funciona e parece mais saudável.Este código não envolve sinalizadores de compilador ou chamadas diretas de tempo de execução:
NSInvocation
permite que vários argumentos sejam definidos, ao contrário,performSelector
isso funcionará em qualquer método.fonte
Bem, muitas respostas aqui, mas como isso é um pouco diferente, combinando algumas respostas que pensei em colocar. Estou usando uma categoria NSObject que verifica se o seletor retorna nulo e também suprime o compilador Atenção.
fonte
Por uma questão de posteridade, eu decidi jogar meu chapéu no ringue :)
Recentemente, tenho visto cada vez mais reestruturações fora do
target
/selector
paradigma, a favor de coisas como protocolos, blocos, etc. No entanto, há um substituto substitutoperformSelector
que já usei algumas vezes agora:Estes parecem ser um substituto limpo, seguro para ARC e quase idêntico,
performSelector
sem ter que se preocupar muito com issoobjc_msgSend()
.No entanto, não faço ideia se existe um analógico disponível no iOS.
fonte
[[UIApplication sharedApplication] sendAction: to: from: forEvent:]
. Eu olhei para ele uma vez, mas parece meio estranho usar uma classe relacionada à interface do usuário no meio do seu domínio ou serviço apenas para fazer uma chamada dinâmica. Obrigado por incluir isso!id
from-performSelector:...
to:
seja nulo, o que não é. Apenas vai direto ao objeto alvo sem verificação prévia. Portanto, não há "mais sobrecarga". Não é uma ótima solução, mas o motivo que você dá não é o motivo. :)A resposta de Matt Galloway neste tópico explica o porquê:
Parece que geralmente é seguro suprimir o aviso se você estiver ignorando o valor de retorno. Não sei qual é a melhor prática se você realmente precisa obter um objeto retido do performSelector - além de "não faça isso".
fonte
O @ c-road fornece o link correto com a descrição do problema aqui . Abaixo você pode ver meu exemplo, quando performSelector causa um vazamento de memória.
O único método, que causa vazamento de memória no meu exemplo, é CopyDummyWithLeak. O motivo é que o ARC não sabe, que copySelector retorna o objeto retido.
Se você executar a Ferramenta de vazamento de memória, poderá ver a seguinte imagem: ... e não haverá vazamento de memória em nenhum outro caso:
fonte
Para tornar a macro de Scott Thompson mais genérica:
Em seguida, use-o assim:
fonte
Não suprima avisos!
Existem pelo menos 12 soluções alternativas para mexer no compilador.
Enquanto você está sendo esperto no momento da primeira implementação, poucos engenheiros na Terra podem seguir seus passos, e esse código acabará quebrando.
Rotas seguras:
Todas essas soluções funcionarão, com algum grau de variação em relação à sua intenção original. Suponha que
param
pode ser,nil
se você desejar:Rota segura, mesmo comportamento conceitual:
Rota segura, comportamento ligeiramente diferente:
(Veja esta resposta)
Use qualquer thread em vez de
[NSThread mainThread]
.Rotas perigosas
Requer algum tipo de silenciamento do compilador, que está fadado a quebrar. Note-se que no momento presente, ele fez pausa em Swift .
fonte
performSelectorOnMainThread
é uma boa maneira de silenciar o aviso e tem efeitos colaterais. (isso não resolve o vazamento de memória) O extra suprime explicitamente o aviso de uma maneira muito clara.#clang diagnostic ignored
- (void)
método não é o problema real.Como você está usando o ARC, você deve usar o iOS 4.0 ou posterior. Isso significa que você pode usar blocos. Se, em vez de lembrar do seletor para executar, você executasse um bloco, o ARC seria capaz de rastrear melhor o que realmente está acontecendo e você não precisaria correr o risco de introduzir acidentalmente um vazamento de memória.
fonte
self
através de um ivar (por exemplo, emivar
vez deself->ivar
).Em vez de usar a abordagem de bloco, o que me deu alguns problemas:
Vou usar o NSInvocation, assim:
fonte
Se você não precisar passar nenhum argumento, uma solução fácil é usar
valueForKeyPath
. Isso é possível em umClass
objeto.fonte
Você também pode usar um protocolo aqui. Então, crie um protocolo como este:
Na sua turma que precisa chamar seu seletor, você tem uma propriedade @.
Quando você precisar chamar
@selector(doSomethingWithObject:)
uma instância do MyObject, faça o seguinte:fonte