OC-KVO

2020-11-02  本文已影响0人  浪的出名

KVO是什么

KVO的使用

[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"change:%@",change);
}
- (void)dealloc
{
    [self.person removeObserver:self forKeyPath:@"name"];
}
// Call the accessor method.
[account setName:@"Savings"];

// Use setValue:forKey:.
[account setValue:@"Savings" forKey:@"name"];

// Use a key path, where 'account' is a kvc-compliant property of 'document'.
[document setValue:@"Savings" forKeyPath:@"account.name"];

// Use mutableArrayValueForKey: to retrieve a relationship proxy object.
Transaction *newTransaction = <#Create a new transaction for the account#>;
NSMutableArray *transactions = [account mutableArrayValueForKey:@"transactions"];
[transactions addObject:newTransaction];
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
    if ([key isEqualToString:@"name"]) {
        return NO;
    }else{
        return [super automaticallyNotifiesObserversForKey:key];
    }
}

- (void)setName:(NSString *)name
{
    if (name != _name) {
        [self willChangeValueForKey:@"name"];
        _name = name;
        [self didChangeValueForKey:@"name"];
    }
}
static void *PersonNameContext = &PersonNameContext;
static void *PersonNickNameContext = &PersonNickNameContext;

[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:PersonNameContext];  
[self.person addObserver:self forKeyPath:@"nickName" options:NSKeyValueObservingOptionNew context:PersonNickNameContext];
When removing an observer, keep several points in mind:

Asking to be removed as an observer if not already registered as one results in an NSRangeException. You either call removeObserver:forKeyPath:context: exactly once for the corresponding call to addObserver:forKeyPath:options:context:, or if that is not feasible in your app, place the removeObserver:forKeyPath:context: call inside a try/catch block to process the potential exception.
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.
The protocol offers no way to ask an object if it is an observer or being observed. Construct your code to avoid release related errors. A typical pattern is to register as an observer during the observer’s initialization (for example in init or viewDidLoad) and unregister during deallocation (usually in dealloc), ensuring properly paired and ordered add and remove messages, and that the observer is unregistered before it is freed from memory.

1,要求被移除为观察者(如果尚未注册为观察者)会导致NSRangeException。您可以对removeObserver:forKeyPath:context:进行一次调用,以对应对addObserver:forKeyPath:options:context:的调用,或者,如果在您的应用中不可行,则将removeObserver:forKeyPath:context:调用在try / catch块内处理潜在的异常。
2,释放后,观察者不会自动将其自身移除。被观察对象继续发送通知,而忽略了观察者的状态。但是,与发送到已释放对象的任何其他消息一样,更改通知会触发内存访问异常。因此,您可以确保观察者在从内存中消失之前将自己删除。
3,该协议无法询问对象是观察者还是被观察者。构造代码以避免发布相关的错误。一种典型的模式是在观察者初始化期间(例如,在init或viewDidLoad中)注册为观察者,并在释放过程中(通常在dealloc中)注销,以确保成对和有序地添加和删除消息,并确保观察者在注册之前被取消注册。从内存中释放出来。

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

+ (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];
}
 [[self.person mutableArrayValueForKeyPath:@"mArray"] addObject:@"111"];

KVO的实现原理

自动键值观察是使用称为isa-swizzling的技术实现的。
顾名思义,isa指针指向维护分配表的对象的类。 该分派表实质上包含指向该类实现的方法的指针以及其他数据。
当为对象的属性注册观察者时,将修改观察对象的isa指针,指向中间类而不是真实类。 结果,isa指针的值不一定反映实例的实际类。
您永远不应依靠isa指针来确定类成员身份。 相反,您应该使用class方法来确定对象实例的类。

Key-Value Observing Implementation Details

Automatic key-value observing is implemented using a technique called isa-swizzling.

The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.

When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.

You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.
(lldb) po self.p1
<Person: 0x60000033c620>
(lldb) po self.p2
<Person: 0x60000033c600>
(lldb) po self.p1->isa
NSKVONotifying_Person
(lldb) po self.p2->isa
Person
- (void)classInfo:(id)obj
{
    Class objClass = object_getClass(obj);
    Class superClass = class_getSuperclass(objClass);
    NSLog(@"class:%@----superClass:%@",objClass,superClass);
    unsigned int outCount;
    Method *methods = class_copyMethodList(objClass, &outCount);
    for (int i = 0; i < outCount; i ++) {
        Method me = methods[i];
        NSLog(@"method:%@",NSStringFromSelector(method_getName(me)));
    }
    free(methods);
}
KVO[8996:3547641] class:NSKVONotifying_Person----superClass:Person
KVO[8996:3547641] method:setAge:
KVO[8996:3547641] method:class
KVO[8996:3547641] method:dealloc
KVO[8996:3547641] method:_isKVOA
KVO[8996:3547641] class:Person----superClass:NSObject
KVO[8996:3547641] method:age
KVO[8996:3547641] method:setAge:
- (void)setAge:(int)age{
    //调用Foundationf框架中的_NSSetIntValueAndNotify方法
    [self _NSSetIntValueAndNotify];
}

- (void)_NSSetIntValueAndNotify{
    //将要修改age的值
    [self willChangeValueForKey:@"age"];
    //调用父类的setAge方法去修改age的值
    [super setAge:age];
    //完成修改age的值,并且执行observeValueForKeyPath方法
    [self didChangeValueForKey:@"age"];
}
- (Class)class{
    return [Person class];
}

还原NSKVONotifying_Person对象的内部构造

@interface NSKVONotifying_Person : Person

@end

@implementation NSKVONotifying_Person

- (void)setAge:(int)age{
    //调用Foundationf框架中的_NSSetIntValueAndNotify方法
    [self _NSSetIntValueAndNotify];
}

- (void)_NSSetIntValueAndNotify{
    //将要修改age的值
    [self willChangeValueForKey:@"age"];
    //调用父类的setAge方法去修改age的值
    [super setAge:age];
    //完成修改age的值,并且执行observeValueForKeyPath方法
    [self didChangeValueForKey:@"age"];
}

- (void)didChangeValueForKey:(NSString *)key{
    //触发observeValueForKeyPath方法
    [self observeValueForKeyPath:@"age" ofObject:self change:nil context:nil];
}

- (void)dealloc{
    //释放操作
}

- (Class)class{
    return [Person class];
}

- (BOOL)_isKVOA{
    return YES;
}

@end

KVO总结

上一篇 下一篇

猜你喜欢

热点阅读