16, IOS inferior Análisis - Custom MVA

MVA personalizada

 

分析了KVOLa implementación 为了加深印象模仿系统的方式自定义一个KVOprincipio .

En primer lugar, el diseño de la interfaz

1.1 Función básica Interfaz

sistema Imitate KVOinterfaz proporciona la siguiente interfaz básica.

  1. observador de registro
  2. Retire el observador
  3. devolución de llamada
//  NSObject+LJLKVO.h
#import <Foundation/Foundation.h>
#import "LJLKVOInfo.h"

@interface NSObject (LJLKVO)
//注册观察者
- (void)ljl_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LJLKeyValueObservingOptions)options context:(nullable void *)context;
//回调
- (void)ljl_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
//移除观察者
- (void)ljl_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end

LJLKVOInfo objetos abstractos

//  LJLKVOInfo.h
#import <Foundation/Foundation.h>

typedef NS_OPTIONS(NSUInteger, LJLKeyValueObservingOptions) {
    LJLKeyValueObservingOptionNew = 0x01,
    LJLKeyValueObservingOptionOld = 0x02,
};

@interface LJLKVOInfo : NSObject
//这个地方 NSObject 要用 weak 进行修饰。
//因这个 对象在外层是(person)self -> mArray -> info - > observer(外层的VC) -> person
//如果使用stong 会造成循环引用
@property(nonatomic, weak) NSObject * observer;
@property(nonatomic, copy) NSString * keyPath;
@property(nonatomic, assign) LJLKeyValueObservingOptions options;

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LJLKeyValueObservingOptions)options;
@end
//  LJLKVOInfo.m
#import "LJLKVOInfo.h"

@implementation LJLKVOInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LJLKeyValueObservingOptions)options
{
    self = [super init];
    if (self) {
        self.observer = observer;
        self.keyPath = keyPath;
        self.options = options;
    }
    return self;
}
@end

 

Complete el siguiente funciones de registro de observador:

  1. En la inspección de referencia (observación de que el colocador, variables de instancia filtrados)
  2. Compruebe si hay propiedadessetter
  3. Dinámicamente crear subclases de objetosBLKVOClass_xxx
  4. isa-swizzling
  5. Reescritura -class, -deallocmétodo
  6. reescribirsetter
  7. Guardar información de los observadores, las devoluciones de llamada cuando una propiedad cambia

 

2.2.1 La idea básica

  1. De acuerdo con keyPath la adquisición settery según setterobtener keyPathreferencia de KVCprioridad de búsqueda, y la nota que desea convertir keyPath la primera letra del caso
  2. LJLKVONotifying_ crea de forma dinámica _xxxsubclases, creadas sólo una vez después no destruida, por lo que cada vez que desee comprobar si ya se creó
  3. método reemplazado también comprobar si se ha producido
  4. Reemplazado class método devuelve la clase padre, es decir, la clase original, por lo que el mundo exterior a llamar [obj class]para obtener el tipo correcto
  5. deallocAlgunos métodos especiales, en ARCla sobrescritura directa no pueden ser, ni llamar explícitamente [super dealloc], por lo tanto el logro de empleado en method-swizzlingla fig.
  6. También puedes ver el peso cuando se agrega un observador, si observer, keyPathy contextson los mismos, no repita Agregar
  7. Retire el observador está diseñado, observer, keyPath, contexttres parámetros pueden estar vacíos, como observeres nil, la eliminación de todos los observadores, coincide exactamente lo contrario para eliminar los otros dos parámetros.
  8. Desde KVOque se consigue a través de la clasificación, por lo tanto el ahorro conseguido por medio de la información de telespectadores asociado con el objeto, se añade una variable al diccionario asociado actual visor de objetos con el fin keyPathpara el keyvariable es una matriz value, los elementos de la matriz es variablemodel
//  NSObject+LJLKVO.m

#import "NSObject+LJLKVO.h"
#import <objc/message.h>

static NSString * const kLJLKVOPrefix = @"LJLKVONotifying_";
static NSString *const kLJLKVOAssiociateKey = @"kLJLKVO_AssiociateKey";

@implementation NSObject (LJLKVO)

-(void)ljl_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LJLKeyValueObservingOptions)options context:(void *)context
{
//    1、验证是否存在setter 方法,过滤实例变量
    [self judgeSetterMethodFromKeyPath:keyPath];
//    2、动态生成子类
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    // 3: isa的指向 : LGKVONotifying_LGPerson
    object_setClass(self, newClass);
    // 4: 保存观察者信息
//    面向对象的封装LJLKVOInfo
    LJLKVOInfo *info = [[LJLKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options];
//    5、处理多属性关联对象的问题
//    获取存放关联对象的数组。
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLJLKVOAssiociateKey));
    
