iOS开发底层之KVO探索下 -18

Posted iOS_developer_zhong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发底层之KVO探索下 -18相关的知识,希望对你有一定的参考价值。

文章目录


# 前言

提示:本章主要讲自定义KVO 和 优秀的第三方KVO

一、自定义KVO

思路如下:

1. 新建一个分类,名称为:NSOBject+MyKVO, 仿照系统KVO的方法写, 基本代码如下:

// Block回调
typedef void(^MYKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);

@interface NSObject (MyKVO)

- (void)my_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(MYKVOBlock)block;

- (void)my_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

@end

2. 监听的keyPath,是否存在setter方法,不让成员变量也能进行kvo.

#pragma mark - 验证是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath
    Class superClass    = object_getClass(self);
    SEL setterSeletor   = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
    if (!setterMethod) 
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"没有当前%@的setter方法",keyPath] userInfo:nil];
    


3. 动态生成子类, 然后isa 执行这个新的类。

    // 1: 动态生成子类
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    // 2: isa的指向 : 新的类。 
    object_setClass(self, newClass);

// 动态生成子类方法
/*
1.  根据类名, 进行派生新的类。 
   1.1 根据类名+我们自定义的KVO类前缀, 生成class ,判断是否存在,存在就直接返回
   1.2 没有存在,就开辟空间。 
2.  创建好新类后,进行其他必要操作。 
   2.1 : 注册类 
   2.2 : 添加class
   2.3 : 添加setter , 发送通知
   2.4 : 添加dealloc : 后续实现了自动dealloc,不用手动销毁。 
*/

- (Class)createChildClassWithKeyPath:(NSString *)keyPath
    
    NSString *oldClassName = NSStringFromClass([self class]);
    // 1.1 
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kMYKVOPrefix,oldClassName];
    Class newClass = NSClassFromString(newClassName);
    // 防止重复创建生成新类
    if (newClass) return newClass;
    /**
     * 如果内存不存在,创建生成
     * 参数一: 父类
     * 参数二: 新类的名字
     * 参数三: 新类的开辟的额外空间
     */
    // 1.2 : 申请类
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    // 2.1 : 注册类
    objc_registerClassPair(newClass);
    // 2.2 : 添加class :
    SEL classSEL = NSSelectorFromString(@"class");
    Method classMethod = class_getInstanceMethod([self class], classSEL);
    const char *classTypes = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSEL, (IMP)zgr_class, classTypes);
    // 2.3 : 添加setter
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    const char *setterTypes = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)zgr_setter, setterTypes);
    // 2.4 : 添加dealloc
    SEL deallocSEL = NSSelectorFromString(@"dealloc");
    Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
    const char *deallocTypes = method_getTypeEncoding(deallocMethod);
    class_addMethod(newClass, deallocSEL, (IMP)zgr_dealloc, deallocTypes);
    
    return newClass;



static void zgr_dealloc(id self,SEL _cmd)
//    Class superClass = [self class];
//    object_setClass(self, superClass);


// 
static void zgr_setter(id self,SEL _cmd,id newValue)
    NSLog(@"来了:%@",newValue);
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue = [self valueForKey:keyPath];
    // 4: 消息转发 : 转发给父类
    // 改变父类的值 --- 可以强制类型转换
    void (*zgr_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)
    zgr_msgSendSuper(&superStruct,_cmd,newValue);
    
    // 5: 信息数据回调
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kMYKVOAssiociateKey));
    
    for (MyInfo *info in mArray) 
        if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) 
            info.handleBlock(info.observer, keyPath, oldValue, newValue);
        
    


Class zgr_class(id self,SEL _cmd)
    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];


    

4. 生成派生类后,我们将isa指向派生类。

// isa的指向派生类
    object_setClass(self, newClass);
  1. 我们需要在添加观察者的入口处,对所有的访问者进行保存, 后续需要进行释放。
    MyInfo *info = [[MyInfo alloc] initWitObserver:observer forKeyPath:keyPath handleBlock:block];
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kMYKVOAssiociateKey));
    if (!mArray) 
        mArray = [NSMutableArray arrayWithCapacity:1];
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kMYKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    [mArray addObject:info];

// MyInfo类的内容如下

