添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
道上混的稀饭  ·  KVO·  2 周前    · 
细心的警车  ·  KVC 和 KVO - 掘金·  1 年前    · 
彷徨的充电器  ·  创作你的创作 - 简书·  1 年前    · 
憨厚的大脸猫  ·  Guide to KVO in Swift ...·  1 年前    · 
光明磊落的路灯  ·  Bat files don't ...·  6 月前    · 
憨厚的登山鞋  ·  怪客先生-ops8小说·  6 月前    · 
// 定义一个简单的类
@interface AYPerson : NSObject
@property(nonatomic, assign) NSInteger age;
@implementation AYPerson
AYPerson *p = [[AYPerson alloc] init];
 // 添加观察对象
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[p addObserver:self forKeyPath:@"age" options:options context:nil];
// 实现代理方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    NSLog(@"observeValueForKeyPath:%@ ofObject:%@ change:%@ context:%@", keyPath, object, change, context);
// 不需要使用的时候需要移除监听
- (void)dealloc
    [self.p1 removeObserver:self forKeyPath:@"age"];

KVO 的底层实现

 AYPerson *p = [[AYPerson alloc] init];
 p.age = 3;
 self.p1 = p;
 AYPerson *p2 = [[AYPerson alloc] init];
 p2.age = 3;
 self.p2 = p2;
 NSLog(@"before------ %@:%p, %@:%p",object_getClass(p),
       object_getClass(p),
       object_getClass(p2),
       object_getClass(p2));
 NSLog(@"meta------ %@:%p, %@:%p", object_getClas(object_getClass(p)),
       object_getClass(object_getClass(p)),
       object_getClass(object_getClass(p2)),
       object_getClass(object_getClass(p2)));
  before------ AYPerson:0x1042090b8, AYPerson:0x1042090b8
  meta------ AYPerson:0x104209090, AYPerson:0x104209090
 // 添加观察对象
 NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
 [p addObserver:self forKeyPath:@"age"options:options context:nil];
 NSLog(@"after------ %@:%p, %@:%p", object_getClas(p),
       object_getClass(p),
       object_getClass(p2),
       object_getClass(p2));
 NSLog(@"meta------ %@:%p, %@:%p", object_getClas(object_getClass(p)),
       object_getClass(object_getClass(p)),
       object_getClass(object_getClass(p2)),
       object_getClass(object_getClass(p2)));
  after------ NSKVONotifying_AYPerson:0x283d814d0, AYPerson:0x1042090b8
  meta------ NSKVONotifying_AYPerson:0x283d81560, AYPerson:0x104209090

给属性添加观察者之后,源类(AYPerson)会生成一个派生类(NSKVONotifying_AYPerson)这个类是动态生成的,使用了runtime方法

/* Adding Classes */ * Creates a new class and metaclass. Class objc_allocateClassPair(Class superclass, const char * name, size_t extraBytes); * Registers a class that was allocated using \c objc_allocateClassPair. void objc_registerClassPair(Class cls);

主动添加一个类 NSKVONotifying_AYPerson,这个时候会添加观察者失败

 [general] KVO failed to allocate class pair for name NSKVONotifying_AYPerson, automatic key-value observing will not work for this class

在监听响应方法中(observeValueForKeyPath)打断点

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 * frame #0: 0x0000000104e94062 MYKVO`-[ViewController observeValueForKeyPath:ofObject:change:context:](self=0x00007fc066704100, _cmd="observeValueForKeyPath:ofObject:change:context:", keyPath=@"age", object=0x0000600001dc4670, change=0x0000600000ab9600, context=0x0000000000000000) at ViewController.m:67:74 frame #1: 0x00007fff2564f735 Foundation`NSKeyValueNotifyObserver + 329 frame #2: 0x00007fff25652e4f Foundation`NSKeyValueDidChange + 499 frame #3: 0x00007fff25652752 Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:] + 741 frame #4: 0x00007fff2565304b Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 68 frame #5: 0x00007fff2564dc15 Foundation`_NSSetLongLongValueAndNotify + 269 frame #6: 0x0000000104e941f4 MYKVO`-[ViewController touchesBegan:withEvent:](self=0x00007fc066704100, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x0000600002ecc780) at ViewController.m:78:13 frame #7: 0x00007fff475a57c5 UIKitCore`forwardTouchMethod + 340 frame #8: 0x00007fff475a5660 UIKitCore`-[UIResponder touchesBegan:withEvent:] + 49 frame #9: 0x00007fff475b4750 UIKitCore`-[UIWindow _sendTouchesForEvent:] + 1867 frame #10: 0x00007fff475b6338 UIKitCore`-[UIWindow sendEvent:] + 4596 frame #11: 0x00007fff47591693 UIKitCore`-[UIApplication sendEvent:] + 356 frame #12: 0x00007fff47611e5a UIKitCore`__dispatchPreprocessedEventFromEventQueue + 6847 frame #13: 0x00007fff47614920 UIKitCore`__handleEventQueueInternal + 5980 frame #14: 0x00007fff23b0d271 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17 frame #15: 0x00007fff23b0d19c CoreFoundation`__CFRunLoopDoSource0 + 76 frame #16: 0x00007fff23b0c974 CoreFoundation`__CFRunLoopDoSources0 + 180 frame #17: 0x00007fff23b0767f CoreFoundation`__CFRunLoopRun + 1263 frame #18: 0x00007fff23b06e66 CoreFoundation`CFRunLoopRunSpecific + 438 frame #19: 0x00007fff38346bb0 GraphicsServices`GSEventRunModal + 65 frame #20: 0x00007fff47578dd0 UIKitCore`UIApplicationMain + 1621 frame #21: 0x0000000104e946ad MYKVO`main(argc=1, argv=0x00007ffeead6ad68) at main.m:18:12 frame #22: 0x00007fff516ecd29 libdyld.dylib`start + 1 frame #23: 0x00007fff516ecd29 libdyld.dylib`start + 1

