Ouvi pessoas afirmarem que o método swizzling é uma prática perigosa. Até o nome swizzling sugere que é um truque.
O método Swizzling está modificando o mapeamento para que o seletor de chamada A realmente invoque a implementação B. Um uso disso é estender o comportamento das classes de código fechado.
Podemos formalizar os riscos para que qualquer pessoa que esteja decidindo usar swizzling possa tomar uma decisão informada se vale a pena pelo que está tentando fazer.
Por exemplo
- Nomeando colisões : se a classe posteriormente estender sua funcionalidade para incluir o nome do método que você adicionou, causará uma enorme variedade de problemas. Reduza o risco nomeando de forma sensata os métodos swizzled.
ios
objective-c
swizzling
Robert
fonte
fonte
Respostas:
Eu acho que essa é uma pergunta realmente ótima, e é uma pena que, em vez de abordar a questão real, a maioria das respostas tenha contornado a questão e simplesmente dito para não usar swizzling.
Usar o método escaldante é como usar facas afiadas na cozinha. Algumas pessoas têm medo de facas afiadas porque pensam que se cortarão mal, mas a verdade é que facas afiadas são mais seguras .
O método swizzling pode ser usado para escrever um código melhor, mais eficiente e mais sustentável. Também pode ser abusado e levar a erros horríveis.
fundo
Como em todos os padrões de design, se estivermos totalmente cientes das conseqüências do padrão, poderemos tomar decisões mais informadas sobre se deve ou não usá-lo. Singletons são um bom exemplo de algo que é bastante controverso e por boas razões - eles são realmente difíceis de implementar adequadamente. Muitas pessoas ainda optam por usar singletons, no entanto. O mesmo pode ser dito sobre o swizzling. Você deve formar sua própria opinião depois de entender completamente o que é bom e o que é ruim.
Discussão
Aqui estão algumas das armadilhas do método swizzling:
Esses pontos são todos válidos e, ao abordá-los, podemos melhorar nosso entendimento do método swizzling, bem como a metodologia usada para alcançar o resultado. Vou levar cada um de cada vez.
O método swizzling não é atômico
Ainda estou para ver uma implementação do método swizzling que é seguro usar simultaneamente 1 . Na verdade, isso não é um problema em 95% dos casos em que você deseja usar o método swizzling. Geralmente, você simplesmente deseja substituir a implementação de um método e deseja que essa implementação seja usada durante toda a vida útil do seu programa. Isso significa que você deve executar seu método rapidamente
+(void)load
. Oload
método de classe é executado em série no início do seu aplicativo. Você não terá nenhum problema com a simultaneidade se fizer sua swizzling aqui. Se você fizer swizzle+(void)initialize
, no entanto, poderá acabar com uma condição de corrida em sua implementação swizzling, e o tempo de execução poderá terminar em um estado estranho.Altera o comportamento do código não proprietário
Esse é um problema com o swizzling, mas é o tipo de questão. O objetivo é poder alterar esse código. A razão pela qual as pessoas apontam isso como um grande problema é porque você não está apenas mudando as coisas para a única instância da
NSButton
qual deseja mudar as coisas, mas para todas asNSButton
instâncias em seu aplicativo. Por esse motivo, você deve ter cuidado ao swizzle, mas não precisa evitá-lo completamente.Pense desta maneira ... se você substituir um método em uma classe e não chamar o método de superclasse, poderá causar problemas. Na maioria dos casos, a superclasse espera que esse método seja chamado (a menos que esteja documentado em contrário). Se você aplicar esse mesmo pensamento ao swizzling, cobrirá a maioria dos problemas. Sempre chame a implementação original. Caso contrário, você provavelmente está mudando demais para estar seguro.
Possíveis conflitos de nomenclatura
Os conflitos de nomes são um problema em todo o cacau. Geralmente, prefixamos nomes de classes e métodos em categorias. Infelizmente, conflitos de nomes são uma praga em nosso idioma. No caso de swizzling, no entanto, eles não precisam ser. Só precisamos mudar um pouco a maneira como pensamos sobre o método swizzling. A maioria dos swizzling é feita assim:
Isso funciona muito bem, mas o que aconteceria se
my_setFrame:
fosse definido em outro lugar? Esse problema não é exclusivo do swizzling, mas podemos contorná-lo de qualquer maneira. A solução alternativa tem um benefício adicional de abordar outras armadilhas também. Aqui está o que fazemos:Embora pareça um pouco menos com o Objective-C (já que ele usa ponteiros de função), evita qualquer conflito de nomeação. Em princípio, está fazendo exatamente a mesma coisa que o swizzling padrão. Isso pode ser uma mudança para as pessoas que usam o swizzling, já que há um tempo já foi definido, mas no final, acho que é melhor. O método swizzling é definido assim:
Swizzling renomeando métodos altera os argumentos do método
Este é o grande problema em minha mente. Esta é a razão pela qual o método padrão swizzling não deve ser feito. Você está alterando os argumentos passados para a implementação do método original. É aqui que acontece:
O que esta linha faz é:
O qual usará o tempo de execução para procurar a implementação do
my_setFrame:
. Depois que a implementação é encontrada, ela invoca a implementação com os mesmos argumentos que foram fornecidos. A implementação encontrada é a implementação originalsetFrame:
, portanto prossegue e chama assim, mas o_cmd
argumento não é osetFrame:
que deveria ser. É agoramy_setFrame:
. A implementação original está sendo chamada com um argumento que nunca esperava que fosse recebido. Isso não é bom.Existe uma solução simples - use a técnica alternativa de swizzling definida acima. Os argumentos permanecerão inalterados!
A ordem dos swizzles é importante
A ordem na qual os métodos são misturados é importante. Supondo que
setFrame:
seja definido apenasNSView
, imagine esta ordem das coisas:O que acontece quando o método on
NSButton
é misturado? Bem mais swizzling irá garantir que ele não está substituindo a implementação desetFrame:
para todos os pontos de vista, por isso vai puxar para cima o método de instância. Isso usará a implementação existente para redefinirsetFrame:
naNSButton
classe, para que a troca de implementações não afete todas as visualizações. A implementação existente é a definida emNSView
. A mesma coisa acontecerá quando o swizzlingNSControl
(novamente usando aNSView
implementação).Quando você
setFrame:
clica em um botão, ele chama o método swizzled e, em seguida, pula diretamente para osetFrame:
método originalmente definido emNSView
. As implementações swizzledNSControl
eNSView
não serão chamadas.Mas e se a ordem fosse:
Desde o swizzling vista que ocorrer primeiro, o swizzling controle será capaz de puxar para cima o método certo. Da mesma forma, uma vez que o swizzling controle foi antes de o botão swizzling, o botão vai puxar para cima implementação swizzled do controle de
setFrame:
. Isso é um pouco confuso, mas esta é a ordem correta. Como podemos garantir essa ordem das coisas?Mais uma vez, basta usar
load
para mexer as coisas. Se você entrarload
e fizer apenas alterações na classe que está sendo carregada, estará seguro. Oload
método garante que o método de carregamento de superclasse será chamado antes de qualquer subclasse. Obteremos a ordem exata exata!Difícil de entender (parece recursivo)
Olhando para um método swizzled tradicionalmente definido, acho realmente difícil dizer o que está acontecendo. Mas, olhando para a maneira alternativa como fizemos swizzling acima, é muito fácil de entender. Este já foi resolvido!
Difícil de depurar
Uma das confusões durante a depuração é ver um histórico estranho, onde os nomes misturados são misturados e tudo fica confuso na sua cabeça. Novamente, a implementação alternativa trata disso. Você verá funções claramente nomeadas em backtraces. Ainda assim, pode ser difícil depurar o swizzling, porque é difícil lembrar qual o impacto do swizzling. Documente bem seu código (mesmo que você ache que é o único que o verá). Siga as boas práticas e você ficará bem. Não é mais difícil depurar do que o código multiencadeado.
Conclusão
O método swizzling é seguro se usado corretamente. Uma medida simples de segurança que você pode adotar é apenas entrar na água
load
. Como muitas coisas na programação, isso pode ser perigoso, mas entender as consequências permitirá que você a use corretamente.1 Usando o método swizzling definido acima, você pode tornar as coisas mais seguras se usar trampolins. Você precisaria de dois trampolins. No início do método, você teria que atribuir o ponteiro da função
store
, a uma função que girava até o endereço para o qual ostore
apontado foi alterado. Isso evitaria qualquer condição de corrida na qual o método swizzled fosse chamado antes que você pudesse definir ostore
ponteiro da função. Você precisaria usar um trampolim no caso em que a implementação ainda não esteja definida na classe e ter a pesquisa de trampolim e chamar o método de superclasse corretamente. Definir o método para procurar dinamicamente a super implementação garantirá que a ordem das chamadas swizzling não seja importante.fonte
Primeiro, definirei exatamente o que quero dizer com método swizzling:
O método swizzling é mais geral do que isso, mas é esse o caso em que estou interessado.
Perigos:
Mudanças na classe original . Nós não possuímos a classe que estamos nadando. Se a classe mudar, nosso swizzle pode parar de funcionar.
Difícil de manter . Você não precisa apenas escrever e manter o método swizzled. você deve escrever e manter o código que pré-forma o swizzle
Difícil de depurar . É difícil acompanhar o fluxo de um swizzle, algumas pessoas podem nem perceber que o swizzle foi pré-formado. Se houver erros introduzidos no swizzle (talvez deva-se a alterações na classe original), eles serão difíceis de resolver.
Em resumo, você deve manter o mínimo de swizzling e considerar como as alterações na classe original podem afetar seu swizzle. Além disso, você deve comentar e documentar claramente o que está fazendo (ou apenas evitá-lo totalmente).
fonte
Não é o próprio swizzling que é realmente perigoso. O problema é que, como você diz, é freqüentemente usado para modificar o comportamento das classes de estrutura. Supõe-se que você saiba algo sobre o funcionamento dessas classes particulares que é "perigoso". Mesmo que suas modificações funcionem hoje, sempre há uma chance de que a Apple mude de classe no futuro e faça com que suas modificações sejam interrompidas. Além disso, se muitos aplicativos diferentes fazem isso, torna muito mais difícil para a Apple alterar a estrutura sem interromper muito software existente.
fonte
Usado com cuidado e sabedoria, pode levar a um código elegante, mas geralmente leva a um código confuso.
Eu digo que deve ser banido, a menos que você saiba que ela apresenta uma oportunidade muito elegante para uma tarefa de design específica, mas você precisa saber claramente por que ela se aplica bem à situação e por que alternativas não funcionam elegantemente para a situação .
Por exemplo, uma boa aplicação do método swizzling é um swizzling, que é como a ObjC implementa a Observação de Valor Chave.
Um mau exemplo pode ser confiar no método swizzling como um meio de estender suas classes, o que leva a um acoplamento extremamente alto.
fonte
Embora eu tenha usado essa técnica, gostaria de salientar que:
fonte
Sinto que o maior perigo está em criar muitos efeitos colaterais indesejados, completamente por acidente. Esses efeitos colaterais podem se apresentar como 'bugs' que, por sua vez, levam você ao caminho errado para encontrar a solução. Na minha experiência, o perigo é código ilegível, confuso e frustrante. É como quando alguém usa demais os ponteiros de função em C ++.
fonte
Você pode acabar com um código com aparência estranha, como
do código de produção real relacionado a alguma mágica da interface do usuário.
fonte
O método swizzling pode ser muito útil nos testes de unidade.
Ele permite que você escreva um objeto simulado e tenha esse objeto simulado em vez do objeto real. Seu código permanece limpo e seu teste de unidade tem um comportamento previsível. Digamos que você queira testar algum código que usa CLLocationManager. Seu teste de unidade pode fazer o swizzle startUpdatingLocation para alimentar um conjunto predeterminado de locais para o seu delegado e seu código não precisa ser alterado.
fonte