iOS底层(二)_KVO原理

面试题

问题1:iOS用什么方式实现一个对象的KVO?(KVO的本质是什么?)

答复:
利用runtimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类,当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数。
函数实现内容如下
willChangeValueForKey:
父类原来的setter
didChangeValueForKey:(内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:))

问题2:如何手动触发KVO?

答复:
手动调用willChangeValueForKey:和didChangeValueForKey:

问题3:直接修改成员变量会触发KVO么?

答复:
不会触发KVO

KVO的全称是Key-Value Observing,俗称”键值监听“,可以用于监听某个对象属性值的改变

第一:KVO的基本使用

基本使用详情如下,需要注意的是:在对象销毁前要移除监听,避免野指针访问

#import 
@interface MJPerson : NSObject
@property (nonatomic, assign) int age;
@end

#import "MJPerson.h"
@implementation MJPerson
@end
#import "ViewController.h"
#import "MJPerson.h"

@interface ViewController ()
@property (nonatomic, strong, readwrite) MJPerson *person1;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 1.0 创建Person对象
    self.person1 = [[MJPerson alloc]init];
    self.person1.age = 1;
    
    // 2.0 对self.person1的age添加监听
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
    
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // 3.0 修改age的属性值(必须使用KVC的方式修改)
    self.person1.age = 11;
    // 等同于
    // [self.person1 setAge:11];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    // 4.0 获取到age值发生改变的通知
    NSString *new = [change objectForKey:NSKeyValueChangeNewKey];
    NSString *old = [change objectForKey:NSKeyValueChangeOldKey];
    NSLog(@"监听到%@的%@属性值改变了---新值:%@ 旧值 %@",object,keyPath,new,old);
}

- (void)dealloc {
    // 5.0 移除监听
    [self.person1 removeObserver:self forKeyPath:@"age"];
}
@end
第二:KVO的实质分析
#import "ViewController.h"
#import "MJPerson.h"

@interface ViewController ()
@property (nonatomic, strong, readwrite) MJPerson *person1;
@property (nonatomic, strong, readwrite) MJPerson *person2;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person1 = [[MJPerson alloc]init];
    self.person1.age = 1;
    
    self.person2 = [[MJPerson alloc]init];
    self.person2.age = 2;
    
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
    
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // 打印方法:p self.person1.isa
    // 打印self.person1的isa,结果:NSKVONotifying_MJPerson
    // NSKVONotifying_MJPerson 是rumtime动态创建的一个类,并且NSKVONotifying_MJPerson的superclass是MJPerson
    self.person1.age = 11;
    // self.person2的isa,结果:MJPerson
    self.person2.age = 12;
    
    //    等同于
    //    [self.person1 setAge:11];
    //    [self.person2 setAge:12];
    
    // 实例对象方法调用原理:首先找到isa指向的类对象——>在类对象中查找对象方法的实现
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSString *new = [change objectForKey:NSKeyValueChangeNewKey];
    NSString *old = [change objectForKey:NSKeyValueChangeOldKey];
    NSLog(@"监听到%@的%@属性值改变了---新值:%@ 旧值 %@",object,keyPath,new,old);
}

- (void)dealloc {
    [self.person1 removeObserver:self forKeyPath:@"age"];
}

@end

推理得出以下rumtime动态创建的NSKVONotifying_MJPerson类的实现,伪代码(重写了setAage:的实现)

@interface NSKVONotifying_MJPerson : MJPerson
@end


#import "NSKVONotifying_MJPerson.h"

@implementation NSKVONotifying_MJPerson

- (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];
}


@end

未添加监听的person1对象


iOS底层(二)_KVO原理_第1张图片
image.png

添加监听器的person2对象


iOS底层(二)_KVO原理_第2张图片
WeChat82eebd16ebe9003855a579b6e401ca04.png

扩展:

添加监听后,动态创建一个NSKVONotifying_MJPerson类,在这类中不只是重写了setAge:方法,还重写了以下的方法

-(Class)class {
    // 这里重写后返回MJPerson类对象,目的:屏蔽内部实现,隐藏了NSKVONotifying_MJPerson类的存在
    return [MJPerson class];
}

- (void)dealloc {
    // 收尾工作
}

- (BOOL)_isKVOA {
    return YES;
}


验证方法如下
对于person1和person2分别调用如下方法,结果如下
2019-09-10 14:28:58.651949+0800 KVOSession[14066:27762244] NSKVONotifying_MJPerson 方法列表 setAge:,class,dealloc,_isKVOA,
2019-09-10 14:28:58.652042+0800 KVOSession[14066:27762244] MJPerson 方法列表 setAge:,age,


- (void)printMethodNamesOfClass:(Class)cls {
    unsigned int count;
    // 获得方法数组
    Method *methodList = class_copyMethodList(cls, &count);
    
    NSMutableString *methodNames = [NSMutableString string];
    
    // 遍历所有的方法
    for (int i=0; i
第三:KVO的实质验证
#import "ViewController.h"
#import "MJPerson.h"
#import 

@interface ViewController ()
@property (nonatomic, strong, readwrite) MJPerson *person1;
@property (nonatomic, strong, readwrite) MJPerson *person2;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person1 = [[MJPerson alloc]init];
    self.person1.age = 1;
    
    self.person2 = [[MJPerson alloc]init];
    self.person2.age = 2;
    
    // 验证一:person1实例对象,添加监听前isa的指向
    NSLog(@"person1添加监听KVO之前 - %@",
          object_getClass(self.person1)
          );

    NSLog(@"person1添加监听KVO之前 - %p",
          // methodForSelector返回的IMP方法实现函数指针
          [self.person1 methodForSelector:@selector(setAge:)]
          );
        

    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
    
    
    NSLog(@"person1添加监听KVO之后 - %@",
          object_getClass(self.person1)
          );

    NSLog(@"person1添加监听KVO之前 - %p",
          // methodForSelector返回的IMP方法实现函数指针
          [self.person1 methodForSelector:@selector(setAge:)]
          );


    /*
      验证一结果:isa指针在添加监听前后,发生了变化
     2019-09-10 11:46:30.515276+0800 KVOSession[10195:27600983] person1添加监听KVO之前 - MJPerson
     2019-09-10 11:46:30.515555+0800 KVOSession[10195:27600983] person1添加监听KVO之后 - NSKVONotifying_MJPerson
     
     验证二结果:根据打印结果,得出添加监听后,setAge:的实现函数变成了_NSSetIntValueAndNotify
     2019-09-10 13:44:55.163073+0800 KVOSession[12803:27683855] person1添加监听KVO之前 - 0x10bc25820
     2019-09-10 13:44:55.163353+0800 KVOSession[12803:27683855] person1添加监听KVO之前 - 0x10c1c6216
     (lldb) p (IMP)0x10bc25820
     (IMP) $0 = 0x000000010bc25820 (KVOSession`-[MJPerson setAge:] at MJPerson.h:14)
     (lldb) p (IMP)0x10c1c6216
     (IMP) $1 = 0x000000010c1c6216 (Foundation`_NSSetIntValueAndNotify)
     */
   
}
@end

你可能感兴趣的:(iOS底层(二)_KVO原理)