//    如果不存在关联对象的数组就进行创建
    if (!observerArr) {
        observerArr = [NSMutableArray arrayWithCapacity:1];
        [observerArr addObject:info];
//        创建关联对象集合
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLJLKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
}

- (void)ljl_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context{
    
}

- (void)ljl_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
    
//    获取关联对象
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLJLKVOAssiociateKey));
    if (observerArr.count<=0) {
        return;
    }
    
    for (LJLKVOInfo *info in observerArr) {
        if ([info.keyPath isEqualToString:keyPath]) {
            [observerArr removeObject:info];
            objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLJLKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            break;
        }
    }
    
    if (observerArr.count<=0) {
        // 将对象的 isa 指回给父类
        Class superClass = [self class];
        object_setClass(self, superClass);
    }
}

#pragma mark - 验证是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
//   1、获取当前类的父类
    Class superClass = object_getClass(self);
//    2、方法编号
    SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));
//    3、方法实现
    Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
    if (!setterMethod) {
//        如果没有好到setter方法就抛出异常
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"没有找到当前 %@ 的setter",keyPath] userInfo:nil];
    }
}

#pragma mark -
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
//   1、 获取旧的类名
    NSString *oldClassName = NSStringFromClass([self class]);
//  2、生成新的类名
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLJLKVOPrefix,oldClassName];
//    3、用生成的新的类名去获取类
    Class newClass = NSClassFromString(newClassName);
    // 4、如果获取到了就直接返回。防止重复创建生成新类
    if (newClass) return newClass;
    /**
     * 如果内存不存在,创建生成
     * 参数一: 父类
     * 参数二: 新类的名字
     * 参数三: 新类的开辟的额外空间
     */
    // 2.1 : 申请类
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    // 2.2 : 注册类
    objc_registerClassPair(newClass);
    // 2.3.1 : 添加class : class的指向是LJLKVOPerson
    SEL classSEL = NSSelectorFromString(@"class");
    Method classMethod = class_getInstanceMethod([self class], classSEL);
    const char *classTypes = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
//    class 方法 IMP 是lg_clss  所以后面再调[self class] 是调用的lg_class
    
    // 2.3.2 : 添加setter
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    const char *setterTypes = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes);
    return newClass;
}

static void lg_setter(id self,SEL _cmd,id newValue){
    NSLog(@"来了:%@",newValue);
    // 4: 消息转发 : 转发给父类
    // 改变父类的值 --- 可以强制类型转换
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue       = [self valueForKey:keyPath];
    
//    向父类发送消息需要的参数
    void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
    // void /* struct objc_super *super, SEL op, ... */
    struct objc_super superStruct = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self)),
    };
    //objc_msgSendSuper(&superStruct,_cmd,newValue)
    lg_msgSendSuper(&superStruct,_cmd,newValue);
    // 1: 取出观察者
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLJLKVOAssiociateKey));
    
    for (LJLKVOInfo *info in observerArr) {
        if ([info.keyPath isEqualToString:keyPath]) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
                // 对新旧值进行处理
                if (info.options & LJLKeyValueObservingOptionNew) {
                    [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                }
                if (info.options & LJLKeyValueObservingOptionOld) {
                    [change setObject:@"" forKey:NSKeyValueChangeOldKey];
                    if (oldValue) {
                        [change setObject:oldValue forKey:NSKeyValueChangeOldKey];
                    }
                }
                // 2: 消息发送给观察者 (回调)
                SEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:change:context:);
                objc_msgSend(info.observer,observerSEL,keyPath,self,change,NULL);
//              把self 换成superClass更好
//                objc_msgSend(info.observer,observerSEL,keyPath,class_getSuperclass(self),change,NULL);
            });
        }
    }
}

Class lg_class(id self,SEL _cmd){
//    这个地方过来的self 是对象,需要获取他的类 object_getClass(self)
    return class_getSuperclass(object_getClass(self));
}

#pragma mark - 从get方法获取set方法的名称 key ===>>> setKey:
static NSString *setterForGetter(NSString *getter){
    
    if (getter.length <= 0) { return nil;}
    
    NSString *firstString = [[getter substringToIndex:1] uppercaseString];
    NSString *leaveString = [getter substringFromIndex:1];
    
    return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}

