键-值观察是一种机制,允许将对象更更改通知其它对象的特定属性
项目中有一个Person类,类里有个name属性
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
1.添加观察
@property (nonatomic, strong) Person *p;
self.p = [[Person alloc] init];
/*addObserver 观察者
forKeyPath 被监听的属性值
options 监听改变前的值还是改变后的值
context 可以传参数值
为myPeople类对象添加一个观察者,监听"name"属性的变化**/
[self.p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
2.监听
/*利用KVO监听到对象的属性值发生改变时,调用下面方法
keyPath 哪个属性被改了
object 哪个对象的属性被改了
change 改成了什么样子
context 添加监听时context的参数值**/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
NSLog(@"keyPath是%@,object是%@,change是%@,context是%@",keyPath,object,change,context);
}
3.更改
/*对象属性值改变,两种改变方式**/
1.self.p.name = @"111";
2.[self.p setValue:@"111" forKey:@"name"];
4.移除
- (void)delloc{
[self.p removeObserver:self forKeyPath:@"name" context:nil];
}
原理
1.只有setter方法能够进行观察
2.是对动态生成的子类进行的监听,NSKVONotifying_Person是Person类的动态子类
1.只有setter方法能够进行观察,所以KVO的本质是重写了Set方法
@interface Person : NSObject{
@public
NSString *body;
}
1.添加监听,但是当值改变时,并不会被监听到
self.p = [[Person alloc] init];
[self.p addObserver:self forKeyPath:@"body" options:NSKeyValueObservingOptionNew context:NULL];
self.p->body = @"111";
2.是对动态生成的子类进行的监听,NSKVONotifying_Person是Person类的动态子类
当类对象初始化后,打印类名为Person类
当添加观察者之后,打印类名为NSKVONotifying_Person类
我们可以通过classList方法打印子类会发现, NSKVONotifying_Person为Person的子类
//获取指定类的子类方法
#include <objc/runtime.h>
- (NSArray *)findSubClass:(Class)defaultClass{
//注册类的总数
int count = objc_getClassList(NULL,0);
//创建一个数组,其中包含给定对象
NSMutableArray * array = [NSMutableArray arrayWithObject:defaultClass];
//获取所有已注册的类
Class *classes = (Class *)malloc(sizeof(Class) * count);
objc_getClassList(classes, count);
//遍历
for (int i = 0; i < count; i++) {
if (defaultClass == class_getSuperclass(classes[i])) {
[array addObject:classes[I]];
}
}
free(classes);
return array;
}
通过阅读源码可以看到,KVO是NSObject的一个分类,我们可以仿写一个
首先捋一下思路
1.先判断是否有set方法,如果没有抛出异常,如果有则2
2.动态生成子类
创建类并添加set方法,改变isa指向
3.消息转发
#import <Foundation/Foundation.h>
@interface NSObject (KCKVO)
/**
KCKVO 添加观察
@param observer 观察者
@param keyPath 观察的键
*/
- (void)kc_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
#import "NSObject+KCKVO.h"
#import <objc/message.h>
static NSString *const kKCKVOPrefix = @"KCKOV_";
@implementation NSObject (KCKVO)
/**
observer 观察者 keyPath 观察的键
*/
- (void)kc_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
//1判断是否存在于 keyPath是否存在 利用setter方法验证
// setName
SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));//NSSelectorFromString 动态加载实例方法。
Class superClass = object_getClass(self);
Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);//class_getInstanceMethod 得到类的实例方法
if (!setterMethod) {//如果没有则抛出异常
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%@:not this setter method",self] userInfo:nil];
}
//2:动态创建类 CCKVO_A
NSString * superClassName = NSStringFromClass(superClass);
Class newClass;
if (![superClassName hasPrefix:kKCKVOPrefix]) {
//创建类 并替换父类
newClass = [self creatClassFromSuperName:superClassName];
object_setClass(self, newClass);
}
//3:添加setter方法 注意这个时候的self==>>>子类
if (![self hasSeletor:setterSeletor]) {
const char *types = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSeletor, (IMP)CCKV0_Setter, types);
}
}
/**
通过父类创建子类
*/
- (Class)creatClassFromSuperName:(NSString *)superName{
//创建类
/**
动态创建类
1:父类对象
2:新类的名字
3:为新类开辟的内存空间
*/
Class superClass = NSClassFromString(superName);
NSString *newClassName = [kKCKVOPrefix stringByAppendingString:superName];
Class newClass = NSClassFromString(newClassName);
if (newClass) { return newClass;}
newClass = objc_allocateClassPair(superClass, newClassName.UTF8String, 0);
//添加class方法
//获取监听对象的class方法 取代class的实现方法---重写
/**
为一个类添加方法
1:类
2:响应的函数,方法名
3:指针IMP 即 implementation ,表示由编译器生成的、指向实现方法的指针。也就是说,这个指针指向的方法就是我们要添加的方法
4:类型:代表一个函数的返回值,参数
*/
Method classMethod = class_getClassMethod(superClass, @selector(class));
const char *types = method_getTypeEncoding(classMethod);
class_addMethod(newClass, @selector(class), (IMP)CCKVO_Class, types);
//注册类
objc_registerClassPair(newClass);
return newClass;
}
/**
判断是否存在该方法
*/
- (BOOL)hasSeletor:(SEL)selector{
Class observedClass = object_getClass(self);
unsigned int methodCount = 0;
//得到一堆方法的名字列表 //class_copyIvarList 实例变量 //class_copyPropertyList 得到所有属性名字
Method *methodList = class_copyMethodList(observedClass, &methodCount);
for (int i = 0; i<methodCount; i++) {
SEL sel = method_getName(methodList[i]);
if (selector == sel) {
free(methodList);
return YES;
}
}
free(methodList);
return NO;
}
#pragma mark - 函数区域(static修饰函数用于限制函数只能在当前文件中调用)
#pragma mark - 从get方法获取set方法的名称 name ===>>> setName:
static NSString * setterForGetter(NSString *getter){
if (getter.length <= 0) { return nil; }
NSString *firstString = [[getter substringToIndex:1] uppercaseString];
NSString *leaveString = [getter substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}
#pragma mark - 从set方法获取getter方法的名称 setName:===> name
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];
getter = [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
return getter;
}
#pragma mark - CCKVO_Class 新类class所指向的函数实现
static Class CCKVO_Class(id self){
return class_getSuperclass(object_getClass(self));
}
//_cmd在Objective-C的方法中表示当前方法的selector,正如同self表示当前方法调用的对象实例。
#pragma mark - 子类添加的setter方法
static void CCKV0_Setter(id self,SEL _cmd,id newValue){
NSString *setterName = NSStringFromSelector(_cmd);
NSString *getterName = getterForSetter(setterName);
if (!getterName) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%@ not instance getter",self] userInfo:nil];
return;
}
id oldValue = [self valueForKey:getterName];
[self willChangeValueForKey:getterName];
//消息转发
void (*KC_OBJCSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
struct objc_super KCSuperSt = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
KC_OBJCSendSuper(&KCSuperSt,_cmd,newValue);
[self didChangeValueForKey:getterName];
NSLog(@"%@",newValue);
}
@end













网友评论