iOS底层原理:KVO简析&自定义

1.KVO的简单使用

  • 三部曲
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.person = [YXPerson alloc];
    //1.添加监听
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.person.name = [NSString stringWithFormat:@"%@+",self.person.name];
}
//2.响应监听
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"新值- %@",[change objectForKey:NSKeyValueChangeNewKey]);
}
//移除监听
- (void)dealloc
{
    [self.person removeObserver:self forKeyPath:@"name"];
}

自动开关

@implementation YXPerson
//自动开关
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
    if ([key isEqualToString:@"name"]) {
        return NO;//返回NO,就监听不到
    }
    return YES;
}
- (void)setName:(NSString *)name
{
    //手动开关,自动开关关闭的时候,可以通过手动开关来打开监听
//    [self willChangeValueForKey:@"name"];
    _name = name;
//    [self didChangeValueForKey:@"name"];
}

@end

KVO观察数组

  • 数组的观察比较特殊,需要用到KVC的方式进行改变
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [[self.person mutableArrayValueForKey:@"myArray"] addObject:@"1"];
}

原理

  • 1.动态生成子类:NSKVONotifiy_A
    • 1.防止实例变量的影响
    • 2.动态子类的过程A:生成类B:添加class方法C:注册
    • 3.对类的存在性进行判断
  • 2.给动态子类添加setter方法
  • 3.消息转发给父类
  • 验证过程需要以下方法,有兴趣的同学可以自己验证
#pragma mark - 打印类所有的方法
- (void)printAllMethodsWithCls:(Class)cls
{
    unsigned int count = 0;
    Method *methods = class_copyMethodList(cls, &count);
    for (int i = 0; i < count; i ++) {
        Method method = methods[i];
        SEL sel = method_getName(method);
        NSLog(@"方法名- %@",NSStringFromSelector(sel));
    }
    free(methods);
}
#pragma mark - 遍历类以及子类
- (void)printClass:(Class)cls
{
    //注册类的总数
    int count = objc_getClassList(NULL, 0);
    //创建一个数组,其中包含给定对象
    NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
    //获取所有已注册的类
    Class *classes = (Class *)malloc(sizeof(Class)*count);
    objc_getClassList(classes, count);
    for (int i = 0; i < count; i ++) {
        if (cls == class_getSuperclass(classes[i])) {
            [mArray addObject:classes[i]];
        }
    }
    free(classes);
    NSLog(@"classes = %@",mArray);
}

简单自定义KVO

#import 

NS_ASSUME_NONNULL_BEGIN

typedef void (^YXKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);

@interface NSObject (YXKVO)
- (void)yx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(YXKVOBlock)block;
- (void)yx_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end

NS_ASSUME_NONNULL_END
#import "NSObject+YXKVO.h"
#import 

static NSString *const k_YXKVOPrefix = @"YXKVONotifying_";
static NSString *const k_YXKVOAssiociateKey = @"kYXKVO_AssiociateKey";

@interface YXInfo : NSObject
@property(nonatomic,weak)NSObject * observer;
@property(nonatomic,copy)NSString * keyPath;
@property(nonatomic,copy)YXKVOBlock handelBlock;
@end
@implementation YXInfo
- (instancetype)initWithObserver:(NSObject *)observer keyPath:(NSString *)keyPath handelBlock:(YXKVOBlock)handelBlock
{
    if (self == [super init]) {
        _observer = observer;
        _keyPath = keyPath;
        _handelBlock = handelBlock;
    }
    return self;
}
@end

