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函式
- willChangeValueForKey:
- 父類原來的setter
- didChangeValueForKey:
- 內部會觸發監聽器(Oberser)的監聽方法( observeValueForKeyPath:ofObject:change:context:)
2.如何手動觸發KVO?
手動呼叫willChangeValueForKey:和didChangeValueForKey:方法