Underlying implementation principle of KVO and underlying principle of kvo
KVO is the basis for implementing Cocoa Bindings. It provides a method to notify the corresponding objects when a property changes. In other languages, this observer mode usually needs to be implemented independently, while in Objective-C, it can be used without additional code,
How is this implemented? It is actually implemented through the powerful runtime of OC. When you observe an object for the first time, runtime creates a new subclass that inherits the original class. In this new class, it overwrites all the observed keys, next, point the isa pointer of OC to the newly created class (this pointer tells oc which type of object is an object at runtime ). So the object becomes a new subclass instance;
These override methods implement how to notify the observer. When a key is changed, the setkey method is triggered, but this method is overwritten, And the sending notification mechanism is added internally. (Of course, I can also leave the set method, for example, directly modifying ivar, but this is not recommended ).
Interestingly, Apple does not want this mechanism to be exposed externally. In addition to setters, this dynamically generated subclass also overwrites the-class method and returns the original class! If you do not take a closer look, the objects that have been passed by KVO will look like the original objects.
The following is a program that demonstrates the mechanism hidden behind KVO.
- // Gcc-o kvoexplorer-framework Foundation kvoexplorer. m
- # Import <Foundation/Foundation. h>
- # Import <objc/runtime. h>
-
-
- @InterfaceTestClass: NSObject
- {
- IntX;
- IntY;
- IntZ;
- }
- @ PropertyIntX;
- @ PropertyIntY;
- @ PropertyIntZ;
- @ End
-
- @ Implementation TestClass
- @ Synthesize x, y, z;
- @ End
-
- StaticNSArray * ClassMethodNames (Class c)
- {
- NSMutableArray * array = [NSMutableArray array];
- UnsignedIntMethodCount = 0;
- Method * methodList = class_copyMethodList (c, & methodCount );
- UnsignedIntI;
- For(I = 0; I <methodCount; I ++)
- [Array addObject: NSStringFromSelector (method_getName (methodList [I])];
- Free (methodList );
- ReturnArray;
- }
-
- Static VoidPrintDescription (NSString * name, id obj)
- {
- NSString * str = [NSString stringWithFormat:
- @ "% @: % @ \ N \ tNSObject class % s \ n \ tlibobjc class % s \ n \ timplements methods <% @> ",
- Name,
- Obj,
- Class_getName ([objClass]),
- Class_getName (obj-> isa ),
- [ClassMethodNames (obj-> isa) componentsJoinedByString: @ ","];
- Printf ("% s \ n", [str UTF8String]);
- }
-
- IntMain (IntArgc,Char** Argv)
- {
- [NSAID utoreleasepoolNew];
- TestClass * x = [[TestClass alloc] init];
- TestClass * y = [[TestClass alloc] init];
- TestClass * xy = [[TestClass alloc] init];
- TestClass * control = [[TestClass alloc] init];
- [X addObserver: x forKeyPath: @ "x" options: 0 context: NULL];
- [Xy addObserver: xy forKeyPath: @ "x" options: 0 context: NULL];
- [Y addObserver: y forKeyPath: @ "y" options: 0 context: NULL];
- [Xy addObserver: xy forKeyPath: @ "y" options: 0 context: NULL];
- PrintDescription (@ "control", control );
- PrintDescription (@ "x", x );
- PrintDescription (@ "y", y );
- PrintDescription (@ "xy", xy );
- Printf ("Using NSObject methods, normal setX: is % p, overridden setX: is % p \ n ",
- [Control methodForSelector: @ selector (setX :)],
- [X methodForSelector: @ selector (setX :)]);
- Printf ("Using libobjc functions, normal setX: is % p, overridden setX: is % p \ n ",
- Method_getImplementation (class_getInstanceMethod (object_getClass (control ),
- @ Selector (setX :))),
- Method_getImplementation (class_getInstanceMethod (object_getClass (x ),
- @ Selector (setX :))));
- Return0;
- }
Let's take a closer look from start to end. First, a class of TestClass is defined. It has three attributes. Then some convenient debugging methods are defined. ClassMethodNames uses the Objective-C Runtime method to traverse a class and obtain the method list. Note that these methods do not include the methods of the parent class. PrintDescription prints all the information about the object, including the class information (including-class and the class obtained through the operation), and the implementation method of this class. Then four TestClass instances are created, each of which uses different observation methods. An observer of x instance observes xkey, y, and xy. For comparison, zkey has no observer. The last control instance does not have any observer. Then, four objects descriptions are printed. Then print the rewritten setter memory address and the memory address of the unoverwritten setter for comparison. This is done twice because-methodForSelector: The method cannot be rewritten. KVO tries to conceal the fact that it actually created a new subclass! However, the running method is invisible.
Run codeLet's look at the output of this Code.
- Control: <TestClass: 0x0000b20>
- NSObjectClassTestClass
- LibobjcClassTestClass
- ImplementsMethods <setX:, x, setY:, y, setZ:, z>
- X: <TestClass: 0 x103280>
- NSObjectClassTestClass
- LibobjcClassNSKVONotifying_TestClass
- ImplementsMethods <setY:, setX :,Class, Dealloc, _ isKVOA>
- Y: <TestClass: 0x0000b00>
- NSObjectClassTestClass
- LibobjcClassNSKVONotifying_TestClass
- ImplementsMethods <setY:, setX :,Class, Dealloc, _ isKVOA>
- Xy: <TestClass: 0x0000b10>
- NSObjectClassTestClass
- LibobjcClassNSKVONotifying_TestClass
- ImplementsMethods <setY:, setX :,Class, Dealloc, _ isKVOA>
- Using NSObject methods, normal setX: is 0x195e, overridden setX: is 0x195e
- Using libobjc functions, normal setX: is 0x195e, overridden setX: is 0x96a1a550
First, it outputs controlobject. There is no problem. Its class is TestClass and six set/get methods are implemented. Then there are three observed objects. Note: The class still displays TestClass, and object_getClass is used to show the true face of this object: it is an instance of NSKVONotifying_TestClass. This NSKVONotifying_TestClass is a dynamically generated subclass! Note how it implements the two observed setters. You will find that it is very clever and has not been rewritten-setZ:, although it is also a setter, because it is not observed. At the same time, it is noted that the three instances correspond to the same class, that is, both setters are overwritten, even though two instances only observe one attribute. This will bring about a little efficiency problem, because even if the property is not observed, it will go through the rewritten setter, but apple obviously thinks this score is better to generate a dynamic subclass, I also think this is a correct choice. You will see three other methods. There is a previously mentioned-class method that has been rewritten. Pretend that you are still the original class. There is also the-dealloc method to deal with some final work. There is also a _ isKVOA method, which looks like a private method. Next, we will output the implementation of-setX. Use-methodForSelector: returns the same value. Because-setX: has been rewritten in the subclass, this means that methodForSelector: Uses-class in the internal implementation and gets the wrong result. Finally, we get different output results through the runtime. As an excellent explorer, let's go to debugger to see how the second method is implemented:
- (Gdb) print (IMP) 0x96a1a550
- $1 = (IMP) 0x96a1a550 <_ NSSetIntValueAndNotify>
It looks like an internal method. Use nm-a for Foundation to get a complete list of private methods:
- 0013df80 t _ NSSetBoolValueAndNotify
- 000a00000t _ NSSetCharValueAndNotify
- 0013e120 t _ NSSetDoubleValueAndNotify
- 0013e1f0 t _ NSSetFloatValueAndNotify
- 000e3550 t _ NSSetIntValueAndNotify
- 0013e390 t _ NSSetLongLongValueAndNotify
- 0013e2c0 t _ NSSetLongValueAndNotify
- 00089df0 t _ NSSetObjectValueAndNotify
- 0013e6f0 t _ NSSetPointValueAndNotify
- 0013e7d0 t _ NSSetRangeValueAndNotify
- 0013e8b0 t _ NSSetRectValueAndNotify
- 0013e550 t _ nsset1_valueand1_y
- 0008ab20 t _ NSSetSizeValueAndNotify
- 0013e050 t _ NSSetUnsignedCharValueAndNotify
- 0009fcd0 t _ NSSetUnsignedIntValueAndNotify
- 0013e470 t _ NSSetUnsignedLongLongValueAndNotify
- 0009fc00 t _ NSSetUnsignedLongValueAndNotify
- 0013e620 t _ nssetunsigned1_valueandnotify
This list also finds some interesting things. For example, Apple writes the corresponding implementation for each primitive type. Objective-C objects only use _ NSSetObjectValueAndNotify, but a complete set is required to correspond to the rest, and it does not seem completely implemented, such as long dobule or _ Bool. The method is not even provided for the general pointer type (generic pointer type. Therefore, attributes not in this method list do not actually support KVO. KVO is a powerful tool, sometimes too powerful, especially with the automatic notification trigger mechanism. Now you know how it is implemented internally. This knowledge may help you better use it, or facilitate debugging when it fails.