Selector no reconocido de iOS-Runtime enviado a protección de instancia/clase Bloqueo

Puede descargar la demostración del código fuente de muestra en GitHub  , bienvenido a dar me gusta y destacar, ¡gracias!

Este artículo también puede referirse al artículo Mecanismo de envío y reenvío de mensajes iOS-Runtime

1. Informe de errores

A menudo nos encontramos con este tipo de bloqueos en el desarrollo de iOS

unrecognized selector sent to instance 0x******

2. El motivo del error

El motivo del error es que llamamos a un método que no existe.

En términos del mecanismo de mensajes de OC: el receptor del mensaje no puede encontrar el selector correspondiente, por lo que se inicia el mecanismo de reenvío de mensajes.Podemos decirle al objeto cómo manejar el mensaje desconocido a través del código durante el proceso de reenvío de mensajes para evitar que el programa estrellarse. .

La implementación predeterminada es lanzar la siguiente excepción, que fallará

3. Soluciones 

Method SwizzlingIntercepción en tiempo de ejecución

1. Mecanismo de mensajes en tiempo de ejecución

Objective-CEn los lenguajes, las llamadas a métodos tienen una [receiver selector];forma similar y su esencia es el proceso de permitir que un objeto envíe un mensaje en tiempo de ejecución.
Veamos cómo funciona la llamada al método [receiver selector];en 『编译阶段』y『运行阶段』

1. Fase de compilación : [selector de receptor], el compilador convierte el método en:
 (1) objc_msgSend(receptor, selector) (sin parámetros)
 (2) objc_msgSend(receptor, selector, org1, org2,…) (con parámetros )

Ejemplo de código OC:

NSObject *objc = [[NSObject alloc]init];

clangVer el código compilado del código OC  por comando:

NSObject *objc = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));

2. Etapa de ejecución: El receptor del mensaje busca el selector correspondiente.
(1) Encuentre la Clase (class) del receptor a través del puntero isa del receptor;
(2) Encuentre el IMP correspondiente (implementación del método) en la tabla hash del caché (caché del método) de la Clase (clase); ( 3)
Si en el caché (Si el IMP correspondiente (implementación del método) no se encuentra en el caché del método), continúe buscando el selector correspondiente en la lista de métodos (lista de métodos) de la Clase (clase), si lo encuentra, llénelo en el caché (caché de método) y devolver el selector;
(4) Si el selector no se encuentra en la Clase (clase), continúe buscando en su superClase (clase principal), y así sucesivamente, hasta que se encuentre la clase raíz; (5) Una vez encontrado el selector correspondiente, ejecutarlo
directamente. El receptor corresponde al IMP (implementación del método) implementado por el método selector.
(6) Si no se puede encontrar el selector correspondiente, cambie a la llamada de intercepción y reenvíe el mensaje; si no hay un método para reescribir la llamada de intercepción, el programa fallará.

2. Principio de protección

   Utilizando Objective-Clas características dinámicas del lenguaje, adoptando AOP(Aspect Oriented Programming)la idea de diseño de la programación orientada a aspectos, sin invadir el código del proyecto original, al interceptar y procesar los factores de bloqueo durante la etapa de tiempo de ejecución de la aplicación, la aplicación puede continuar funcionando de manera estable y normal.
   Concretamente hablando, es agregar clases que requieren Hooks Category(分类), interceptar métodos del sistema que probablemente provoquen bloqueos en cada categoría e intercambiar los métodos originales del sistema con Method Swizzlinglos métodos de protección agregados . Luego, agregue acciones de protección en el método de reemplazo para evitar y corregir bloqueos.selector(方法选择器)IMP(函数实现指针)

3. Proceso de reenvío y redirección de mensajes

1. Análisis dinámico de mensajes : el tiempo de ejecución de Objective-C llamará a +resolveInstanceMethod: o +resolveClassMethod:, lo que le brinda la oportunidad de proporcionar una implementación de función. Podemos reescribir estos dos métodos, agregar otras funciones para implementar y devolver SÍ, luego el sistema de tiempo de ejecución reiniciará el proceso de envío de un mensaje. Si devuelve NO o no se agrega ninguna otra implementación de función, vaya al siguiente paso.
2. Redirección del receptor del mensaje : si el objeto actual implementa forwardingTargetForSelector:, Runtime llamará a este método, permitiéndonos reenviar el receptor del mensaje a otros objetos. Si este método de paso devuelve cero, vaya al siguiente paso.
3. Redirección de mensajes : el sistema Runtime utiliza el método methodSignatureForSelector: para obtener los parámetros y los tipos de valores de retorno de la función.
Si methodSignatureForSelector: devuelve un objeto NSMethodSignature (firma de función), el sistema Runtime creará un objeto NSInvocation y notificará al objeto actual a través del mensaje forwardInvocation: dando a este mensaje la última oportunidad de encontrar el IMP.
Si methodSignatureForSelector: devuelve nil. Luego, el sistema Runtime emitirá un mensaje doesNotRecognizeSelector: y el programa fallará.

El diagrama de flujo es el siguiente:

