ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

iOS开发底层之KVO探索上 - 17

2021-09-10 18:03:46  阅读:165  来源: 互联网

标签:17 KVO self iOS person context void 属性


文章目录


前言

本章内容主要是围绕KVO进行探索,从KVO的介绍 -》KVO的坑点 -》 KVO的大致流程 -》KVO的自定义实现 -》优秀的KVO封装库介绍。

一、KVO是什么?

KVO的全称为:Key-Value Observing,“键值监听”。

主要作用为:监听某个对象属性值的改变,继而进行对应的业务处理。

简单使用的代码:

 self.person = [[MYPerson alloc] init];
 
    // 属性监听
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

}

// KVO的释放
-(void)dealloc {
    [self.person removeObserver:self  forKeyPath:@"likeSome"];
}

// 方便调试
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    int b = 100 +  (arc4random() % 101);
    NSString *tt = [NSString stringWithFormat:@"value: %d",b];
    self.person.name = tt;
}

// kvo值改变回调
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    NSLog(@"--keypath = 【%@】 -change = 【%@】", keyPath,change);
}

二、KVO注意项

1. KVO中的Context有什么作用?

根据官方文档 : KVO官方说明
上面介绍到: Context起到的作用就是一个标识符, 主要是为了监听对象更加安全,万一监听的属性路径一致,导致无法区分监听场景而诞生的,下面展示下官方的一段源码:


static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;

- (void)registerAsObserverForAccount:(Account*)account {
	//不同的context的监听
    [account addObserver:self
              forKeyPath:@"balance"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                 context:PersonAccountBalanceContext];
 
    [account addObserver:self
              forKeyPath:@"interestRate"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                  context:PersonAccountInterestRateContext];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
 
    if (context == PersonAccountBalanceContext) {
        // Do something with the balance…
 
    } else if (context == PersonAccountInterestRateContext) {
        // Do something with the interest rate…
    } else {
        // Any unrecognized context must belong to super
    }
}

2. 忘记移除观察者,而造成程序的崩溃

官方文档中,有这么一句话:

An observer does not automatically remove itself when deallocated. The observed object continues to send notifications, oblivious to the state of the observer. However, a change notification, like any other message, sent to a released object, triggers a memory access exception. You therefore ensure that observers remove themselves before disappearing from memory.

大概的意思就是: 观察者不会在dealloc的时候自动移除,而观察对象后续的操作就算观察者依附的对象已经dealloc也会继续发送消息, 这样就会导致程序崩溃。 所以我们需要手动移除观察者, 后面有更加优化的方案解决这个问题。 请继续读下去。 

3. 控制某些属性不能使用KVO

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqual: @"name"]) {
        return  false;
    } else {
        return true;
    }
}

4. 一对多的观察。

是否有遇到过这种场景: A属性的值, 是取决于另外两个属性B、C之间的计算才能得出, 这个时候对A属性进行观察,怎么办呢?

官方举例: fullName = firstName + lastName
当firstName 或者 lastName ,任意一个属性发生改变,必然会影响到 fullName。
那么就需要用到下面这个方法:

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"fullName"]) {
        NSArray *affectingKeys = @[@"lastName", @"firstName"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}

// 写法二:
+ (NSSet *)keyPathsForValuesAffectingFullName {
    return [NSSet setWithObjects:@"lastName", @"firstName", nil];
}

// fullName的get方法
- (NSString *)fullName {
    return [NSString stringWithFormat:@"%@ %@",firstName, lastName];
}

// 其他的流程和前面写的一样只要去监听 fullName
[self.person addObserver:self forKeyPath:@"fullName" options:NSKeyValueObservingOptionNew context:nil];

5. 对可变数组的KVO。

通过mutableArrayValueForKey 来实现, 直接上用法。

[self.person addObserver:self forKeyPath:@"dateArray" options:NSKeyValueObservingOptionNew context:NULL];

self.person.dateArray = [NSMutableArray array];

[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"hello"];

// 通过 mutableArrayValueForKey ,kvo是基于KVC之上

三、KVO的流程和原理

自动键值观察:是通过isa-swizzling的技术实现。

  1. 在原有类的基础上新增一个派生类 NSKVONotifying_ 开头。
    可通过代码来调试出来:
    self.person = [[MYPerson alloc] init];
    // 没有使用KVO之前,打印所有类。
    [self printClasses:[self.person class]];
    // 属性
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    // 使用KVO再打印所有类。 
    [self printClasses:[self.person class]];

// 利用runtime打印所有的类方法。 
- (void)printClasses:(Class)cls {
    // 注册类的总数
    int count = objc_getClassList(NULL, 0);
    // 创建一个数组, 其中包含给定对象
    NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
    // 获取所有已注册的类
    Class* classes = (Class *)malloc(sizeof(Class) *count);
    objc_getClassList(classes, count);
    for (int i = 0; i<count; i++) {
        if (cls == class_getSuperclass(classes[i])) {
            [mArray addObject:classes[i]];
        }
    }
    free(classes);
    NSLog(@"classes = %@", mArray);
}
    
    // 打印结果
    2021-09-10 17:31:51.644193+0800 001-内存对齐原则[18934:903108] classes = (
    MYPerson
)
	2021-09-10 17:31:51.665579+0800 001-内存对齐原则[18934:903108] classes = (
    MYPerson,
    "NSKVONotifying_MYPerson"
)

// 很清楚的看见,当使用kvo后, 会新增加一个NSKVONotifying_MYPerson 类。 
  1. 关于在移除观察者的时候, 会不会吧isa指向会原类。
    我们在移除观察者代码前后,打上断点去观察, 就会发现,isa会指向原类 。

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

// lldb调试 结果
(lldb) p object_getClassName(self.person)
(const char * _Nonnull) $0 = 0x000000028232f380 "NSKVONotifying_MYPerson"
(lldb) p object_getClassName(self.person)
(const char * _Nonnull) $1 = 0x0000000100907618 "MYPerson"

整体流程为:
当前类设置观察者后, 会派生一个子类(名称开头为NSKVONotifying_), 这个派生类和原来的类一模一样, 并且会在原来的类之上增加对属性的 setter方法监听,也就是 willChange , didChange。 当这些被观察的属性发生改变的时候,就会给所有的观察者,发送指令, 这个属性改变了,你该干啥就要干啥了。 这些设置好后,就把原类的isa指向这个派生的类, 最后在原类移除观察者的时候,就会将isa还原回来。

标签:17,KVO,self,iOS,person,context,void,属性
来源: https://blog.csdn.net/zhonggaorong/article/details/120216237

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有