KVO
noun
Behavioral patterns are abstractions that divide responsibilities and algorithms among different objects. Behavioral patterns focus not only on the structure of classes and objects, but also on their interactions.
Behavioral patterns allow for a clearer division of the responsibilities of classes and objects, and for studying the interaction between instance objects at run time. When the system is running, objects are not isolated, they can communicate with each other and cooperate to complete some complex functions. One object will also affect the operation of other objects when it is running.
Behavioral pattern can be divided into class behavior pattern and object behavior pattern:
- Class behavior pattern: The behavior pattern of a class uses inheritance relationships to distribute behavior among several classes. Class behavior pattern mainly distributes responsibilities of parent and subclass by means of polymorphism.
- Object behavior pattern: The object behavior pattern uses the aggregate relationship of objects to assign behaviors. The object behavior pattern mainly assigns responsibilities of two or more classes through object association. According to the principle of composite reuse, association relation should be used to replace inheritance relation, so most behavior design pattern belongs to object behavior design pattern.
The Composite Resuse Principle, also known as the Composite/aggregate reuse Principle, requires that when software is reused, it should be realized by combining or aggregating relationships as much as possible, and then inheritance relationships should be considered.
Observer design pattern
The observer pattern is defined as a one-to-many dependency of multiple objects. When an object’s state changes, all dependent objects are notified and automatically updated. It is the behavior object pattern.
- advantages
- The coupling relationship between the target and the observer is abstract
- A set of triggers is established between the target and the observer.
- disadvantages
- The dependency between target and observer is not completely removed, and circular references are possible.
- When there are many observers, the presentation of notifications takes a lot of time, affecting the efficiency of the program.
-
Pattern structure
- Abstract Subject role: Also called abstract target class, it provides an aggregate class for holding observer objects and methods for adding and deleting observer objects, as well as abstract methods for notifying all observers.
- Concrete Subject roles, also known as Concrete target classes, implement notification methods in abstract classes that notify all registered observer objects when the internal state of a Concrete Subject changes.
- Abstract Observer role: It is an abstract class or interface that contains an abstract method that updates itself and is called when notified of changes to a specific topic.
- Concrete Observer role: Implements an abstract method defined in an abstract Observer to update its own state when it is notified of changes to the target.
-
UML diagrams
define
KVO, which stands for Key Value Observing, is a set of event notification mechanism provided by Apple. Allows an object to listen for changes to specific properties of another object and receive events when they change.
API
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
Copy the code
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
Copy the code
How to use
self.person1 = [[Person alloc] init]; // addObserver [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; self.person1.age = 20; // removeObserver [self.person1 removeObserver:self forKeyPath:@"age"]; self.person1.age = 100;Copy the code
- (void) observeforkeypath :(NSString *)keyPath ofObject:(id)object Change (NSDictionary<NSKeyValueChangeKey,id> *)change context (void *)context{NSLog(@" object %@ value %@ change %@ context %@",object,keyPath,change,context); }Copy the code
Start with questions
- Where the registered Observer is located, what data structures are stored, and how they are removed.
- What happens to the observer after the addObserver operation is performed, how the observed changes, and whether it can implement a KVO by itself.
- Compare KVO, Delegate, and Block. “To be continued”
- What are the common problems of KVO? Is there any good solution? Give examples.
How to discover the essence of KVO step by step
Execute the following code:
- (void)func { self.person1 = [[Person alloc] init]; self.person2 = [[Person alloc] init]; NSLog (@ "before"); NSLog(@"person1 changes --%@ --%@",[self.person1 class],object_getClass(self.person1)); NSLog(@"person2 change --%@--%@",[self.person2 class],object_getClass(self.person2)); [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; NSLog (@ "after"); NSLog(@"person1 changes --%@--%@",[self.person1 class],object_getClass(self.person1)); NSLog(@"person2 change --%@--%@",[self.person2 class],object_getClass(self.person2)); }Copy the code
Print the result
Person Person before Person Person Person after Person NSKVONotifying_Person PersonCopy the code
Finish can be found that adding addObserver: forKeyPath: options: context: after the results can be seen that:
self.person1
theclass
The methods andself.person2
theclass
The same result is returned.object_getClass
The obtained results are as followsNSKVONotifying_Person
.Person
And it’s also shown hereself.person1
The ISA direction has changed fromPerson
Modified in order toNSKVONotifying_Person
.
To verify the relationship between NSKVONotifying_Person and Person, execute the following code.
NSLog(@"%p",class_getSuperclass(object_getClass(self.person1)));
NSLog(@"%p",object_getClass(self.person2));
Copy the code
Print result:
0x101582eb0
0x101582eb0
Copy the code
Here’s the conclusion :(note that self.person1 has added addObserver)
SuperClass class object class object object | | | | — – | — — — — — — — — — — – | — — — — — — — — — — – | — — — — — — — — — — – | the self. The person1 | NSKVONotifying_Person | Person | self.person2 | Person |NSObject
NSKVONotifying_Person is a subclass of Person. When was the NSKVONotifying_Person generated?
- (void)func2 {
Class cla1 = NSClassFromString(@"NSKVONotifying_Person");
[self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
Class cla2 = NSClassFromString(@"NSKVONotifying_Person");
NSLog(@"%@ %@",cla1,cla2);
}
Copy the code
If only CLA2 has a value, NSKVONotifying_Person is generated only after addObserver.
Back to the class method, print the following method:
- (void)printMethodNamesOfClass:(Class)cls{
unsigned int count;
Method *methods = class_copyMethodList(cls, &count);
NSMutableString *methodNames = [[NSMutableString alloc] init];
for (unsigned int i = 0; i < count; i++) {
Method method = methods[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
[methodNames appendString:methodName];
[methodNames appendString:@","];
}
free(methods);
NSLog(@"cls == %@ methods == %@",cls,methodNames);
cls = class_getSuperclass(cls);
if (cls && cls != [NSObject class]) {
[self printMethodNamesOfClass:cls];
}
}
Copy the code
cls == Person methods == data,setData:,.cxx_destruct,block,height,setHeight:,setBlock:,age,setAge:,
cls == NSKVONotifying_Person methods == setAge:,class,dealloc,_isKVOA,
cls == Person methods == data,setData:,.cxx_destruct,block,height,setHeight:,setBlock:,age,setAge:
Copy the code
Note The newly generated NSKVONotifying_Person has more setAge:,class,dealloc,_isKVOA methods than Person. This is why we get [self.person1 class] == [self.person2 class].
What is the implementation of setAge in NSKVONotifying_Person? Execute the following code
- (void)func{ Method method = class_getInstanceMethod(object_getClass(self.person1), @selector(setAge:)); IMP imp = method_getImplementation(method); NSLog(@"2323"); }Copy the code
(IMP) imp = 0x00007fff207a7195 (Foundation`_NSSetLongLongValueAndNotify)
Copy the code
Specific implementation is _NSSetLongLongValueAndNotify functions in a Fundation.
Let’s try the name attribute
[self.person1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
Method method = class_getInstanceMethod(object_getClass(self.person1), @selector(setName:));
IMP imp = method_getImplementation(method);
Copy the code
(IMP) imp = 0x00007fff207a6553 (Foundation`_NSSetObjectValueAndNotify)
Copy the code
Presentation according to the attribute types corresponding to different implementation methods _NSSetLongLongValueAndNotify, _NSSetObjectValueAndNotify, _NSSetXAndNotify
What about the internal implementation of _NSSetXAndNotify? Very curious.
Search on the Internet, but no specific implementation can be found, Hoper speculation, using Xcode compilation.
The DIS_KVC_KVO repository restores the specific implementation, with pseudocode as follows.
[object willChangeValueForKey:key];
IMP imp = class_getMethodImplementation(info->originalClass, selector);
setValueWithImplementation(imp);
[object didChangeValueForKey:key];
Copy the code
void DSKeyValueDidChange(id object, id keyOrKeys){ DSKeyValueNotifyObserver(......) ; } void DSKeyValueNotifyObserver(id observer,NSString * keyPath, id object, void *context){ DSKVONotify(observer, keyPath, object, *changeDictionary, context); } void DSKVONotify(id observer, NSString *keyPath, id object, NSDictionary *changeDictionary, void *context) { [observer observeValueForKeyPath:keyPath ofObject:object change:changeDictionary context:context]; }Copy the code
_NSSetObjectValueAndNotify do these three things.
- call
willChangeValueForKey
methods - Calling the parent class
setKey
methods - call
didChangeValueForKey
Method, in which the specific observer is notified that the property has changed.
Conclusion:
- Generate a new to
NSKVONotifying_
Is prefixed with the new Class and inherits the original Class, and then points the object’s ISA to thisNSKVONotifying_
Class. - rewrite
Class
.setKey
.dealloc
Method, add_isKVOA
Methods. One thing to noteNSKVONotifying_
The concrete implementation of the setKey method of_NSSetXAndNotify
This way.
How is the observer stored
Found NSKeyValueObservationInfo
NSObject has this property,observationInfo
@property (nullable) void *observationInfo
Copy the code
Execute the following code and interrupt the point.
NSLog(@"%@",self.person1.observationInfo);
Copy the code
Print after we find observationInfo is actually NSKeyValueObservationInfo instance of the object, is hidden from us.
Per objectobservationInfo
Will be stored in a global Dictionary, and the key is the address of the current object.
Fix NSKeyValueObservationInfo
By the method of printing observationInfo instance variables, properties, and so on, can think bold NSKeyValueObservationInfo structure is as follows.
@interface NSKeyValueObservationInfo : NSObject
{
NSArray <NSKeyValueObservance*> *_observances;
long long _cachedHash;
bool _cachedIsShareable;
}
- (instancetype)initWithObservances:(NSArray*)observances
count:(NSInteger)count
hashValue:(BOOL)hashValue;
@end
Copy the code
@interface NSKeyValueObservance : NSObject
{
NSObject *_observer;
NSObject *_originalObservable;
NSKeyValueProperty *_property;
NSKeyValueObservingOptions _options;
void * _context;
}
- (instancetype)initWithObserver:(NSObject*)observer
property:(NSKeyValueProperty*)propert
options:(NSKeyValueObservingOptions)options
context:(void *)context
originalObservable:(NSObject*)originalObservable;
@end
@interface NSKeyValueProperty : NSObject{
NSString *keyPath;
}
@end
Copy the code
How do I implement KVO myself
How do I implement KVO myself
The key code
- Create a new class
Class originalClass = object_getClass(self);
Class kvoClazz = objc_allocateClassPair(originalClass, kvoClassName.UTF8String, 0);
Method clazzMethod = class_getInstanceMethod(originalClass, @selector(class));
const char *types = method_getTypeEncoding(clazzMethod);
class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class,types);
objc_registerClassPair(kvoClazz);
Copy the code
- Modify pointer pointing
clazz = newClass
object_setClass(self, clazz);
Copy the code
- Override the set method to point IMP to your own setter
const char *types = method_getTypeEncoding(setterMethod);
class_addMethod(clazz, setterSelector, (IMP)kvo_setter, types);
Copy the code
- Call the set method of the parent class
struct objc_super supperclasszz = {.receiver = self,.super_class = class_getSuperclass(object_getClass(self))};
void (*objc_msgSendSuperCasted)(void*,SEL,id) = (void*)objc_msgSendSuper;
objc_msgSendSuperCasted(&supperclasszz,_cmd,newValue);
Copy the code
KVO Common flash rollback problems
- The number of KVO additions and removals does not match
- Add or remove
keypath == nil
, resulting in a crash. - An observer was added but not implemented
observeValueForKeyPath:ofObject:change:context:
Method, causing a crash - The subject was released early, and the subject was still registered with KVO while in Dealloc, causing a crash. For example, the observer is a local variable (iOS 10 and before crashes).
How is FBKVOController solved
FBKVOController source code analysis
A link to the
The KVO principle analyzes the design patterns of behavioral pattern observers