#import <Foundation/Foundation.h>
#import "NSObject+MyKVO.h"
NS_ASSUME_NONNULL_BEGIN
static NSString *const kMYKVOPrefix = @"MYKVONotifying_";
static NSString *const kMYKVOAssiociateKey = @"kMYKVO_AssiociateKey";
@interface MyInfo : NSObject
@property (nonatomic, weak) NSObject    *observer;
@property (nonatomic, copy) NSString    *keyPath;
@property (nonatomic, copy) MYKVOBlock  handleBlock;
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(MYKVOBlock)block;
@end

@implementation MyInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(MYKVOBlock)block
    if (self=[super init]) 
        _observer = observer;
        _keyPath  = keyPath;
        _handleBlock = block;
    
    return self;

@end

5. 类中对外的kvo入口方法与销毁方法如下:

// 具体的实现在上面, 这里是串联起来。 
- (void)my_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(MYKVOBlock)block
    
    // 1: 验证是否存在setter方法 : 不让实例进来
    [self judgeSetterMethodFromKeyPath:keyPath];
    // 2: 动态生成子类
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    // 3: isa的指向心类
    object_setClass(self, newClass);
    // 4: 保存信息
    MyInfo *info = [[MyInfo alloc] initWitObserver:observer forKeyPath:keyPath handleBlock:block];
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kMYKVOAssiociateKey));
    if (!mArray) 
        mArray = [NSMutableArray arrayWithCapacity:1];
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kMYKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    [mArray addObject:info];


// 观察者的移除
- (void)my_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
    // 通过关联对象 获取保存的观察者数组。 
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kMYKVOAssiociateKey));
    if (observerArr.count<=0) 
        return;
    
    
    for (MyInfo *info in observerArr) 
    // 通一个keypath
        if ([info.keyPath isEqualToString:keyPath]) 
            [observerArr removeObject:info];
            // 保存剩下的观察者数组。 
            objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kMYKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            break;
        
    
    
    // 如果观察者数组为零,就可以把isa还原成原类。 
    if (observerArr.count<=0) 
        // 指回给父类
        Class superClass = [self class];
        object_setClass(self, superClass);
    


6. 使用method swizzle处理观察者销毁。

+ (BOOL)zgr_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;
    
    // 如果原类没有实现dealloc方法,就进行添加对应方法,反之崩溃。 
    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));
    // 如果是 true 说明类中是没有实现这个方法的。 反之,则原类有这个方法
    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 zgr_hookOrigInstanceMenthod:NSSelectorFromString(@"dealloc") newInstanceMenthod:@selector(myDealloc)];
    );


- (void)myDealloc

// 还原isa . 
    Class superClass = [self class];
    object_setClass(self, superClass);
    [self myDealloc];


二、KVO框架

1. FBKVOController探索

1.1 : 基本使用

// 普通属性监听
    [self.kvoCtrl observe:self.person keyPath:@"age" options:0 action:@selector(lg_observerAge)];
    // 普通属性, block回调
    [self.kvoCtrl observe:self.person keyPath:@"name" options:(NSKeyValueObservingOptionNew) block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) 
        NSLog(@"****%@****",change);
    ];
    // 数组属性, block回调。 
    [self.kvoCtrl observe:self.person keyPath:@"mArray" options:(NSKeyValueObservingOptionNew) block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) 
        NSLog(@"****%@****",change);
    ];
    

1.2 : kvo生命周期

#pragma mark - lazy
// 跟随着观察者,你销毁,我销毁,自动销毁做准备
- (FBKVOController *)kvoCtrl
    if (!_kvoCtrl) 
        _kvoCtrl = [FBKVOController controllerWithObserver:self];
    
    return _kvoCtrl;


1.3 :自动销毁设计

- (void)dealloc

  [self unobserveAll];
  pthread_mutex_destroy(&_lock);


// 移除所有的观察者, 生命周期跟着FBKVOController。 
- (void)_unobserveAll

  // lock
  pthread_mutex_lock(&_lock);

  NSMapTable *objectInfoMaps = [_objectInfosMap copy];

  // clear table and map
  [_objectInfosMap removeAllObjects];

  // unlock
  pthread_mutex_unlock(&_lock);

  _FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];

  for (id object in objectInfoMaps) 
    // unobserve each registered object and infos
    NSSet *infos = [objectInfoMaps objectForKey:object];
    [shareController unobserve:object infos:infos];
  


以上是关于iOS开发底层之KVO探索下 -18的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发底层之KVO探索上 - 17

iOS底层探索之KVO—FBKVOController分析

iOS底层探索之KVO—KVO简介

iOS底层探索之KVO—自定义KVO

iOS底层探索之KVO—KVO原理分析

iOS底层探索之KVO—自定义KVO