可以看到KVO第一个响应的方法是Foundation_NSSetLongLongValueAndNotify 获取FoundationMach-O文件之后可以使用如下指令,在terminal中查看Foundation中是否有类似方法

$ nm Foundation | grep ValueAndNotify
000000018307f5f8 t __NSSetBoolValueAndNotify
0000000182ffb37c t __NSSetCharValueAndNotify
000000018307f868 t __NSSetDoubleValueAndNotify
0000000183039cc4 t __NSSetFloatValueAndNotify
0000000183024f30 t __NSSetIntValueAndNotify
000000018307fd50 t __NSSetLongLongValueAndNotify
000000018307fae0 t __NSSetLongValueAndNotify
0000000182ffb534 t __NSSetObjectValueAndNotify
0000000183080230 t __NSSetPointValueAndNotify
0000000183080378 t __NSSetRangeValueAndNotify
00000001830804c0 t __NSSetRectValueAndNotify
000000018307ffc0 t __NSSetShortValueAndNotify
0000000183080624 t __NSSetSizeValueAndNotify
000000018307f730 t __NSSetUnsignedCharValueAndNotify
000000018307f9a8 t __NSSetUnsignedIntValueAndNotify
000000018307fe88 t __NSSetUnsignedLongLongValueAndNotify
000000018307fc18 t __NSSetUnsignedLongValueAndNotify
00000001830800f8 t __NSSetUnsignedShortValueAndNotify
000000018307e9a8 t __NSSetValueAndNotifyForKeyInIvar
000000018307ea1c t __NSSetValueAndNotifyForUndefinedKey
000000018308080c t ____NSSetBoolValueAndNotify_block_invoke
0000000183080860 t ____NSSetCharValueAndNotify_block_invoke
0000000183080908 t ____NSSetDoubleValueAndNotify_block_invoke
000000018308095c t ____NSSetFloatValueAndNotify_block_invoke
00000001830809b0 t ____NSSetIntValueAndNotify_block_invoke
0000000183080af8 t ____NSSetLongLongValueAndNotify_block_invoke
0000000183080a58 t ____NSSetLongValueAndNotify_block_invoke
000000018308076c t ____NSSetObjectValueAndNotify_block_invoke
0000000183080c40 t ____NSSetPointValueAndNotify_block_invoke
0000000183080c94 t ____NSSetRangeValueAndNotify_block_invoke
0000000183080ce8 t ____NSSetRectValueAndNotify_block_invoke
0000000183080b98 t ____NSSetShortValueAndNotify_block_invoke
0000000183080d40 t ____NSSetSizeValueAndNotify_block_invoke
00000001830808b4 t ____NSSetUnsignedCharValueAndNotify_block_invoke
0000000183080a04 t ____NSSetUnsignedIntValueAndNotify_block_invoke
0000000183080b48 t ____NSSetUnsignedLongLongValueAndNotify_block_invoke
0000000183080aa8 t ____NSSetUnsignedLongValueAndNotify_block_invoke
0000000183080bec t ____NSSetUnsignedShortValueAndNotify_block_invoke

看来观察不同类型的属性,会调用不同的__NSSet***AndNotify方法.

打印并比较,添加了KVO和没有添加KVO的AYPerson方法如下

NSKVONotifying_AYPerson: setAge:, class, dealloc, _isKVOA
AYPerson: age, setAge:

生成的派生类中多了 _isKVOA方法,并重写了 setAge:, class, dealloc

-(Class)class
    return [super class];
- (void)setAge:(NSInteger)age
    [self didChangeValueForKey:@"age"];
    [super setAge: age];
    [self willChangeValueForKey:@"age"];
- (Bool)_isKVOA
    return YES;

它的实现大致上是上面的代码

KVO需要注意的点

只有调用set方法会触发KVO,直接改成员变量的值不会触发, 可以手动调用 willChangeValueForKey:, didChangeValueForKey:

    // 直接设置属性,不会调用KVO
    self->_age += 3;
    // 模拟重写后set方法,手动调用KVO
    [self willChangeValueForKey:@"age"];
    self->_age += 3;
    [self didChangeValueForKey:@"age"];

reference: MJ的iOS底层原理课