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
  1. The coupling relationship between the target and the observer is abstract
  2. A set of triggers is established between the target and the observer.
  • disadvantages
  1. The dependency between target and observer is not completely removed, and circular references are possible.
  2. When there are many observers, the presentation of notifications takes a lot of time, affecting the efficiency of the program.
  • Pattern structure

    1. 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.
    2. 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.
    3. 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.
    4. 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.person1theclassThe methods and self.person2theclassThe same result is returned.
  • object_getClass The obtained results are as followsNSKVONotifying_Person .PersonAnd it’s also shown hereself.person1The ISA direction has changed fromPersonModified 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.

  • callwillChangeValueForKey methods
  • Calling the parent classsetKeymethods
  • calldidChangeValueForKey Method, in which the specific observer is notified that the property has changed.

Conclusion:

  • Generate a new toNSKVONotifying_Is prefixed with the new Class and inherits the original Class, and then points the object’s ISA to thisNSKVONotifying_Class.
  • rewriteClass.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 removekeypath == nil, resulting in a crash.
  • An observer was added but not implementedobserveValueForKeyPath: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