De los tres pasos anteriores, qué paso es el más adecuado para comenzar, por lo que debemos considerar las cosas
1. resolveInstanceMethod necesita agregar dinámicamente métodos que no existen en la clase en sí, y estos métodos son redundantes para la clase en sí 2 ForwardingTargetForSelector
puede reenviar el mensaje a un objeto, la sobrecarga es pequeña y la probabilidad de que se reescriba es baja, por lo que es adecuado para reescribir.

3. ForwardInvocation puede reenviar mensajes a múltiples objetos en forma de NSInvocación, pero su sobrecarga es relativamente grande, y es necesario crear nuevos objetos NSInvocación, y los usuarios a menudo llaman a la función de reenviarInvocación para reenviar mensajes, lo cual no es adecuado para varias veces reescribir.

Elegimos el segundo paso (redireccionamiento del receptor de mensajes) para interceptar. Debido a que  -forwardingTargetForSelector el método puede reenviar el mensaje a un objeto, la sobrecarga es pequeña y la probabilidad de que se reescriba es baja, por lo que es adecuado para la reescritura.

Los pasos específicos son los siguientes:

1. Agregue una categoría a NSObject e implemente un método personalizado -zm_forwardingTargetForSelector: en la categoría
2. Use Method Swizzling para intercambiar métodos entre -forwardingTargetForSelector: y -zm_forwardingTargetForSelector:.
3. En el método personalizado, primero determine si el objeto actual ha implementado la redirección del receptor de mensajes y la redirección de mensajes. Si ninguno de ellos está implementado, cree dinámicamente una clase de destino y agregue dinámicamente un método a la clase de destino.
4. Reenvíe el mensaje al objeto de instancia de la clase generada dinámicamente, que se realiza mediante el método creado dinámicamente por la clase de destino, para que la aplicación no se bloquee.

Encapsulación del Método Método Swizzling

//--------NSObject+MethodSwizzling.h代码
@interface NSObject (MethodSwizzling)

// 判断是否是系统类
static inline BOOL isSystemClass(Class cls) {
    BOOL isSystem = NO;
    NSString *className = NSStringFromClass(cls);
    if ([className hasPrefix:@"NS"] || [className hasPrefix:@"__NS"] || [className hasPrefix:@"OS_xpc"]) {
        isSystem = YES;
        return isSystem;
    }
    NSBundle *mainBundle = [NSBundle bundleForClass:cls];
    if (mainBundle == [NSBundle mainBundle]) {
        isSystem = NO;
    }else{
        isSystem = YES;
    }
    return isSystem;
}

/** 交换两个类方法的实现
 * @param originalSelector  原始方法的 SEL
 * @param swizzledSelector  交换方法的 SEL
 * @param targetClass  类
 */
+ (void)defenderSwizzlingClassMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass;

/** 交换两个对象方法的实现
 * @param originalSelector  原始方法的 SEL
 * @param swizzledSelector 交换方法的 SEL
 * @param targetClass  类
 */
+ (void)defenderSwizzlingInstanceMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass;

@end




//--------NSObject+MethodSwizzling.代码
#import "NSObject+MethodSwizzling.h"

#import <objc/runtime.h>

@implementation NSObject (MethodSwizzling)

+ (void)defenderSwizzlingClassMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass {
    swizzlingClassMethod(targetClass, originalSelector, swizzledSelector);
}

+ (void)defenderSwizzlingInstanceMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass {
    swizzlingInstanceMethod(targetClass, originalSelector, swizzledSelector);
}

// 交换两个类方法的实现
void swizzlingClassMethod(Class class, SEL originalSelector, SEL swizzledSelector) {

    Method originalMethod = class_getClassMethod(class, originalSelector);
    Method swizzledMethod = class_getClassMethod(class, swizzledSelector);

    BOOL didAddMethod = class_addMethod(class,
                                        originalSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));

    if (didAddMethod) {
        class_replaceMethod(class,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

// 交换两个对象方法的实现
void swizzlingInstanceMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

    BOOL didAddMethod = class_addMethod(class,
                                        originalSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));

    if (didAddMethod) {
        class_replaceMethod(class,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

@end

El método de clasificación NSObject+SelectorForwarding realiza el intercambio de métodos

#import "NSObject+SelectorForwarding.h"

#import "NSObject+MethodSwizzling.h"
#import <objc/runtime.h>

@implementation NSObject (SelectorForwarding)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        // 拦截 `+forwardingTargetForSelector:` 方法,替换自定义实现
        [NSObject defenderSwizzlingClassMethod:@selector(forwardingTargetForSelector:)
                                       withMethod:@selector(zm_forwardingTargetForSelector:)
                                        withClass:[NSObject class]];
        
        // 拦截 `-forwardingTargetForSelector:` 方法,替换自定义实现
        [NSObject defenderSwizzlingInstanceMethod:@selector(forwardingTargetForSelector:)
                                          withMethod:@selector(zm_forwardingTargetForSelector:)
                                           withClass:[NSObject class]];
        
    });
}

