iOS 知识点iOS 技能

KVC & KVO 浅析

2020-06-30  本文已影响0人  三国韩信

KVC部分

KVC的设值过程.png
#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的原理:

  1. 生产了一个动态子类。NSKVONotifying_xxx,并把对象的isa指针指向新生成的子类。
  2. 观察的是属性,实例变量是不能KVO观察的(要有setter方法)
  3. 新生产的动态子类重写父类的方法有-(Class)class、 -(void)dealloc、-(BooL)_isKVO方法,另外还有观察的属性的setter方法。重写这些方法是为了不给开发者知道中间新生产了一个动态子类。
  4. 移除观察者后,对象的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];
}
上一篇 下一篇

猜你喜欢

热点阅读