introduce
KVO
full name of KeyValueObserving
, is an event notification mechanism provided by Apple. Allows an object to listen for changes in a specific property of another object and receive events when it changes. Due to KVO
, it has an effect on attributes. Generally, objects NSObject
KVO
default.
KVO
and NSNotificationCenter
are both an implementation of the observer mode in iOS
The difference is that, with respect to the relationship between the observer and the observer, KVO
is one-to-one, not one-to-many. KVO
is not intrusive to the monitored object, and the monitoring can be realized without manually modifying its internal code.
KVO
can monitor the changes of a single attribute, and can also monitor the changes of a collection of objects. The proxy object is obtained through the mutableArrayValueForKey:
KVC
of 06198dc4e86c9a. When the internal object of the proxy object changes, the monitoring method of KVO
Collection objects include NSArray
and NSSet
.
use
Using KVO
divided into three steps
- Register an observer through the
addObserver:forKeyPath:options:context:
method, and the observer can receive thekeyPath
property change event of 06198dc4e86d10. observeValueForKeyPath:ofObject:change:context:
method in the observer. When the attribute ofkeyPath
KVO
will call back this method to notify the observer.- When the observer does not need to listen, you can call the
removeObserver:forKeyPath:
method to removeKVO
It should be noted that callingremoveObserver
needs to be before the observer disappears, otherwise it will causeCrash
.
register
When registering an observer, you can pass in the options
parameter, which is an enumerated type. If the incoming NSKeyValueObservingOptionNew
and NSKeyValueObservingOptionOld
means to receive the new value and the old value, the default is to receive only the new value. If you want to receive a callback immediately after registering the observer, you can join the NSKeyValueObservingOptionInitial
enumeration.
You can also context
This object can be received in the code that receives the message callback. It is a value-passing method KVO
After calling the addObserver
method, KVO
does not make a strong reference to the observer. So we need to pay attention to the life cycle of the observer, otherwise it will cause Crash
brought by the observer being released.
monitor
Observers need to implement the observeValueForKeyPath:ofObject:change:context:
method. KVO
event arrives. If it is not implemented, it will result in Crash
. change
dictionary stores the value related to the KVO
attribute, which is returned according to the enumeration passed in at options
The enumeration will correspond to the corresponding key
to retrieve the value from the dictionary. For example, there is a NSKeyValueChangeOldKey
field to store the old value before the change.
change
there NSKeyValueChangeKindKey
field, and NSKeyValueChangeOldKey
relationship same level, to provide information on this change, the corresponding NSKeyValueChange
enumerated type value
. For example, when the observed attribute changes, the field is NSKeyValueChangeSetting
.
If the object is a collection of objects to be observed, in NSKeyValueChangeKindKey
field contains NSKeyValueChangeInsertion
, NSKeyValueChangeRemoval
, NSKeyValueChangeReplacement
information indicating the operation mode set object.
Other trigger methods
When calling the KVO
attribute object, not only can it be called set
KVO
compatible with many calling methods.
// 直接调用set方法,或者通过属性的点语法间接调用
[account setName:@"Savings"];
// 使用KVC的setValue:forKey:方法
[account setValue:@"Savings" forKey:@"name"];
// 使用KVC的setValue:forKeyPath:方法
[document setValue:@"Savings" forKeyPath:@"account.name"];
// 通过mutableArrayValueForKey:方法获取到代理对象,并使用代理对象进行操作
Transaction *newTransaction = <#Create a new transaction for the account#>;
NSMutableArray *transactions = [account mutableArrayValueForKey:@"transactions"];
[transactions addObject:newTransaction];
Practical application
KVO
mainly used for key value observation operations. If you want to notify another object after a value changes, it is most suitable to KVO
There is a classic case in the iOS
tutorial of Stanford University, which communicates between Model
and Controller
KVO
trigger
Active trigger
KVO
when the attribute changes is automatic. If you want to manually control the call timing, or want to implement KVO
attribute yourself, you can call it through the method provided KVO
- (void)setBalance:(double)theBalance {
if (theBalance != _balance) {
[self willChangeValueForKey:@"balance"];
_balance = theBalance;
[self didChangeValueForKey:@"balance"];
}
}
We can see a call KVO
rely mainly on two methods, called before the property changes willChangeValueForKey:
method, called after the change didChangeValueForKey:
method. However, if you do not call willChangeValueForKey
, directly calling didChangeValueForKey
does not take effect. The two are in order and need to be paired.
Disable KVO
If you want to prohibit the KVO
a certain attribute, for example, if the key information does not want to be SDK
by the KVO
, you can use the automaticallyNotifiesObserversForKey
method to return NO
to prohibit other places from performing KVO
on this attribute. If the method returns YES
it means it can be called. If it returns NO
it means it can’t be called. This method is a class method, you can judge keyPath
inside the method to choose whether this attribute is allowed to be KVO
.
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"balance"]) {
automatic = NO;
}
else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
KVC trigger
KVC
to KVO
special compatible, when by KVC
when calling an instance variable of non-property, KVC
interior will trigger KVO
callback, and by NSKeyValueDidChange
and NSKeyValueWillChange
callback upward.
The following ignores main
function upwards, and only retains the critical stack. This is the KVO
stack called back by calling the property setter
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 38.1
* frame #0: 0x0000000101bc3a15 TestKVO`::-[ViewController observeValueForKeyPath:ofObject:change:context:](self=0x00007f8419705890, _cmd="observeValueForKeyPath:ofObject:change:context:", keyPath="object", object=0x0000604000015b00, change=0x0000608000265540, context=0x0000000000000000) at ViewController.mm:84
frame #1: 0x000000010327e820 Foundation`NSKeyValueNotifyObserver + 349
frame #2: 0x000000010327e0d7 Foundation`NSKeyValueDidChange + 483
frame #3: 0x000000010335f22b Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:usingBlock:] + 778
frame #4: 0x000000010324b1b4 Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 61
frame #5: 0x00000001032a7b79 Foundation`_NSSetObjectValueAndNotify + 255
frame #6: 0x0000000101bc3937 TestKVO`::-[ViewController viewDidLoad](self=0x00007f8419705890, _cmd="viewDidLoad") at ViewController.mm:70
This is done by KVC
callback trigger up, you can see the normal way by modifying the properties of the trigger KVO
, and by KVC
triggered KVO
there are differences. KVC
by way of KVO
, even without the call of _NSSetObjectValueAndNotify
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 37.1
* frame #0: 0x0000000106be1a85 TestKVO`::-[ViewController observeValueForKeyPath:ofObject:change:context:](self=0x00007fe68ac07710, _cmd="observeValueForKeyPath:ofObject:change:context:", keyPath="object", object=0x0000600000010c80, change=0x000060c000262780, context=0x0000000000000000) at ViewController.mm:84
frame #1: 0x000000010886d820 Foundation`NSKeyValueNotifyObserver + 349
frame #2: 0x000000010886d0d7 Foundation`NSKeyValueDidChange + 483
frame #3: 0x000000010894d422 Foundation`NSKeyValueDidChangeWithPerThreadPendingNotifications + 148
frame #4: 0x0000000108879b47 Foundation`-[NSObject(NSKeyValueCoding) setValue:forKey:] + 292
frame #5: 0x0000000106be19aa TestKVO`::-[ViewController viewDidLoad](self=0x00007fe68ac07710, _cmd="viewDidLoad") at ViewController.mm:70
Realization principle
Core logic
KVO
is achieved through isa-swizzling
technology, which is the KVO
implementation of the entire 06198dc4e870fe. At runtime, create an intermediate class based on the original class, which is a subclass of the original class, and dynamically modify the isa
current object to point to the intermediate class. And class
overridden methods, return to the original class Class
. Apple rewrites the class
method to shield the existence of intermediate classes.
Therefore, Apple recommends that you should not rely on the isa
pointer in development, but class
instance method to avoid being KVO
by 06198dc4e87120 or other runtime
methods.
_NSSetObjectValueAndNotify
Then modifies the intermediate category corresponding set
method and inserted willChangeValueForkey
methods and didChangeValueForKey
method, call the parent class in the middle of two methods set
method. In this process, the system encapsulates it into the _NSSetObjectValueAndNotify
function. By viewing the assembly code of this function, you can see the call willChangeValueForkey
method and the didChangeValueForKey
The system does not only encapsulate the _NSSetObjectValueAndNotify
function, but calls different functions according to the attribute type. If it is the Int
type, it will call _NSSetIntValueAndNotify
. These implementations are defined in the Foundation
framework. Specifically, you can view the implementation of the Foundation
hopper
runtime
will replace the implementation of the setObject
method of the newly generated NSKVONotifying_KVOTest
_NSSetObjectValueAndNotify
function instead of rewriting the setObject
function. Through the following test code, you can view the selector
corresponding to IMP
, and print out its realized address.
KVOTest *test = [[KVOTest alloc] init];
[test setObject:[[NSObject alloc] init]];
NSLog(@"%p", [test methodForSelector:@selector(setObject:)]);
[test addObserver:self forKeyPath:@"object" options:NSKeyValueObservingOptionNew context:nil];
[test setObject:[[NSObject alloc] init]];
NSLog(@"%p", [test methodForSelector:@selector(setObject:)]);
// 打印结果,第一次的方法地址为0x100c8e270,第二次的方法地址为0x7fff207a3203
(lldb) p (IMP)0x100c8e270
(IMP) $0 = 0x0000000100c8e270 (DemoProject`-[KVOTest setObject:] at KVOTest.h:11)
(lldb) p (IMP)0x7fff207a3203
(IMP) $1 = 0x00007fff207a3203 (Foundation`_NSSetObjectValueAndNotify)
_NSKVONotifyingCreateInfoWithOriginalClass
For the system to achieve KVO
principle, it is possible to object_setClass
break point, or to objc_allocateClassPair
method can also break point, these two methods are class methods must take to create. Through the assembly stack of these two methods, backtracking forward. Then, you can get the following assembly code after translation.
You can see that there are some class name splicing rules, and then create a new class based on the class name. If newCls
is empty, it has already been created, or it may be empty. If newCls
not empty, register the newly created class and set some parameters of the SDTestKVOClassIndexedIvars
Class _NSKVONotifyingCreateInfoWithOriginalClass(Class originalClass) {
const char *clsName = class_getName(originalClass);
size_t len = strlen(clsName);
len += 0x10;
char *newClsName = malloc(len);
const char *prefix = "NSKVONotifying_";
__strlcpy_chk(newClsName, prefix, len);
__strlcat_chk(newClsName, clsName, len, -1);
Class newCls = objc_allocateClassPair(originalClass, newClsName, 0x68);
if (newCls) {
objc_registerClassPair(newCls);
SDTestKVOClassIndexedIvars *indexedIvars = object_getIndexedIvars(newCls);
indexedIvars->originalClass = originalClass;
indexedIvars->KVOClass = newCls;
CFMutableSetRef mset = CFSetCreateMutable(nil, 0, kCFCopyStringSetCallBacks);
indexedIvars->mset = mset;
CFMutableDictionaryRef mdict = CFDictionaryCreateMutable(nil, 0, nil, kCFTypeDictionaryValueCallBacks);
indexedIvars->mdict = mdict;
pthread_mutex_init(indexedIvars->lock);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
bool flag = true;
IMP willChangeValueForKeyImp = class_getMethodImplementation(indexedIvars->originalClass, @selector(willChangeValueForKey:));
IMP didChangeValueForKeyImp = class_getMethodImplementation(indexedIvars->originalClass, @selector(didChangeValueForKey:));
if (willChangeValueForKeyImp == _NSKVONotifyingCreateInfoWithOriginalClass.NSObjectWillChange && didChangeValueForKeyImp == _NSKVONotifyingCreateInfoWithOriginalClass.NSObjectDidChange) {
flag = false;
}
indexedIvars->flag = flag;
NSKVONotifyingSetMethodImplementation(indexedIvars, @selector(_isKVOA), NSKVOIsAutonotifying, nil);
NSKVONotifyingSetMethodImplementation(indexedIvars, @selector(dealloc), NSKVODeallocate, nil);
NSKVONotifyingSetMethodImplementation(indexedIvars, @selector(class), NSKVOClass, nil);
});
} else {
return nil;
}
return newCls;
}
verify
In order to verify KVO
, we add the following test code. First create a KVOObject
class, and add two properties in it, then rewrite the description
method, and print some key parameters inside.
It should be noted that in order to verify KVO
at runtime, I printed the object's class
method, and obtained the object's class and parent class runtime
Before and after adding KVO
monitor, print it once and observe what the system does.
@interface KVOObject : NSObject
@property (nonatomic, copy ) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
- (NSString *)description {
IMP nameIMP = class_getMethodImplementation(object_getClass(self), @selector(setName:));
IMP ageIMP = class_getMethodImplementation(object_getClass(self), @selector(setAge:));
NSLog(@"object setName: IMP %p object setAge: IMP %p \n", nameIMP, ageIMP);
Class objectMethodClass = [self class];
Class objectRuntimeClass = object_getClass(self);
Class superClass = class_getSuperclass(objectRuntimeClass);
NSLog(@"objectMethodClass : %@, ObjectRuntimeClass : %@, superClass : %@ \n", objectMethodClass, objectRuntimeClass, superClass);
NSLog(@"object method list \n");
unsigned int count;
Method *methodList = class_copyMethodList(objectRuntimeClass, &count);
for (NSInteger i = 0; i < count; i++) {
Method method = methodList[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
NSLog(@"method Name = %@\n", methodName);
}
return @"";
}
Creating a KVOObject
object, KVO
key information objects are printed front and back, to see KVO
what happens before and after.
self.object = [[KVOObject alloc] init];
[self.object description];
[self.object addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[self.object description];
The following is the key information printed before and after KVO
We found that KVO
, its real type became NSKVONotifying_KVOObject
, which is no longer the previous class. KVO
will dynamically create a new class at runtime, isa
object to the newly created class, and superClass
to the original class KVOObject
. The newly created class naming rule is in the format NSKVONotifying_xxx
KVO
order to make it more like the previous class, but also the object class
example method rewritten, it is more like the original class.
Add KVO
after, due to the modification of setName
method and setAge
methods IMP
, so print these two methods IMP
, also a new address, new implementations in NSKVONotifying_KVOObject
in.
This implementation is not intrusive to the business code. It can monitor a single object and modify its method implementation KVOObject
KVO
callback when assigning values.
_isKVOA
method was also found in the above code. This method can be used as KVO
, and the system may also use this method. If we want to determine whether the current class is a KVO
, we can search for this method from the method list.
// 第一次
object address : 0x604000239340
object setName: IMP 0x10ddc2770 object setAge: IMP 0x10ddc27d0
objectMethodClass : KVOObject, ObjectRuntimeClass : KVOObject, superClass : NSObject
object method list
method Name = .cxx_destruct
method Name = description
method Name = name
method Name = setName:
method Name = setAge:
method Name = age
// 第二次
object address : 0x604000239340
object setName: IMP 0x10ea8defe object setAge: IMP 0x10ea94106
objectMethodClass : KVOObject, ObjectRuntimeClass : NSKVONotifying_KVOObject, superClass : KVOObject
object method list
method Name = setAge:
method Name = setName:
method Name = class
method Name = dealloc
method Name = _isKVOA
object_getClass
Why the above calls runtime
of object_getClass
function, you can get to the real class it?
Call object_getClass
after the function which returns a Class
type, Class
is objc_class
defined a typedef
alias by objc_class
can get to the subject isa
pointer Class
, i.e. an object class object.
From this we can know that the object_getClass
function internally returns the object's isa
pointer.
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
}
be careful
Crash
KVO
of addObserver
and removeObserver
need to be paired, if repeated remove
will lead NSRangeException
type of Crash
, if you forget remove
will be received again after the release of the observer KVO
callback Crash
.
Apple's official recommended way is, in init
carried out when addObserver
in dealloc
when removeObserver
, so you can ensure add
and remove
are paired, is an ideal use.
Error checking
If a wrong keyPath
is passed in, there will be no error message. When calling KVO
, you need to pass in a keyPath
. Because keyPath
is in the form of a string, if the attribute name is changed, the string does not change, which will easily lead to Crash
. For this problem, we can use the reflection mechanism of the system to reflect keyPath
, so that the compiler can perform a legality check @selector()
NSString *keyPath = NSStringFromSelector(@selector(isFinished));
Cannot trigger callback
Due to KVO
, if a member variable is called for assignment, KVO
will not be triggered.
@interface TestObject : NSObject {
@public
NSObject *object;
}
@end
// 错误的调用方式
self.object = [[TestObject alloc] init];
[self.object addObserver:self forKeyPath:@"object" options:NSKeyValueObservingOptionNew context:nil];
self.object->object = [[NSObject alloc] init];
However, if the assignment operation is called KVC
, the callback method of KVO
This is because KVC
to KVO
a separate compatible, in KVC
internal assignment method, manually invoke the willChangeValueForKey:
and didChangeValueForKey:
method.
// KVC的方式调用
self.object = [[TestObject alloc] init];
[self.object addObserver:self forKeyPath:@"object" options:NSKeyValueObservingOptionNew context:nil];
[self.object setValue:[[NSObject alloc] init] forKey:@"object"];
Add repeatedly
Repeating addObserver
on KVO
will not cause a crash, but there will be a problem of KVO
[self.testLabel addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];
self.testLabel.text = @"test";
[self.testLabel addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];
[self.testLabel addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];
[self.testLabel addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];
self.testLabel.text = @"test";
// 输出
2018-08-03 11:48:49.502450+0800 KVOTest[5846:412257] test
2018-08-03 11:48:52.975102+0800 KVOTest[5846:412257] test
2018-08-03 11:48:53.547145+0800 KVOTest[5846:412257] test
2018-08-03 11:48:54.087171+0800 KVOTest[5846:412257] test
2018-08-03 11:48:54.649244+0800 KVOTest[5846:412257] test
Through the above test code, and print object
corresponding to Class
in the callback, it can be seen that the subclass will not be created repeatedly, and it is always a class. Although repeating addobserver
will not crash immediately, it will crash immediately when removeObserver
is called for the first time after repeated addition. From the perspective of the crash stack, it is the same as the problem of repeated removal, which is an exception that the system actively throws.
Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <UILabel 0x7f859b547490> for the key path "text" from <UILabel 0x7f859b547490> because it is not registered as an observer.'
Duplicate removal
KVO
does not allow keyPath
. Repeated removal will cause a crash. For example, the following test code.
[self.testLabel addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];
self.testLabel.text = @"test";
[self.testLabel removeObserver:self forKeyPath:@"text"];
[self.testLabel removeObserver:self forKeyPath:@"text"];
[self.testLabel removeObserver:self forKeyPath:@"text"];
After executing the above test code, the following crash message will be caused. From KVO
can see the collapse of the stack, the system in order to achieve KVO
of addObserver
and removeObserver
, as NSObject
added a named NSKeyValueObserverRegistration
of Category
, KVO
of addObserver
and removeObserver
implemented in it.
Removing KVO
when listening, the system will determine the current KVO
of keyPath
has already been removed, if it has been removed, take the initiative to throw a NSException
exception.
2018-08-03 10:54:27.477379+0800 KVOTest[4939:286991] *** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <ViewController 0x7ff6aee31600> for the key path "text" from <UILabel 0x7ff6aee2e850> because it is not registered as an observer.'
*** First throw call stack:
(
0 CoreFoundation 0x000000010db2312b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x000000010cc6af41 objc_exception_throw + 48
2 CoreFoundation 0x000000010db98245 +[NSException raise:format:] + 197
3 Foundation 0x0000000108631f15 -[NSObject(NSKeyValueObserverRegistration) _removeObserver:forProperty:] + 497
4 Foundation 0x0000000108631ccb -[NSObject(NSKeyValueObserverRegistration) removeObserver:forKeyPath:] + 84
5 KVOTest 0x0000000107959a55 -[ViewController viewDidAppear:] + 373
// .....
20 UIKit 0x000000010996d5d6 UIApplicationMain + 159
21 KVOTest 0x00000001079696cf main + 111
22 libdyld.dylib 0x000000010fb43d81 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Check the link
KVO
is an implementation of an event binding mechanism. After keyPath
changes, the corresponding method will be called back. bug
which is not easy to investigate when the object relationship is very complicated. For example, the keyPath
which the attribute corresponding to 06198dc4e876b5 is called is very complicated, so it is not recommended to use KVO
for this attribute.
Implement KVO by yourself
In addition to the above shortcomings, KVO
does not support the block
syntax, and the parent method needs to be rewritten separately. Adding the add
and remove
methods will result in very scattered code. Therefore, I simply implemented a KVO
runtime
, and the source code is placed on my Github
, called EasyKVO .
self.object = [[KVOObject alloc] init];
[self.object lxz_addObserver:self originalSelector:@selector(name) callback:^(id observedObject, NSString *observedKey, id oldValue, id newValue) {
// 处理业务逻辑
}];
self.object.name = @"lxz";
// 移除通知
[self.object lxz_removeObserver:self originalSelector:@selector(name)];
The calling code is very simple. You can directly add the monitoring of KVO
lxz_addObserver:originalSelector:callback:
method, and you can receive the callback after the attribute changes callback
of block
And the method keyPath
receives a SEL
type 06198dc4e87741, so the @selector()
when the parameter is passed in through 06198dc4e87742. If it is an unimplemented method, it will directly report a warning.
Pass the observer and keyPath
lxz_removeObserver:originalSelector:
method, and remove the observer object KVO
when all keyPath
observer is removed.
If you repeat addObserver
and removeObserver
it’s okay, there is internal judgment logic. EasyKVO
internally weak
, which will not affect the life cycle of the observer, and will not cause Crash
after the observer is released. One add
method call corresponds to one block
. If the observer monitors multiple keyPath
attributes, there is no need to judge keyPath
block
callback.
KVOController
Want to project a safe and convenient use KVO
then recommend Facebook
one KVO
open source third-party frameworks KVOController . KVOController
is essentially the system KVO
package, with native KVO
all the features, but also to avoid the native KVO
a lot of problems, compatible block
and action
two kinds of callbacks.
Source code analysis
From the point of view source code is relatively simple, mainly divided into NSObject
of Category
and FBKVOController
two parts.
In Category
provided in the KVOController
and KVOControllerNonRetaining
two properties have a first name suggests observer
generating strong reference, the second is not. Its internal code is to create FBKVOController
code objects, and create objects out assigned to Category
properties directly through this Category
can lazily create FBKVOController
object.
- (FBKVOController *)KVOControllerNonRetaining
{
id controller = objc_getAssociatedObject(self, NSObjectKVOControllerNonRetainingKey);
if (nil == controller) {
controller = [[FBKVOController alloc] initWithObserver:self retainObserved:NO];
self.KVOControllerNonRetaining = controller;
}
return controller;
}
Realization principle
FBKVOController
is divided into three parts in _FBKVOInfo
, 06198dc4e878bc is a private class, the function of this class is very simple, it is to save FBKVOController
in a structured form, similar to the function of the model class.
There is also a private class _FBKVOSharedController
, which is the key to the implementation of the FBKVOController
It can be seen from the naming that it is a singleton, all the FBKVOController
implemented by KVO
, the observer is it. Every time FBKVOController
add a KVO
through 06198dc4e878ef, _FBKVOSharedController
will set itself as an observer, and implement the observeValueForKeyPath:ofObject:change:context:
method inside it, and forward the received message through block
or action
.
Its function is very simple, add KVO
observe:info:
method, and use a NSHashTable
save the _FBKVOInfo
information. Use the unobserve:info:
method to remove the monitor, and NSHashTable
the corresponding _FBKVOInfo
from 06198dc4e87923. Both methods will call the system's KVO
method internally.
For external use, the FBKVOController
class is required, which internally implements the operations of initialization and adding and removing monitors. After calling the add listener method, a _FBKVOInfo
object will be created internally and held by a NSMapTable
object, and then _FBKVOSharedController
will be called to register and listen.
Use FBKVOController
words, do not need to manually call removeObserver
method, when the listener objects disappear, will dealloc
call remove
method. If because of business needs, you can manually call the remove
method. Repeated calls to the remove
method will not cause problems.
- (void)_observe:(id)object info:(_FBKVOInfo *)info
{
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
return;
}
if (nil == infos) {
infos = [NSMutableSet set];
[_objectInfosMap setObject:infos forKey:object];
}
[infos addObject:info];
[[_FBKVOSharedController sharedController] observe:object info:info];
}
Because FBKVOController
is very simple, so here is a very simple talk, the specific implementation can go to Github download the source code and analyze it carefully.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。