runtime swift swizzling

Función Swift swizzling/runtime



(7)

Antes de Swift, en Objective-C swizzle o ganaba métodos en una clase usando <objc/runtime.h> .

Si alguien tiene alguna información sobre el tema de la modificación de las funciones de ejecución y enganche de Swift, como CydiaSubstrate y otras bibliotecas que ayudaron en esta área, infórmeme.


Después de dedicarle algo de tiempo ... Despiértate esta mañana ... la versión beta 6 está fuera y el problema se solucionó en la versión beta6. De las notas de la versión "El envío dinámico ahora puede llamar anulaciones de métodos y propiedades introducidas en extensiones de clase, ¡arreglando una regresión introducida en Xcode 6 beta 5. (17985819)!"


Es probable que pueda cambiar las clases generadas por swift que heredan de las clases de Objective-C sin ningún problema, ya que parecen utilizar el envío dinámico de métodos todo el tiempo. Es posible que pueda cambiar los métodos de las clases definidas con rapidez que existen en el tiempo de ejecución de Objective-C en virtud de que se pasan a través del puente, pero es probable que los métodos del lado de Objective-C solo sean proxies de vuelta a través del puente a la velocidad rápida. tiempo de ejecución lateral, por lo que no está claro que sería particularmente útil hacerlos a un lado.

Las llamadas al método swift "puro" no parecen ser enviadas dinámicamente a través de objc_msgSend y parece (a partir de una breve experimentación) que el tipo de seguridad de swift se implementa en tiempo de compilación, y que gran parte de la información de tipo real está ausente (es decir, desaparecida). ) en tiempo de ejecución para los tipos que no son de clase (ambos de los cuales probablemente contribuyan a las supuestas ventajas de velocidad de Swift).

Por estas razones, espero que los métodos swiftling de swizzling significativos sean significativamente más difíciles que los métodos de Objective-C swizzling, y probablemente se parecerán mucho más a mach_override que a los mach_override de Objective-C.


Estoy respondiendo esta pregunta más de un año después porque ninguna de las otras respuestas proporciona el conjunto definitivo de requisitos para el método swizzling para cada clase de clase.

Lo que se describe por otros, mientras que funcionará perfectamente para las extensiones de las clases de Foundation / uikit (como NSDictionary), simplemente nunca funcionará para sus propias clases de Swift.

Como se describe here , hay un requisito adicional para el método swizzling que no sea extender NSObject en su clase personalizada.

El método rápido que quieras cambiar debe estar marcado como dynamic .

Si no lo marca, el tiempo de ejecución simplemente continuará invocando el método original en lugar del original, incluso si los punteros del método parecen haber sido intercambiados correctamente.

Actualizar:

He ampliado esta respuesta en una entrada de blog .


He tenido éxito con el método swizzling en Swift. Este ejemplo muestra cómo enlazar el método de descripción en NSDictionary

Mi implementación:

extension NSDictionary { func myDescription() -> String!{ println("Description hooked") return "Hooooked " + myDescription(); } }

Código swizzling:

func swizzleEmAll() { var dict:NSDictionary = ["SuperSecret": kSecValueRef] var method: Method = class_getInstanceMethod(object_getClass(dict), Selector.convertFromStringLiteral("description")) println(dict.description) // Check original description var swizzledMethod: Method = class_getInstanceMethod(object_getClass(dict), Selector.convertFromStringLiteral("myDescription")) method_exchangeImplementations(method, swizzledMethod) println(dict.description) //Check that swizzling works }

Editado: este código funcionará para cualquier clase Swift personalizada que herede de NSObject (pero no funcionará para las clases que no lo hacen). Más ejemplos: https://github.com/mbazaliy/MBSwizzler


Me gustaría extender la gran respuesta proporcionada por mbazaliy .

Otra forma de hacer swizzling en Swift es proporcionar una implementación utilizando un bloque Objective-C.

Por ejemplo, para reemplazar el método de description en la clase NSString podemos escribir:

let originalMethod = class_getInstanceMethod(NSString.self, "description") let impBlock : @objc_block () -> NSString = { () in return "Bit of a hack job!" } let newMethodImp = imp_implementationWithBlock(unsafeBitCast(impBlock, AnyObject.self)) method_setImplementation(originalMethod, newMethodImp)

Esto funciona a partir de Swift 1.1.



Tuve un proyecto de Xcode 7 para iOS escrito en Swift 2, usando Cocoapods. En un Cocoapod específico, con fuente de Objective-C, quise anular un método corto, sin forzar el pod. Escribir una extensión Swift no funcionaría en mi caso.

Para utilizar el método swizzling, creé una nueva clase de Objective-C en mi paquete principal con el método que quería reemplazar / inyectar en el cocoapod. (También agregó el encabezado puente)

Usando la solución de mbazaliy en stackflow , puse mi código similar a este en didFinishLaunchingWithOptions en mi Appdelegate:

let mySelector: Selector = "nameOfMethodToReplace" let method: Method = class_getInstanceMethod(SomeClassInAPod.self, mySelector) let swizzledMethod: Method = class_getInstanceMethod(SomeOtherClass.self, mySelector) method_exchangeImplementations(method, swizzledMethod)

Esto funcionó perfectamente. La diferencia entre el código de @mbazaliy es que no tuve que crear una instancia de la clase SomeClassInAPod primero, lo que en mi caso hubiera sido imposible.

Nota: Puse el código en la Appdelegate porque cada vez que se ejecuta el código, intercambia el método por el original; solo se debe ejecutar una vez.

También necesitaba copiar algunos activos a los que se hacía referencia en el paquete del Pod al paquete principal.