1. 程式人生 > >KVO實現原理

KVO實現原理

KVO的全稱是Key-Value Observing,俗稱“鍵值監聽”,可以用於監聽某個物件屬性值的改變。

下面來探討一下KVO的本質

1.新建一個XZPerson類

#import <Foundation/Foundation.h>

@interface XZPerson : NSObject

 @property(nonatomic,assign)int age;

@end


#import "XZPerson.h"

@implementation XZPerson

- (void)setAge:(int)age{

    _age = age;

}

@end


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

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

@implementation ViewController
-(void)printMethodNameOFclass:(Class)class{
    unsigned int count;
    //獲取方法陣列
    Method *methodList = class_copyMethodList(class, &count);
    //儲存方法名
    NSMutableString*methodNames = [NSMutableString string];
    for (int i = 0; i<count; i++) {
        //獲取方法
        Method meth = methodList[i];
        // 獲取方法名
        NSString * selname =NSStringFromSelector(method_getName(meth));
        [methodNames appendFormat:@"%@,",selname];
    }
    free(methodList);
    NSLog(@"%@-%@",class,methodNames);
}
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person1 = [[XZPerson alloc] init];
    self.person1.age = 10;
    
    self.person2 = [[XZPerson alloc] init];
    self.person2.age = 10;
    NSLog(@"person1新增kvo之前-%@-%p-%p",object_getClass(self.person1),[self.person1 methodForSelector:@selector(setAge:)],
          [self.person2 methodForSelector:@selector(setAge:)]);
    //獲取方法實現IMP
    [self.person1 methodForSelector:@selector(setAge:)];
    [self.person2 methodForSelector:@selector(setAge:)];
    
    //給person物件新增觀察者
    [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
//    //獲取方法實現IMP
    [self.person1 methodForSelector:@selector(setAge:)];
    [self.person2 methodForSelector:@selector(setAge:)];
    
    NSLog(@"person1新增kvo之後-%@-%p-%p",object_getClass(self.person1),[self.person1 methodForSelector:@selector(setAge:)],
          [self.person2 methodForSelector:@selector(setAge:)]);
    NSLog(@"新增kvo後的person的父類%@",[object_getClass(self.person1) superclass]);
    ///NSKVONotifying_XZPerson
    //p IMP(0x100773f8e)
    //[self.person1 setAge:100];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.person1.age = 20;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@的屬性%@的值由%@變成了%@",object,keyPath,change[@"old"],change[@"new"]);
}

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

控制檯列印結果如下:

2018-10-10 10:27:39.808750+0800 KVOTEST[1067:45659] person1新增kvo之前-XZPerson-0x1022fe4b0-0x1022fe4b0

2018-10-10 10:27:39.809313+0800 KVOTEST[1067:45659] person1新增kvo之後-NSKVONotifying_XZPerson-0x1026a3f8e-0x1022fe4b0

2018-10-10 10:27:39.809650+0800 KVOTEST[1067:45659] 新增kvo後的person的父類XZPerson

(lldb) p IMP(0x1022fe4b0)

(IMP) $0 = 0x00000001022fe4b0 (KVOTEST`-[XZPerson setAge:] at XZPerson.m:14)

(lldb) p IMP(0x1026a3f8e)

(IMP) $1 = 0x00000001026a3f8e (Foundation`_NSSetIntValueAndNotify)

(lldb) 

由此可以看出:person1 在新增kvo監聽之後,生成了一個  NSKVONotifying_XZPerson的類,其父類為XZPerson;

使用lldb檢視person1的setAge方法的實現,發現新增KVO之後的person1在setAge方法內部呼叫了 Foundation 的`_NSSetIntValueAndNotify方法,這裡涉及到Foundation框架的反編譯。此方法的虛擬碼如下:

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

總結:

  • iOS用什麼方式實現對一個物件的KVO(KVO的本質是什麼?)
  • 利用RuntimeAPI動態生成一個子類,並且讓instance物件的isa指向這個全新的子類
  • 當修改instance物件的屬性時,會呼叫Foundation_NSSetXXXValueAndNotify函式
  1. willChangeValueForKey:
  2. 父類原來的setter
  3. didChangeValueForKey:
  •  內部會觸發監聽器(Oberser)的監聽方法( observeValueForKeyPath:ofObject:change:context:

2.如何手動觸發KVO?

手動呼叫willChangeValueForKey:didChangeValueForKey:方法