He escuchado a personas decir que el método swizzling es una práctica peligrosa. Incluso el nombre swizzling sugiere que es un poco tramposo.
El método Swizzling está modificando la asignación para que el selector de llamadas A invoque la implementación B. Un uso de esto es extender el comportamiento de las clases de código cerrado.
¿Podemos formalizar los riesgos para que cualquiera que decida si usar swizzling pueda tomar una decisión informada sobre si vale la pena por lo que está tratando de hacer?
P.ej
- Nombrar colisiones : si la clase luego amplía su funcionalidad para incluir el nombre del método que ha agregado, causará una gran cantidad de problemas. Reduzca el riesgo al nombrar con sensatez métodos swizzled
ios
objective-c
swizzling
Robert
fuente
fuente
Respuestas:
Creo que esta es una gran pregunta, y es una pena que, en lugar de abordar la pregunta real, la mayoría de las respuestas hayan eludido el problema y simplemente hayan dicho que no utilicen swizzling.
Usar el método chisporrotear es como usar cuchillos afilados en la cocina. Algunas personas tienen miedo de los cuchillos afilados porque piensan que se cortarán mucho, pero la verdad es que los cuchillos afilados son más seguros .
El método swizzling se puede utilizar para escribir un código mejor, más eficiente y más fácil de mantener. También puede ser abusado y provocar errores horribles.
Antecedentes
Al igual que con todos los patrones de diseño, si somos plenamente conscientes de las consecuencias del patrón, podemos tomar decisiones más informadas sobre si usarlo o no. Los singletons son un buen ejemplo de algo que es bastante controvertido y, por una buena razón, son realmente difíciles de implementar correctamente. Sin embargo, muchas personas aún optan por usar singletons. Lo mismo se puede decir sobre el swizzling. Debe formarse su propia opinión una vez que comprenda completamente tanto lo bueno como lo malo.
Discusión
Estas son algunas de las dificultades del método swizzling:
Todos estos puntos son válidos, y al abordarlos podemos mejorar tanto nuestra comprensión del método swizzling como la metodología utilizada para lograr el resultado. Tomaré cada uno a la vez.
Método swizzling no es atómico
Todavía tengo que ver una implementación del método swizzling que sea seguro de usar simultáneamente 1 . En realidad, esto no es un problema en el 95% de los casos en los que desea utilizar el método swizzling. Por lo general, simplemente desea reemplazar la implementación de un método y desea que esa implementación se use durante toda la vida útil de su programa. Esto significa que debes hacer que tu método se mezcle
+(void)load
. Elload
método de clase se ejecuta en serie al inicio de su aplicación. No tendrás problemas con la concurrencia si haces swizzling aquí. Si se va a swizzle en+(void)initialize
, sin embargo, usted podría terminar con una condición de carrera en su aplicación y el tiempo de ejecución swizzling podría terminar en un estado extraño.Cambia el comportamiento del código no propiedad
Este es un problema con swizzling, pero es una especie de punto. El objetivo es poder cambiar ese código. La razón por la que las personas señalan que esto es un gran problema es porque no solo está cambiando las cosas para la única instancia para la
NSButton
que desea cambiar las cosas, sino para todas lasNSButton
instancias de su aplicación. Por esta razón, debe ser cauteloso cuando se bañe, pero no necesita evitarlo por completo.Piénselo de esta manera ... si anula un método en una clase y no llama al método de superclase, puede causar problemas. En la mayoría de los casos, la superclase espera que se llame a ese método (a menos que se documente lo contrario). Si aplica este mismo pensamiento al swizzling, ha cubierto la mayoría de los problemas. Siempre llame a la implementación original. Si no lo hace, probablemente esté cambiando demasiado para estar seguro.
Posibles conflictos de nombres
Los conflictos de nombres son un problema en todo Cocoa. Frecuentemente prefijamos nombres de clases y nombres de métodos en categorías. Desafortunadamente, los conflictos de nombres son una plaga en nuestro idioma. Sin embargo, en el caso de swizzling, no tienen que serlo. Solo necesitamos cambiar ligeramente la forma en que pensamos sobre el método. La mayor cantidad de swizzling se hace así:
Esto funciona bien, pero ¿qué pasaría si
my_setFrame:
se definiera en otro lugar? Este problema no es exclusivo de swizzling, pero podemos solucionarlo de todos modos. La solución alternativa tiene un beneficio adicional de abordar también otras dificultades. Esto es lo que hacemos en su lugar:Si bien esto se parece un poco menos a Objective-C (ya que utiliza punteros de función), evita cualquier conflicto de nombres. En principio, está haciendo exactamente lo mismo que el swizzling estándar. Esto puede ser un poco un cambio para las personas que han estado usando swizzling como se ha definido durante un tiempo, pero al final, creo que es mejor. El método de swizzling se define así:
El cambio de nombre al cambiar de método cambia los argumentos del método
Este es el más grande en mi mente. Esta es la razón por la que no se debe hacer un método estándar. Está cambiando los argumentos pasados a la implementación del método original. Aquí es donde sucede:
Lo que hace esta línea es:
Que utilizará el tiempo de ejecución para buscar la implementación de
my_setFrame:
. Una vez que se encuentra la implementación, invoca la implementación con los mismos argumentos que se dieron. La implementación que encuentra es la implementación originalsetFrame:
, por lo que continúa y llama a eso, pero el_cmd
argumento no essetFrame:
como debería ser. Es ahoramy_setFrame:
. Se llama a la implementación original con un argumento que nunca esperó recibir. Esto no está bien.Hay una solución simple: use la técnica alternativa de swizzling definida anteriormente. ¡Los argumentos permanecerán sin cambios!
El orden de las olas es importante
El orden en que los métodos se mezclan importa. Suponiendo
setFrame:
que solo se define enNSView
, imagine este orden de cosas:¿Qué sucede cuando el método
NSButton
está activado? Bueno, la mayor cantidad de swizzling asegurará que no esté reemplazando la implementación desetFrame:
todas las vistas, por lo que extraerá el método de instancia. Esto usará la implementación existente para redefinirsetFrame:
en laNSButton
clase para que el intercambio de implementaciones no afecte a todas las vistas. La implementación existente es la definida enNSView
. Lo mismo sucederá cuando se enciendaNSControl
(nuevamente usando laNSView
implementación).Cuando llama
setFrame:
a un botón, llamará a su método swizzled y luego saltará directamente alsetFrame:
método originalmente definidoNSView
. No se llamarán las implementacionesNSControl
yNSView
swizzled.Pero, ¿y si el orden fuera:
Desde el punto de vista swizzling se lleva a cabo en primer lugar, la swizzling control será capaz de tirar hacia arriba el método correcto. Del mismo modo, dado que el control de swizzling fue antes del botón de swizzling, el botón levantará la implementación de control de swizzled
setFrame:
. Esto es un poco confuso, pero este es el orden correcto. ¿Cómo podemos asegurar este orden de cosas?Nuevamente, solo usa
load
para mezclar cosas. Si te sumergesload
y solo haces cambios en la clase que se está cargando, estarás a salvo. Elload
método garantiza que se llamará al método de carga de superclase antes de cualquier subclase. ¡Obtendremos el orden exacto correcto!Difícil de entender (se ve recursivo)
Mirando un método swizzled definido tradicionalmente, creo que es realmente difícil saber qué está pasando. Pero mirando la forma alternativa en que hemos hecho swizzling arriba, es bastante fácil de entender. Este ya ha sido resuelto!
Difícil de depurar
Una de las confusiones durante la depuración es ver un extraño retroceso donde los nombres mezclados se mezclan y todo se confunde en tu cabeza. Nuevamente, la implementación alternativa aborda esto. Verá funciones claramente nombradas en las trazas inversas. Aún así, el swizzling puede ser difícil de depurar porque es difícil recordar qué impacto está teniendo el swizzling. Documente bien su código (incluso si cree que es el único que lo verá). Sigue las buenas prácticas y estarás bien. No es más difícil de depurar que el código multiproceso.
Conclusión
El método swizzling es seguro si se usa correctamente. Una medida de seguridad simple que puede tomar es solo entrar
load
. Al igual que muchas cosas en la programación, puede ser peligroso, pero comprender las consecuencias le permitirá usarlo correctamente.1 Usando el método swizzling definido anteriormente, puede hacer que las cosas sean seguras si usa trampolines. Necesitarías dos trampolines. Al comienzo del método, tendría que asignar el puntero de función
store
, a una función que girara hasta que la dirección a la questore
apuntaba cambiara. Esto evitaría cualquier condición de carrera en la que se llamara al método swizzled antes de que pudiera establecer elstore
puntero de función. Debería usar un trampolín en el caso de que la implementación no esté ya definida en la clase y tener la búsqueda en el trampolín y llamar al método de superclase correctamente. La definición del método para que busque dinámicamente la súper implementación garantizará que el orden de las llamadas no tenga importancia.fuente
Primero definiré exactamente lo que quiero decir con método swizzling:
El método swizzling es más general que esto, pero este es el caso que me interesa.
Peligros
Cambios en la clase original . No somos dueños de la clase que estamos intercambiando. Si la clase cambia, nuestro swizzle puede dejar de funcionar.
Difícil de mantener . No solo tienes que escribir y mantener el método swizzled. tienes que escribir y mantener el código que preforma el swizzle
Difícil de depurar . Es difícil seguir el flujo de una llovizna, es posible que algunas personas ni siquiera se den cuenta de que la tormenta se ha formado previamente. Si hay errores introducidos desde la tormenta (quizás debido a cambios en la clase original) serán difíciles de resolver.
En resumen, debe mantener el swizzling al mínimo y considerar cómo los cambios en la clase original podrían afectar su swizzle. También debe comentar y documentar claramente lo que está haciendo (o simplemente evitarlo por completo).
fuente
No es el swizzling en sí lo que es realmente peligroso. El problema es, como usted dice, que a menudo se usa para modificar el comportamiento de las clases de marco. Supone que sabes algo sobre cómo funcionan esas clases privadas que es "peligroso". Incluso si sus modificaciones funcionan hoy, siempre existe la posibilidad de que Apple cambie la clase en el futuro y haga que su modificación se rompa. Además, si muchas aplicaciones diferentes lo hacen, es mucho más difícil para Apple cambiar el marco sin romper una gran cantidad de software existente.
fuente
Utilizado con cuidado y prudencia, puede conducir a un código elegante, pero por lo general, solo conduce a un código confuso.
Digo que debe prohibirse, a menos que sepa que presenta una oportunidad muy elegante para una tarea de diseño en particular, pero necesita saber claramente por qué se aplica bien a la situación y por qué las alternativas no funcionan de manera elegante para la situación. .
Por ejemplo, una buena aplicación del método swizzling es isa swizzling, que es cómo ObjC implementa la Observación de valor clave.
Un mal ejemplo podría ser confiar en el método swizzling como un medio para extender sus clases, lo que conduce a un acoplamiento extremadamente alto.
fuente
Aunque he usado esta técnica, me gustaría señalar que:
fuente
Siento que el mayor peligro es crear muchos efectos secundarios no deseados, completamente por accidente. Estos efectos secundarios pueden presentarse como 'errores' que a su vez lo llevan por el camino equivocado para encontrar la solución. En mi experiencia, el peligro es un código ilegible, confuso y frustrante. Algo así como cuando alguien abusa de los punteros de función en C ++.
fuente
Puede terminar con un código de aspecto extraño como
del código de producción real relacionado con alguna magia de UI.
fuente
El método swizzling puede ser muy útil en las pruebas unitarias.
Le permite escribir un objeto simulado y utilizar ese objeto simulado en lugar del objeto real. Su código para permanecer limpio y su prueba de unidad tiene un comportamiento predecible. Digamos que desea probar algún código que usa CLLocationManager. Su prueba unitaria podría confundir startUpdatingLocation para que alimente un conjunto predeterminado de ubicaciones a su delegado y su código no tenga que cambiar.
fuente