KVC & KVO 浅析
2020-06-30 本文已影响0人
三国韩信
KVC部分
- KVC的设值过程:( 即 [person setValue:@"beckhams" forKey:@"name"];这句代码的调用流程。)
- 判断setName或_setName或setIsName 方法是否存在。如果任意一个存在,则会调用该方法。
- 如果没有1,则判断 accessInstanceVariablesDirectly函数是否返回YES。如果返回YES,则会去找有木有相似的实例变量_name、_isName、name、isName。如果有任一个相似的实例变量,则会直接给该实例变量赋值。
- 条件1或条件2不满足,则报错没找到 setValue:forUndefinedKey。
- KVC的取值过程:( 即 [person ValueForKey:@"name"];这句代码的调用流程。)
- 先找getter的方法。判断是否有类似的(getName、name、isName、_name)getter方法。
- 判断 accessInstanceVariablesDirectly函数是否返回YES。如果返回YES,则会去找有木有相似的实例变量_name、_isName、name、isName。如果有任一个相似的实例变量,则会直接获取这实例变量的值返回。
- 如果上面条件不满足,判断是否是NSArray或NSSet类型(集合类型的取值过程不一样的。)
- 如果找不到上面的条件,则报错没找到 setValue:forUndefinedKey。
#import <Foundation/Foundation.h>
@interface Test: NSObject {
NSString *_name;
}
@end
@implementation Test
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}
/*
这里设置成为NO之后,会关闭了kvc。然后会来到下面的valueForUndefinedKey方法。
那么如果我们只是想关闭部分属性的kvc呢?
那我们可以把accessInstanceVariablesDirectly返回NO,所有的kvc都被禁用后,在valueForUndefinedKey或者setValue:forUndefinedKey这2个方法里操作想要有kvc的那些属性。
*/
- (id)valueForUndefinedKey:(NSString *)key {
NSLog(@"出现异常,该key不存在%@",key);
return nil;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"出现异常,该key不存在%@", key);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
//生成对象
Test *obj = [[Test alloc] init];
//通过KVC赋值name
[obj setValue:@"xiaoming" forKey:@"name"];
//通过KVC取值name打印
NSLog(@"obj的名字是%@", [obj valueForKey:@"name"]);
}
return 0;
}
/*
关于集合类型kvc的操作,NSArray和NSSet的情况。
比如 NSArray *array = [person valueForKey:@"pens"]; 此时person里没有pens这个属性的。
那么这句代码的流程是啥,如何保证它不崩溃。
*/
/*
当person类里有实现这2个方法的时候,上面的kvc就不会奔溃报错。
countOf<key>、objectIn<key>AtIndex:
*/
//MARK: - 不可变数组NSArray
- (NSUInteger)countOfPens{ // 个数
NSLog(@"%s",__func__);
return [self.arr count];
}
- (id)objectInPensAtIndex:(NSUInteger)index { // 获取值
NSLog(@"%s",__func__);
return [NSString stringWithFormat:@"pens %lu", index];
}
//MARK: - 可变数组NSMutableArray (即这句代码NSMutableArray *array2 = [person mutableArrayValueForKey:@"namesArrM"];)
-(void)insertNamesArrM:(NSArray *)array atIndexes:(NSIndexSet *)indexes{
//插入一个数组对象
NSLog(@"%s",__func__);
}
-(void)insertObject:(NSString *)object inNamesArrMAtIndex:(NSUInteger)index{
//插入一个对象
NSLog(@"%s",__func__);
}
-(void)removeNamesArrMAtIndexes:(NSIndexSet *)indexes{
//移除一个数组
NSLog(@"%s",__func__);
}
-(void)removeObjectFromNamesArrMAtIndex:(NSUInteger)index{
//移除一个对象
NSLog(@"%s",__func__);
}
-(void)replaceNamesArrMAtIndexes:(NSIndexSet *)indexes withNamesArrM:(NSArray *)array{
//根据下标,替换数组
NSLog(@"%s",__func__);
}
-(void)replaceObjectInNamesArrMAtIndex:(NSUInteger)index withObject:(id)object{
//根据下标替换一个对象
NSLog(@"%s",__func__);
}
// 这是关于set集合的kvc的取值的情况。比如 NSSet *set = [person valueForKey:@"books"]; 对books这个key做kvc取值。
// 个数
- (NSUInteger)countOfBooks{
NSLog(@"%s",__func__);
return [self.set count];
}
// 是否包含这个成员对象
- (id)memberOfBooks:(id)object {
NSLog(@"%s",__func__);
return [self.set containsObject:object] ? object : nil;
}
// 迭代器
- (id)enumeratorOfBooks {
// objectEnumerator
NSLog(@"来了 迭代编译");
return [self.arr reverseObjectEnumerator];
}
//MARK: - NSMutableOrderedSet
//插入单个对象
-(void)insertObject:(NSString *)object inOrderedSetMAtIndex:(NSUInteger)index{
NSLog(@"%s",__func__);
}
//插入多个对象
-(void)insertOrderedSetM:(NSArray *)array atIndexes:(NSIndexSet *)indexes{
NSLog(@"%s",__func__);
}
//替换单个对象
-(void)replaceObjectInOrderedSetMAtIndex:(NSUInteger)index withObject:(id)object{
NSLog(@"%s",__func__);
}
//替换多个对象(set)
-(void)replaceOrderedSetMAtIndexes:(NSIndexSet *)indexes withOrderedSetM:(NSArray *)array{
NSLog(@"%s",__func__);
}
//移除单个对象
-(void)removeObjectFromOrderedSetMAtIndex:(NSUInteger)index{
NSLog(@"%s",__func__);
}
//移除多个对象
-(void)removeOrderedSetMAtIndexes:(NSIndexSet *)indexes{
NSLog(@"%s",__func__);
}
//MARK: - NSMutablSet
//添加多个对象、并集
-(void)addNamesSetM:(NSSet *)objects{
NSLog(@"%s",__func__);
}
//添加单个对象
-(void)addNamesSetMObject:(NSString *)object{
NSLog(@"%s",__func__);
}
//移除多个对象
-(void)removeNamesSetM:(NSSet *)objects{
NSLog(@"%s",__func__);
}
//移除单个对象
-(void)removeNamesSetMObject:(NSString *)object{
NSLog(@"%s",__func__);
}
//交集
-(void)intersectNamesSetM:(NSSet *)objects{
NSLog(@"%s",__func__);
}
KVC的取值过程.png
注意:KVC访问非对象类型的属性(某个属性不是一个obj对象,有可能是int、float、结构体等)
// KVC - 访问非对象属性
// person类有个属性threeFloats是一个结构体,它的kvc赋值就必须包装成一个NSValue对象。
// 其他非对象类型的,比如int、float 可以包装成NSValue的子类NSNumber对象。
typedef struct {
float x, y, z;
} ThreeFloats;
ThreeFloats floats = {1., 2., 3.};
NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[person setValue:value forKey:@"threeFloats"];
NSValue *reslut = [person valueForKey:@"threeFloats"];
NSLog(@"%@",reslut);
// 取值
ThreeFloats th;
[reslut getValue:&th] ;
NSLog(@"%f - %f - %f",th.x,th.y,th.z);
KVO部分
禁止某个属性KVO自动观察
手动通知提供了更自由的方式去决定什么时间,什么方式去通知观察者。想要使用手动通知必须实现 automaticallyNotifiesObserversForKey: (或者 automaticallyNotifiesObserversOf )方法。在一个类中同时使用自动和手动通知是可行的。对于想要手动通知的属性,可以根据它的keyPath返回NO,而其对于其他位置的keyPath,要返回父类的这个方法。
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"name"]) {
return NO;
} else {
return [super automaticallyNotifiesObserversForKey:key];
}
}
// 或者
+ (BOOL)automaticallyNotifiesObserversOfName {
return NO;
}
而当自动观察关闭后,手动的去观察某个对象一样可以出发observeValueForKeyPath的回调。
[self.p willChangeValueForKey:@"nickName"];
self.p.nickName = @"my";
[self.p didChangeValueForKey:@"nickName"];
KVO的核心就是isa swizzling。
KVO的原理:
- 生产了一个动态子类。NSKVONotifying_xxx,并把对象的isa指针指向新生成的子类。
- 观察的是属性,实例变量是不能KVO观察的(要有setter方法)
- 新生产的动态子类重写父类的方法有-(Class)class、 -(void)dealloc、-(BooL)_isKVO方法,另外还有观察的属性的setter方法。重写这些方法是为了不给开发者知道中间新生产了一个动态子类。
- 移除观察者后,对象的isa要指向原来的类。但是生成的动态子类NSKVONotifying_xxx会一直在内存中缓存着。
简易的KVO的自定义,主要用于帮助理解KVO中isa swizzling的变化。
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (LGKVO)
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
#import "NSObject+LGKVO.h"
#import <objc/message.h>
static NSString *const kLGKVOPrefix = @"LGKVONotifying_";
static NSString *const kLGKVOAssiociateKey = @"kLGKVO_AssiociateKey";
@implementation NSObject (LGKVO)
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
// 1: 验证是否存在setter方法 : 不让实例进来
[self judgeSetterMethodFromKeyPath:keyPath];
// 2: 动态生成子类
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3: isa的指向 : LGKVONotifying_LGPerson
object_setClass(self, newClass);
// 4: 保存观察者
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
// 指回给父类
Class superClass = [self class];
object_setClass(self, superClass);
}
#pragma mark - 验证是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
Class superClass = object_getClass(self);
SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
if (!setterMethod) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"老铁没有当前%@的setter",keyPath] userInfo:nil];
}
}
#pragma mark -
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
// 防止重复创建生成新类
if (newClass) return newClass;
/**
* 如果内存不存在,创建生成
* 参数一: 父类
* 参数二: 新类的名字
* 参数三: 新类的开辟的额外空间
*/
// 2.1 : 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2 : 注册类
objc_registerClassPair(newClass);
// 2.3.1 : 添加class : class的指向是LGPerson
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
// 2.3.2 : 添加setter
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes);
return newClass;
}
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"来了:%@",newValue);
// 4: 消息转发 : 转发给父类
// 改变父类的值 --- 可以强制类型转换
void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
//objc_msgSendSuper(&superStruct,_cmd,newValue)
lg_msgSendSuper(&superStruct,_cmd,newValue);
// 既然观察到了,下一步不就是回调 -- 让我们的观察者调用
// - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
// 1: 拿到观察者
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
// 2: 消息发送给观察者
SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
}
Class lg_class(id self,SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
#pragma mark - 从get方法获取set方法的名称 key ===>>> setKey:
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方法的名称 set<Key>:===> key
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];
return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}