@implementation NSObject (YXKVO)
- (void)yx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(YXKVOBlock)block
{
    //1.验证是否存在set方法
    [self judgeSetMethodFormKeyPath:keyPath];
    
    //2.动态生成子类
    Class newClass = [self creatChildClassWithKeyPath:keyPath];
    
    //添加set方法
    SEL setSel = NSSelectorFromString([self setterForGetter:keyPath]);
    Method setMetod = class_getInstanceMethod([self class], setSel);
    const char *setType = method_getTypeEncoding(setMetod);
    class_addMethod(newClass, setSel, (IMP)yx_setter, setType);
    
    //3.更改isa指向
    object_setClass(self, newClass);
    
    //4.保存信息
    YXInfo *info = [[YXInfo alloc] initWithObserver:observer keyPath:keyPath handelBlock:block];
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(k_YXKVOAssiociateKey));
    if (!mArray) {
        mArray = [NSMutableArray arrayWithCapacity:1];
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(k_YXKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [mArray addObject:info];
}
- (void)yx_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
    
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(k_YXKVOAssiociateKey));
    if (observerArr.count<=0) {
        return;
    }
    
    for (YXInfo *info in observerArr) {
        if ([info.keyPath isEqualToString:keyPath]) {
            [observerArr removeObject:info];
            objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(k_YXKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            break;
        }
    }
    
    if (observerArr.count<=0) {
        // 指回给父类
        Class superClass = [self class];
        object_setClass(self, superClass);
    }
}
#pragma mark - 验证是否存在set方法
- (NSString *)setterForGetter:(NSString *)keyPath
{
    if (keyPath.length < 0) {
        return @"";
    }
    NSString *firstString = [[keyPath substringToIndex:1] uppercaseString];
    NSString *leaveString = [keyPath substringFromIndex:1];
    return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}
- (void)judgeSetMethodFormKeyPath:(NSString *)keyPath
{
    Class superClass = object_getClass(self);
    SEL setterSel = NSSelectorFromString([self setterForGetter:keyPath]);
    Method setterMethod = class_getInstanceMethod(superClass, setterSel);
    if (!setterMethod) {
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"当前没有%@的set方法",keyPath] userInfo:nil];
    }
}
#pragma mark - 动态生成子类
- (Class)creatChildClassWithKeyPath:(NSString *)keyPath
{
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",k_YXKVOPrefix,oldClassName];
    Class newClass = NSClassFromString(newClassName);
    if (newClass) {
        return newClass;
    }
    
    //申请类
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    
    //注册类
    objc_registerClassPair(newClass);
    
    //添加class方法
    SEL classSel = NSSelectorFromString(@"class");
    Method classMethod = class_getInstanceMethod([self class], classSel);
    const char *classTypes = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSel, (IMP)yx_class, classTypes);
    
    //添加dealloc
    SEL deallocSEL = NSSelectorFromString(@"dealloc");
    Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
    const char *deallocType  = method_getTypeEncoding(deallocMethod);
    class_addMethod(newClass, deallocSEL, (IMP)yx_dealloc, deallocType);
    
    return newClass;
}
Class yx_class(id self,SEL _cmd){
    return class_getSuperclass(object_getClass(self));
}
static void yx_setter(id self,SEL _cmd,id newValue){
    NSLog(@"子类set");
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue = [self valueForKey:keyPath];
    //消息转发:转发给父类
    //改变父类的值
    void(*yx_msgSendSuper)(void *,SEL,id) = (void *)objc_msgSendSuper;
    struct objc_super superStruct = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };
    yx_msgSendSuper(&superStruct,_cmd,newValue);
    
    //数据回调
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(k_YXKVOAssiociateKey));
    for (YXInfo *info in mArray) {
        if ([info.keyPath isEqualToString:keyPath] && info.handelBlock) {
            info.handelBlock(info.observer, keyPath, oldValue, newValue);
        }
    }
}
static void yx_dealloc(id self,SEL _cmd){

    Class superClass = [self class];
    object_setClass(self, superClass);
}
#pragma mark - 从set方法获取getter方法的名称 set:===> 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];
}
@end
  • 以上只是简单自定义,主要是为了对系统的KVO加深理解,如果想更深入得了解,可以看一下GNU下载地址和FBKVOController下载地址,下载进行学习

你可能感兴趣的:(iOS底层原理:KVO简析&自定义)