// 自定义实现 `+zm_forwardingTargetForSelector:` 方法
+ (id)zm_forwardingTargetForSelector:(SEL)aSelector {
    SEL forwarding_sel = @selector(forwardingTargetForSelector:);
    
    // 获取 NSObject 的消息转发方法
    Method root_forwarding_method = class_getClassMethod([NSObject class], forwarding_sel);
    // 获取 当前类 的消息转发方法
    Method current_forwarding_method = class_getClassMethod([self class], forwarding_sel);
    
    // 判断当前类本身是否实现第二步:消息接受者重定向
    BOOL realize = method_getImplementation(current_forwarding_method) != method_getImplementation(root_forwarding_method);
    
    // 如果没有实现第二步:消息接受者重定向
    if (!realize) {
        // 判断有没有实现第三步:消息重定向
        SEL methodSignature_sel = @selector(methodSignatureForSelector:);
        Method root_methodSignature_method = class_getClassMethod([NSObject class], methodSignature_sel);
        
        Method current_methodSignature_method = class_getClassMethod([self class], methodSignature_sel);
        realize = method_getImplementation(current_methodSignature_method) != method_getImplementation(root_methodSignature_method);
        
        // 如果没有实现第三步:消息重定向
        if (!realize) {
            // 创建一个新类
            NSString *errClassName = NSStringFromClass([self class]);
            NSString *errSel = NSStringFromSelector(aSelector);
        
            NSLog(@"*** Crash Message: +[%@ %@]: unrecognized selector sent to class %p ***",errClassName, errSel, self);
            
            
            NSString *className = @"CrachClass";
            Class cls = NSClassFromString(className);
            
            // 如果类不存在 动态创建一个类
            if (!cls) {
                Class superClsss = [NSObject class];
                cls = objc_allocateClassPair(superClsss, className.UTF8String, 0);
                // 注册类
                objc_registerClassPair(cls);
            }
            // 如果类没有对应的方法,则动态添加一个
            if (!class_getInstanceMethod(NSClassFromString(className), aSelector)) {
                class_addMethod(cls, aSelector, (IMP)Crash, "@@:@");
            }
            // 把消息转发到当前动态生成类的实例对象上
            return [[cls alloc] init];
        }
    }
    return [self zm_forwardingTargetForSelector:aSelector];
}

// 自定义实现 `-zm_forwardingTargetForSelector:` 方法
- (id)zm_forwardingTargetForSelector:(SEL)aSelector {
    
    SEL forwarding_sel = @selector(forwardingTargetForSelector:);
    
    // 获取 NSObject 的消息转发方法
    Method root_forwarding_method = class_getInstanceMethod([NSObject class], forwarding_sel);
    // 获取 当前类 的消息转发方法
    Method current_forwarding_method = class_getInstanceMethod([self class], forwarding_sel);
    
    // 判断当前类本身是否实现第二步:消息接受者重定向
    BOOL realize = method_getImplementation(current_forwarding_method) != method_getImplementation(root_forwarding_method);
    
    // 如果没有实现第二步:消息接受者重定向
    if (!realize) {
        // 判断有没有实现第三步:消息重定向
        SEL methodSignature_sel = @selector(methodSignatureForSelector:);
        Method root_methodSignature_method = class_getInstanceMethod([NSObject class], methodSignature_sel);
        
        Method current_methodSignature_method = class_getInstanceMethod([self class], methodSignature_sel);
        realize = method_getImplementation(current_methodSignature_method) != method_getImplementation(root_methodSignature_method);
        
        // 如果没有实现第三步:消息重定向
        if (!realize) {
            // 创建一个新类
            NSString *errClassName = NSStringFromClass([self class]);
            NSString *errSel = NSStringFromSelector(aSelector);
    
            NSLog(@"*** Crash Message: -[%@ %@]: unrecognized selector sent to instance %p ***",errClassName, errSel, self);
            
            NSString *className = @"CrachClass";
            Class cls = NSClassFromString(className);
            
            // 如果类不存在 动态创建一个类
            if (!cls) {
                Class superClsss = [NSObject class];
                cls = objc_allocateClassPair(superClsss, className.UTF8String, 0);
                // 注册类
                objc_registerClassPair(cls);
            }
            // 如果类没有对应的方法,则动态添加一个
            if (!class_getInstanceMethod(NSClassFromString(className), aSelector)) {
                class_addMethod(cls, aSelector, (IMP)Crash, "@@:@");
            }
            // 把消息转发到当前动态生成类的实例对象上
            return [[cls alloc] init];
        }
    }
    return [self zm_forwardingTargetForSelector:aSelector];
}

// 动态添加的方法实现
static int Crash(id slf, SEL selector) {
    return 0;
}

@end

De esta manera, la aplicación no se bloqueará porque no se puede encontrar el método.

artículo de referencia

Desarrollo de iOS: "Sistema de protección contra choques" (1) Selector no reconocido

Cómo construir un mecanismo de protección contra fallas de iOS

Supongo que te gusta

Origin blog.csdn.net/MinggeQingchun/article/details/118060624
Recomendado
Clasificación