libc++abi.dylib: terminating with uncaught exception of type NSException *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<Person: 0x6000018fc120>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled. Key path: name Observed object: <Person: 0x6000018fc100> Change: { kind = 1; new = LeeWong; old = "<null>"; }
// 对于任意类型的属性 NSKeyValueChangeSetting 表示被监听的对象接收到-setValue:forKey: // 消息调用或对象的setter方法被调用或者 // -willChangeValueForKey:/-didChangeValueForKey: 被调用 - For any sort of property (attribute, to-one relationship, or ordered or unordered to-many relationship) NSKeyValueChangeSetting indicates that the observed object has received a -setValue:forKey: message, or that the key-value coding-compliant set method for the key has been invoked, or that a -willChangeValueForKey:/-didChangeValueForKey: pair has otherwise been invoked.
// 对于一个有序一对多关系,NSKeyValueChangeInsertion、NSKeyValueChangeRemoval NSKeyValueChangeReplacement 表示通过调用对象的mutableArrayValueForKey:方法给array发送了一个修改消息或者数组或有序集合的修改方法被调用再或者-willChange:valuesAtIndexes:forKey:/-didChange:valuesAtIndexes:forKey 被调用 - For an _ordered_ to-many relationship, NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, and NSKeyValueChangeReplacement indicate that a mutating message has been sent to the array returned by a -mutableArrayValueForKey: message sent to the object, or sent to the ordered set returned by a -mutableOrderedSetValueForKey: message sent to the object, or that one of the key-value coding-compliant array or ordered set mutation methods for the key has been invoked, or that a -willChange:valuesAtIndexes:forKey:/-didChange:valuesAtIndexes:forKey: pair has otherwise been invoked. // 对于一个无序的一对多关系 NSKeyValueChangeInsertion NSKeyValueChangeRemoval NSKeyValueChangeReplacement 表示通过调用对象mutableSetValueForKey方法,导致集合的修改方法被调用,或者key-value coding-compliant set 修改方法被调用,或者willChangeValueForKey:withSetMutation:usingObjects:/-didChangeValueForKey:withSetMutation:usingObjects: 方法被调用 - For an _unordered_ to-many relationship (introduced in Mac OS 10.4), NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, and NSKeyValueChangeReplacement indicate that a mutating message has been sent to the set returned by a -mutableSetValueForKey: message sent to the object, or that one of the key-value coding-compliant set mutation methods for the key has been invoked, or that a -willChangeValueForKey:withSetMutation:usingObjects:/-didChangeValueForKey:withSetMutation:usingObjects: pair has otherwise been invoked.
/* Given a key that identifies a property (attribute, to-one relationship, or ordered or unordered to-many relationship), send -observeValueForKeyPath:ofObject:change:context: notification messages of kind NSKeyValueChangeSetting to each observer registered for the key, including those that are registered with other objects using key paths that locate the keyed value in this object. Invocations of these methods must always be paired.
The change dictionaries in notifications resulting from use of these methods contain optional entries if requested at observer registration time: - The NSKeyValueChangeOldKey entry, if present, contains the value returned by -valueForKey: at the instant that -willChangeValueForKey: is invoked (or an NSNull if -valueForKey: returns nil). - The NSKeyValueChangeNewKey entry, if present, contains the value returned by -valueForKey: at the instant that -didChangeValueForKey: is invoked (or an NSNull if -valueForKey: returns nil). */ - (void)willChangeValueForKey:(NSString *)key; - (void)didChangeValueForKey:(NSString *)key;
2020-12-1212:39:40.433214+0800 Runtime-KVO[52130:15055464] Person willChangeValueForKey name 2020-12-1212:39:40.433527+0800 Runtime-KVO[52130:15055464] keyPath name object <Person: 0x600002f65740> change{ kind = 1; new = LeeWong; old = "<null>"; } context self 2020-12-1212:39:40.433643+0800 Runtime-KVO[52130:15055464] Person didChangeValueForKey name
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.
自动的键值监听是通过使用isa-swizzling技术实现,即isa交换,下面我们重点看下这段话:
1
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 trueclass.
2020-12-1213:36:28.533095+0800 Runtime-KVO[55411:15105963] class Person 2020-12-1213:36:28.533274+0800 Runtime-KVO[55411:15105963] isa classNSKVONotifying_Person
2020-12-1214:10:07.443251+0800 Runtime-KVO[57507:15138615] --------------------------------- 2020-12-1214:10:07.443362+0800 Runtime-KVO[57507:15138615] class Person 2020-12-1214:10:07.443446+0800 Runtime-KVO[57507:15138615] isa class Person 2020-12-1214:10:07.443787+0800 Runtime-KVO[57507:15138615] --------------------------------- 2020-12-1214:10:07.443896+0800 Runtime-KVO[57507:15138615] class Person 2020-12-1214:10:07.443988+0800 Runtime-KVO[57507:15138615] isa classNSKVONotifying_Person 2020-12-1214:10:07.444116+0800 Runtime-KVO[57507:15138615] --------------------------------- 2020-12-1214:10:07.444209+0800 Runtime-KVO[57507:15138615] class Person 2020-12-1214:10:07.444303+0800 Runtime-KVO[57507:15138615] isa class Person
2020-12-1214:51:30.825912+0800 Runtime-KVO[59915:15176280] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[<__NSArrayM 0x60000246f0c0> addObserver:forKeyPath:options:context:] is not supported. Key path: count' *** First throw call stack:
/* Given a key that identifies an _ordered_ to-many relationship, return a mutable array that provides read-write access to the related objects. Objects added to the mutable array will become related to the receiver, and objects removed from the mutable array will become unrelated.
The default implementation of this method recognizes the same simple accessor methods and array accessor methods as -valueForKey:'s, and follows the same direct instance variable access policies, but always returns a mutable collection proxy object instead of the immutable collection that -valueForKey: would return. It also: 1. Searches the class of the receiver for methods whose names match the patterns -insertObject:in<Key>AtIndex: and -removeObjectFrom<Key>AtIndex: (corresponding to the two most primitive methods defined by the NSMutableArray class), and (introduced in Mac OS 10.4) also -insert<Key>:atIndexes: and -remove<Key>AtIndexes: (corresponding to -[NSMutableArray insertObjects:atIndexes:] and -[NSMutableArray removeObjectsAtIndexes:). If at least one insertion method and at least one removal method are found each NSMutableArray message sent to the collection proxy object will result in some combination of -insertObject:in<Key>AtIndex:, -removeObjectFrom<Key>AtIndex:, -insert<Key>:atIndexes:, and -remove<Key>AtIndexes: messages being sent to the original receiver of -mutableArrayValueForKey:. If the class of the receiver also implements an optional method whose name matches the pattern -replaceObjectIn<Key>AtIndex:withObject: or (introduced in Mac OS 10.4) -replace<Key>AtIndexes:with<Key>: that method will be used when appropriate for best performance. 2. Otherwise (no set of array mutation methods is found), searches the class of the receiver for an accessor method whose name matches the pattern -set<Key>:. If such a method is found each NSMutableArray message sent to the collection proxy object will result in a -set<Key>: message being sent to the original receiver of -mutableArrayValueForKey:. 3. Otherwise (no set of array mutation methods or simple accessor method is found), if the receiver's class' +accessInstanceVariablesDirectly property returns YES, searches the class of the receiver for an instance variable whose name matches the pattern _<key> or <key>, in that order. If such an instance variable is found, each NSMutableArray message sent to the collection proxy object will be forwarded to the instance variable's value, which therefore must typically be an instance of NSMutableArray or a subclass of NSMutableArray. 4. Otherwise (no set of array mutation methods, simple accessor method, or instance variable is found), returns a mutable collection proxy object anyway. Each NSMutableArray message sent to the collection proxy object will result in a -setValue:forUndefinedKey: message being sent to the original receiver of -mutableArrayValueForKey:. The default implementation of -setValue:forUndefinedKey: raises an NSUndefinedKeyException, but you can override it in your application.
Performance note: the repetitive -set<Key>: messages implied by step 2's description are a potential performance problem. For better performance implement insertion and removal methods that fulfill the requirements for step 1 in your KVC-compliant class. For best performance implement a replacement method too. */ - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
Performance note: the repetitive -set<Key>: messages implied by step 2's description are a potential performance problem. For better performance implement insertion and removal methods that fulfill the requirements for step 1 in your KVC-compliant class. For best performance implement a replacement method too.
/* Return a set of key paths for properties whose values affect the value of the keyed property. When an observer for the key is registered with an instance of the receiving class, KVO itself automatically observes all of the key paths for the same instance, and sends change notifications for the key to the observer when the value for any of those key paths changes. The default implementation of this method searches the receiving class for a method whose name matches the pattern +keyPathsForValuesAffecting<Key>, and returns the result of invoking that method if it is found. So, any such method must return an NSSet too. If no such method is found, an NSSet that is computed from information provided by previous invocations of the now-deprecated +setKeys:triggerChangeNotificationsForDependentKey: method is returned, for backward binary compatibility.
This method and KVO's automatic use of it comprise a dependency mechanism that you can use instead of sending -willChangeValueForKey:/-didChangeValueForKey: messages for dependent, computed, properties. You can override this method when the getter method of one of your properties computes a value to return using the values of other properties, including those that are located by key paths. Your override should typically invoke super and return a set that includes any members in the set that result from doing that (so as not to interfere with overrides of this method in superclasses).
You can't really override this method when you add a computed property to an existing class using a category, because you're not supposed to override methods in categories. In that case, implement a matching +keyPathsForValuesAffecting<Key> to take advantage of this mechanism. */ + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
2020-12-1320:52:59.084192+0800 Runtime-KVO[81809:15903199] Person willChangeValueForKey name 2020-12-1320:52:59.084354+0800 Runtime-KVO[81809:15903199] observeValueForKeyPath for keyPath personDescription 2020-12-1320:52:59.084446+0800 Runtime-KVO[81809:15903199] Person didChangeValueForKey name 2020-12-1320:52:59.085154+0800 Runtime-KVO[81809:15903199] Person willChangeValueForKey age 2020-12-1320:52:59.085245+0800 Runtime-KVO[81809:15903199] observeValueForKeyPath for keyPath personDescription 2020-12-1320:52:59.085350+0800 Runtime-KVO[81809:15903199] Person didChangeValueForKey age
/* Return YES if the key-value observing machinery should automatically invoke -willChangeValueForKey:/-didChangeValueForKey:, -willChange:valuesAtIndexes:forKey:/-didChange:valuesAtIndexes:forKey:, or -willChangeValueForKey:withSetMutation:usingObjects:/-didChangeValueForKey:withSetMutation:usingObjects: whenever instances of the class receive key-value coding messages for the key, or mutating key-value coding-compliant methods for the key are invoked. Return NO otherwise. Starting in Mac OS 10.5, the default implementation of this method searches the receiving class for a method whose name matches the pattern +automaticallyNotifiesObserversOf<Key>, and returns the result of invoking that method if it is found. So, any such method must return BOOL too. If no such method is found YES is returned. */ + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;
2020-12-1320:49:08.814044+0800 Runtime-KVO[81739:15899611] Person willChangeValueForKey name 2020-12-1320:49:08.814166+0800 Runtime-KVO[81739:15899611] observeValueForKeyPath for keyPath name 2020-12-1320:49:08.814406+0800 Runtime-KVO[81739:15899611] keyPath name object <Person: 0x60000159e610> change{ kind = 1; new = LeeWong; old = "<null>"; } context 2020-12-1320:49:08.814578+0800 Runtime-KVO[81739:15899611] Person didChangeValueForKey name
/* Take or return a pointer that identifies information about all of the observers that are registered with the receiver, the options that were used at registration-time, etc. The default implementation of these methods store observation info in a global dictionary keyed by the receivers' pointers. For improved performance, you can override these methods to store the opaque data pointer in an instance variable. Overrides of these methods must not attempt to send Objective-C messages to the passed-in observation info, including -retain and -release. */ @property (nullable) void *observationInfo NS_RETURNS_INNER_POINTER;