#pragma mark - 从set方法获取getter方法的名称 set<Key>:===> key
static NSString *getterForSetter(NSString *setter){
    
    if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
    
    NSRange range = NSMakeRange(3, setter.length-4);
    NSString *getter = [setter substringWithRange:range];
    NSString *firstString = [[getter substringToIndex:1] lowercaseString];
    return  [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}

Lo anterior es que se consigue una KVO completa. Trate de llamar a que la siguiente

    self.person = [[LJLKVOPerson alloc] init];
    [self.person ljl_addObserver:self
                      forKeyPath:@"name"
                         options:(LJLKeyValueObservingOptionOld | LJLKeyValueObservingOptionNew)
                         context:NULL];
    
    [self printClassAllMethod:[LJLKVOPerson class]];
    [self printClasses:[LJLKVOPerson  class]];
    self.person.name = @"LJL";
    self.person->_nikcName    = @"1234";

A menudo probar la impresión normal, imprimir contenido específico ya no aparecen. Se puede hacer referencia a un artículo sobre  análisis subyacente IOS - MVA
 

para resumir

Encargo del MVA
. 1, comprobando
      las variables de instancia dejar que venga: para verificar si hay métodos setter
subclase 2, genera dinámicamente

  1.  Dinámica abrir una nueva categoría: LJLKVONotifying_xxx
  2.  la clase de registro
  3.  Añadir clase: Punto de clase es LGPerson
  4. métodos setter: Asignación a través del organismo, la clase principal aplicación self.name = @ "ljl";
  5.  objc_getAssociatedObject asociado con nuestros televidentes viven

3, la isa punto LJLKVONotifying_LGPerson
4, el reenvío de mensajes: la devolución de llamada correspondiente

 Anterior análisis artículo MVA para generar una clase dinámica niño anula el organismo, clase, dealloc, método _isKVOA, dealloc por qué este método para volver a escribir esto? ¿Qué se puede hacer?

destructor dealloc se libera. MVA necesidad cuando no se requiere para quitar manualmente el espectador, esto es una molestia, y si el caso va a olvidar para eliminar algunos problemas, por lo que ahora desea personalizar MVA será liberado automáticamente.

//NSObject+LJLKVO.m
//这里不能直接写dealloc 因为这是NSObject 的分类会有很多系统的类调用到(在还没启动的时候都有可能会被调用到)
//- (void)dealloc{
//    Class superClass = [self class];
//    object_setClass(self, superClass);
//}
//进行方法交换
+ (BOOL)kc_hookOrigInstanceMenthod:(SEL)oriSEL newInstanceMenthod:(SEL)swizzledSEL {
    Class cls = self;
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    
    if (!swiMethod) {
        return NO;
    }
    if (!oriMethod) {
        class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
    }
    
    BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
    if (didAddMethod) {
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{
        method_exchangeImplementations(oriMethod, swiMethod);
    }
    return YES;
}


+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        [self kc_hookOrigInstanceMenthod:NSSelectorFromString(@"dealloc") newInstanceMenthod:@selector(myDealloc)];
    });
}

- (void)myDealloc{
    //将isa 指针指回去
    Class superClass = [self class];
    object_setClass(self, superClass);
    [self myDealloc];
}

- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
    
    ......
    // 2.3.3 : 添加dealloc
    SEL deallocSEL = NSSelectorFromString(@"dealloc");
    Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
    const char *deallocTypes = method_getTypeEncoding(deallocMethod);
    class_addMethod(newClass, deallocSEL, (IMP)lg_dealloc, deallocTypes);
  
//进行方法交换,然后myDealloc 的时候释放 但是这样写的话如果
//    Method m1 = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
//    Method m2 = class_getInstanceMethod([self class], @selector(myDealloc));
//    method_exchangeImplementations(m1, m2);
//    method_exchangeImplementations(m1, m2);
    
    return newClass;
}

Además del sistema para imitar el KVO, también logrado un KVO funcional, en lugar de utilizar el bloque de devolución de llamada de devolución de llamada método - observeValueForKeyPath: ofObject: cambio: contexto :;, con fácil escucha conocer el objeto correspondiente al contenido, así lanzado como automáticamente sin ser retirado manualmente, evitar olvidar causa otros problemas. código completo: LGKVO

extendido:

Por último, se da charla sobre cuestiones objc_msgSend llamada. Necesidad de pasar parámetros a usar cuando la necesidad de modificar la configuración

 

Publicado 83 artículos originales · ganado elogios 12 · vistas 180 000 +

Supongo que te gusta

Origin blog.csdn.net/shengdaVolleyball/article/details/104742218
Recomendado
Clasificación