阅读 166

OC底层原理探索之KVO原理分析上

什么是KVO

官方文档 Key-value observing是一种允许对象在其他对象的指定属性发生变化时被通知的机制。

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
复制代码

我们通常在最后一个参数context传递NULL,比如

[self.person addObserver:self forKeyPath:@"nick" options:NSKeyValueObservingOptionNew context:NULL];
复制代码

Context

A safer and more extensible approach is to use the context to ensure notifications you receive are destined for your observer and not a superclass.(更安全、更可扩展的方法是使用上下文来确保接收到的通知是发给观察者的,而不是超类) Listing 1 Creating context pointers

static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
复制代码

Listing 2 Registering the inspector as an observer of the balance and interestRate properties

- (void)registerAsObserverForAccount:(Account*)account {
    [account addObserver:self
              forKeyPath:@"balance"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                 context:PersonAccountBalanceContext];
 
    [account addObserver:self
              forKeyPath:@"interestRate"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                  context:PersonAccountInterestRateContext];
}
复制代码

移除观察者

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.(当被释放时,观察者不会自动移除自己。被观察的对象继续发送通知,无视观察者的状态。然而,与任何其他消息一样,发送到已释放对象的更改通知会触发内存访问异常。因此,您可以确保观察者在从记忆中消失之前将自己移走) ​

Automatic Change Notification自动

一对多的情况:比如下载视频的进度 writte和total都会变化

+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
    
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"downloadProgress"]) {
        NSArray *affectingKeys = @[@"totalData", @"writtenData"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}
复制代码

Manual Change Notification 手动

+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
    return NO;
}
复制代码
- (void)setNick:(NSString *)nick{
    [self willChangeValueForKey:@"nick"];
    _nick = nick;
    [self didChangeValueForKey:@"nick"];
}
复制代码

对可变数组的观察

上一节KVC有说明 The protocol defines three different proxy methods for collection object access, each with a key and a key path variant: mutableArrayValueForKey: and mutableArrayValueForKeyPath:These return a proxy object that behaves like an NSMutableArray object. mutableSetValueForKey: and mutableSetValueForKeyPath:These return a proxy object that behaves like an NSMutableSet object. mutableOrderedSetValueForKey: and mutableOrderedSetValueForKeyPath:These return a proxy object that behaves like an NSMutableOrderedSet object.

[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"1"];
复制代码

KVO底层原理

添加观察者时: image.png 移除观察者时: image.png 我们发现在添加了KVO观察者之后,self.person指向了一个NSKVONotifying_Person,在移除观察者的时候,isa又指回来了(但是创建的NSKVONotifying_Person类并没有被销毁)。我们可以猜测这个是在运行时动态生成。那么这个NSKVONotifying_PersonLGPerson有啥关系呢?

#pragma mark - 遍历类以及子类
- (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);
}
复制代码

我们在给Person添加观察者之前和之后,分别打印下当前的Person的子类;

    [self printClasses:[Person class]];
    [self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];
    [self printClasses:[Person class]];
复制代码

image.png 可以得到结论:当前的NSKVONotifying_PersonPerson的子类

NSKVONotifying_Person里面包含了什么

对于一个类来说就那么几个关键的东西:属性、方法、协议

#pragma mark - 遍历方法-ivar-property
- (void)printClassAllMethod:(Class)cls{
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(cls, &count);
    for (int i = 0; i<count; i++) {
        Method method = methodList[i];
        SEL sel = method_getName(method);
        IMP imp = class_getMethodImplementation(cls, sel);
        NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
    }
    free(methodList);
}
复制代码
[self printClassAllMethod:objc_getClass("NSKVONotifying_Person")];
复制代码

image.png 重写了这四个方法:setNickName - class - dealloc - _isKVOA

KVO观察的到底是成员变量还是属性 ivar VS Property

虽然NSKVONotifying_Person重写了set方法,但是我们还有有一个疑惑,观察者到底观察的是成员变量还是属性呢?继续验证

@interface Person : NSObject{
    @public
    NSString *name;
}
@property (nonatomic, copy) NSString *nickName;

@end
复制代码
[self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
复制代码

KVO回调

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@",change);
}
复制代码

打印输出: image.png 结论:并不会观察到成员变量。

setter方法监听

通过上面我们知道KVO实际上对set方法进行观察,set方法做了什么。使用LLDB调试 image.png 当观察到变更的时候就会hit,此时bt一下 image.pngtouchesBeginsetNickName之间系统调用了三个方法: 1.-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:] 2.-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] 3._NSSetObjectValueAndNotify 点击查看调用栈内的汇编代码大致可以得出一个流程: 在我们开始监听的时候,生成了一个NSKVONotifying_Person,同时生成了4个方法。当我们set赋值的时候,此时的set其实是NSKVONotifying_Person的set方法,此时在方法3中经过一些处理willChangeValueForKey:didChangeValueForKey:修改完之后回调通知就来了。

文章分类
iOS
文章标签