iOS开发-面试

kvo底层窥探

2018-07-04  本文已影响105人  目前运行时

相信kvo 作为一个ios开发者都用过,但是kvo底层具体怎么实现的,相信很多人也不太明白或者就算有的人明白 但是也说的不是很清楚,我开始也不太明白,自己打算研究一下,下面我来分享一下,如果有错误希望大家给予指正:
将oc的代码转换成c或者c++代码的命令(比如我转换的main.m文件)
首先切换到main.m所在的文件位置,然后执行这段命令:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

如果代码中有__weak
执行这段命令

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

#import "ViewController.h"
#import "DGPerson.h"
#import <objc/runtime.h>

@interface ViewController ()

@property (strong, nonatomic) DGPerson *person1;
//@property (strong, nonatomic) DGPerson *person2;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 动态生成这个类isa ---- NSKVONotifying_DGPerson
    self.person1 = [[DGPerson alloc] init];
    // 因为没有添加观察者 所以不生成中间类 而是isa --- 指向 DGPerson
    self.person1.age = 10;
    
//    self.person2 = [[DGPerson alloc] init];
//    self.person2.age = 20;
    // 查看他们调用那一个方法
//    NSLog(@"调用之前的 调用方法的地址 :%p %p",[self.person1 methodForSelector:@selector(setAge:)],[self.person2 methodForSelector:@selector(setAge:)]);
    
    // 添加观察者。观察者的主要目的是 观察对象的属性的改变
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
 [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"sasd"];
#pragma mark - 添加了观察者的之后的方法调用foundation的这个方法_NSSetIntValueAndNotify
    //  P (IMP) 0x10daf2790 :通过地址打印调用的方法
    
//   NSLog(@"调用之后的 调用方法的地址 :%p %p",[self.person1 methodForSelector:@selector(setAge:)],[self.person2 methodForSelector:@selector(setAge:)]);
    NSLog(@"asdahsdajsdajsd");

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    self.person1.age = 20;
//    self.person2.age = 30;

}
/**
 观察者的回去调用的代理

 @param keyPath 那个属性
 @param object 那个对象
 @param change 改变的字典
 @param context 传递的数据
 */
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    NSLog(@"%@ --- 的 %@ 属性值发生了改变,变化为 %@  传递给观察者的参数 %@",object,keyPath,change,context);
    
}
-(void)dealloc {
    [self.person1 removeObserver:self forKeyPath:@"age"];
}
@end

解释:其中注释的地方 大家不要看 ,先看没有注释的地方,可以看到我们改变age的值开始调用了observeValueForKeyPath。。。。。的这个方法,其中observeValueForKeyPath这个方法的参数我在注释的时候都做了说明,特别注意的是context:苹果的官方文档的解释是:注册观察者以接收键值观察通知时提供的值,其实我们可以简单的理解为传递的参数数据。
以上的打印信息为:

2018-07-04 16:52:42.828077+0800 kvo的底层原理窥探[2581:264281] <DGPerson: 0x600000018440> --- 的 age 属性值发生了改变,变化为 {
    kind = 1;
    new = 20;
    old = 10;
} 
#import "NSKVONotifying_DGPerson.h"

@implementation NSKVONotifying_DGPerson

- (void)setAge:(int)age
{
    _NSSetIntValueAndNotify();
}

// 伪代码
void _NSSetIntValueAndNotify()
{
    [self willChangeValueForKey:@"age"];
    [super setAge:age];
    [self didChangeValueForKey:@"age"];
}

- (void)didChangeValueForKey:(NSString *)key
{
    // 通知监听器,某某属性值发生了改变
    [oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
}

解释:其中 以上的代码是我估计的,其实苹果是闭元的,我们看不到具体怎么实现,但是如果你的手机是越狱的 可以通过ifunbox这个软件来找到fundation框架中的这个方法,确实有的 我找过 因为现在的我手机不是越狱的 我没法演示了,通过反汇编的软件来看到他的汇编的代码,那个软件可以生成伪代码,她的伪代码和我写的基本差不多。

   Class person1Class = object_getClass(self.person1);
    Class person1Cls = [self.person1 class];
     NSLog(@"%@ --- %@",person1Class,person1Cls);
    Class person2Class = object_getClass(self.person2);
    Class person2Cls = [self.person2 class];
     NSLog(@"%@ --- %@",person2Class,person2Cls);

发生现象:


image.png

解释:要明白这个,需要知道这个函数object_getClass的作用,她的作用是:
1.传递一个实例对象生成的是类对象
2.传递一个类对象生成的是原类对象
3.传递原类对象生成的是基类的原类对象
以上的打印信息,可以证明添加了观察这的对象他在这个NSKVONotifying_DGPerson类中重写了class方法,为什么呢 因为如果不重写 那么我们的调用[self.person1 class]这个方法 应该返回的是:NSKVONotifying_DGPerson而不是DGPerson,我猜测苹果这样写 就是为了让我们普通开发者不知道NSKVONotifying_DGPerson,为什么object_getClass 就能返回他本身的类呢?因为object_getClass 是runtime的方法,他拿到的就是我们真实的方法。怎么证明NSKVONotifying_DGPerson确实重写了class方法呢?我用下面的代码进行证实。

- (void)methodWithClass:(Class)cls {
    
    unsigned int count;
    Method *methodList = class_copyMethodList(cls, &count);
    NSMutableString *nameStr = [NSMutableString string];
    
    for (NSInteger index = 0; index < count; index ++) {
        Method method = methodList[index];
        NSString *methodName = NSStringFromSelector(method_getName(method));
        [nameStr appendFormat:@" + %@",methodName];
    }
    NSLog(@"nameStr --- %@",nameStr);
    // c语言的函数要释放
    free(methodList);
}

打印:


image.png

可以看到 其实他是实现了四个方法 其中有class,我们大致猜测一下 ,class的实现应该是这样的

- (Class)class{
return [DGPerson class];
}
#import "DGPerson.h"

@implementation DGPerson


-(void)setAge:(int)age {
    if (age < 0) {
        _age = age;
    }else{
        [self willChangeValueForKey:@"age"];
        _age = age;
        [self didChangeValueForKey:@"age"];
    }
}
/**
 是否自动发送通知呢

 @return no:不自动发送通知
 */
+(BOOL)automaticallyNotifiesObserversOfAge{
    return NO;
}
@end

注意:需要添加观察者 ,不添加观察者无效

#import "DGTwoViewController.h"
#import "DGStudent.h"

@interface DGTwoViewController ()

@end

@implementation DGTwoViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    
    [self.student addObserver:self forKeyPath:@"weight" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"weight"];
    [self.student addObserver:self forKeyPath:@"height" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"height"];
}
/**
 观察者的回去调用的代理

 @param keyPath 那个属性
 @param object 那个对象
 @param change 改变的字典
 @param context 传递的数据
 */
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"------------------");
    NSLog(@"%@ --- 的 %@ 属性值发生了改变,变化为 %@  传递给观察者的参数 %@",object,keyPath,change,context);
    if (([object isKindOfClass:[DGStudent class]]) && ([keyPath isEqualToString:@"weight"] || [keyPath isEqualToString:@"height"])) {
        NSLog(@"我们是好孩子");
    }else{
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }

}
#pragma mark - 其他的相关的处理
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    self.student.weight = 50;
    self.student.height = 50;
}

-(void)dealloc {
    [self.student removeObserver:self forKeyPath:@"age"];
}
@end
头文件:
#import "DGViewController.h"
@interface DGTwoViewController : DGViewController
@end
上一篇下一篇

猜你喜欢

热点阅读