Summary of iOS underlying principles –OC object nature (a) – digging gold
Summary of iOS underlying principles –OC object nature (ii) – digging gold
OC object classification: Instance, class, META -calss object ISA and Superclass – gold mining
Summary of basic principles of iOS — the essence of KVO/KVC — gold digging
Summary of iOS underlying principles – Using Runtime source code to analyze the low-level implementation of categories
.
I. Implementation principle of KVO
Interview questions:
1. How does iOS implement KVO for an object? (What is the nature of KVO?) 2. How to start KVO manually? 3. Does directly modifying a member variable trigger KVO? KVC related: 1. Does modifying attributes through KVC trigger KVO? 2. What is the assignment and value process of KVC? How does it work?Copy the code
1. What is KVO?
The full name of KVO is key-value Observing, commonly known as "key-value monitoring", which can be used to monitor an object whose properties are worth changing.Copy the code
2. Simple implementation of KVO
Let’s briefly review the code implementation of KVO.
/ / / > DLPerson. H file
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface DLPerson : NSObject
@property (nonatomic.assign) int age;
@end
NS_ASSUME_NONNULL_END
Copy the code
/ / / > ViewController. M file
#import "ViewController.h"
#import "DLPerson.h"
@interface ViewController(a)
@property (nonatomic.strong) DLPerson *person1;
@property (nonatomic.strong) DLPerson *person2;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[DLPerson alloc]init];
self.person1.age = 1;
self.person2 = [[DLPerson alloc]init];
self.person2.age = 2;
///> person1 adds kVO listener
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person1.age = 20;
self.person2.age = 30;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey.id> *)change context:(void *)context{
NSLog(@" listening for %@ property change %@",object,keyPath,change);
}
- (void)dealloc{
///> Remember to remove it after use
[self.person1 removeObserver:self forKeyPath:@"age"];
}
@end
@end
Copy the code
<DLPerson: 0x6000033D4e40 > - {kind = 1; new = 20; old = 1; } ///> Because we are only listening for person1, only changes to person1 will be printed.Copy the code
3. Problems existing in KVO
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// self.person1.age = 20;
[self.person1 setAge:20]; Self. Person1. age = 20;
self.person2.age = 30;
[self.person2 setAge:20];Self. Person2. age = 20;
}
Copy the code
Because when we use the @Property attribute in DLPerson we automatically generate the set and GET methods for the attribute. The following code:
/ / / > DLPerson. M file
#import "DLPerson.h"
@implementation DLPerson
- (void)setAge:(int)age{
_age = age;
}
- (int)age{
return _age;
}
@end
Copy the code
Since person1 and Person2 are essentially calling the set method, they must both walk through the setAge method in the DLPerson class.
So why does person1 go to the setAge method in the DLPerson class
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey.id> *)change context:(nullable void *)context;
Copy the code
Method and person2 doesn’t?
4. Essential analysis of KVO
If you are not familiar with the isa pointer of OC objects, please visit the underlying implementation of iOS. The classification of OC objects: instance, class, meta-calss isa and superclass – gold dig article.
Let’s explore the nature of the two objects. First let’s print out the isa for Person1 and Person2 and look at their instance objects. What is the class object isa points to?
NSKVONotifying_DLPerson
The ISA pointer for Person2 prints DLPerson
Person1 and person2 are instance objects so the ISA pointer to person1 and person2 points to class objects,
So, if the object has no KVO listener added, its ISA points to its original class object, as shown in the following figure
person2.isa == DLPerson
Copy the code
When a KVO listener is added to an object, the isa pointer to the current object points not to your original class, but to another class object, as shown below
person1.isa == NSKVONotifying_DLPerson
Copy the code
-
The NSKVONotifying_DLPerson class is a class created dynamically by the Runtime, and a new class is created during application execution.
-
The NSKVONotifying_DLPerson class is a subclass of DLPerson.
-
The NSKVONotifying_DLPerson class has its own setAge:, class, dealloc, isKVOA… Methods.
When our DLperson instance object calls the setAge method,
The ISA pointer to the instance object finds the class object, then looks for the corresponding object method in the class object and calls it, if any,
If not, look for the corresponding object method in the superclass object pointed to, if there is, call,
If no corresponding object method is found, the: unrecognized Selector sent to instance error is reported
-
The isa pointer to person1 points to NSKVONotifying_DLPerson, and NSKVONotifying_DLPerson has setAge:
//> The setAge method of person1 follows the setAge method of NSKVONotifying_DLPerson, ///> And implement [superclass setAge:age] in KVO listener; . [self.person1 setAge:20]; ///> The setAge method of person2 follows the setAge: method of DLPerson, [self.person2 setAge:30]; Copy the code
As you saw last time, the two instance objects person1 and person2 both follow DLPerson’s setAge: method, except that person1, which adds KVO, adds other operations to its own setAge method.
-
Other operations guess pseudocode! :
/ / / > NSKVONotifying_DLPerson. M file #import "NSKVONotifying_DLPerson.h" @implementation NSKVONotifying_DLPerson - (void)setAge:(int)age{ _NSSetIntValueAndNotify(); < span style = "max-width: 100%; clear: both; min-height: 1em } void _NSSetIntValueAndNotify(){ [self willChangeValueForKey:@"age"]; [super setAge:age]; [self didChangeValueForKey:@"age"]; } - (void)didChangeValueForKey:(NSString *)key{ ///> Notify that the listener key has changed [observe observeValueForKeyPath:key ofObject:self change:nil context:nil]; } @end Copy the code
_NSSetIntValueAndNotify(); < span style = "max-width: 100%; clear: both; min-height: 1em
5. Call sequence of KVO
Since our NSKVONotifying_DLPerson class does not participate in compilation, we can override the method code of its parent class in dlPerson. m as follows:
/ / / > ViewController. M file
#import "ViewController.h"
#import "DLPerson.h"
@interface ViewController(a)
@property (nonatomic.strong) DLPerson *person1;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[DLPerson alloc]init];
self.person1.age = 10;
///> person1 adds kVO listener
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.person1 setAge:20];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey.id> *)change context:(void *)context{
NSLog(@" listening for %@ property change %@",object,keyPath,change);
}
- (void)dealloc{
///> Remember to remove it after use
[self.person1 removeObserver:self forKeyPath:@"age"];
}
@end
Copy the code
/ / / > DLPerson. M file
#import "DLPerson.h"
@implementation DLPerson
- (void)setAge:(int)age{
_age = age;
}
- (void)willChangeValueForKey:(NSString *)key{
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey");
}
- (void)didChangeValueForKey:(NSString *)key{
NSLog(@"didChangeValueForKey - begin");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey - end");
}
@end
Copy the code
Output result:
WillChangeValueForKey didChangeValueForKey -begin listen on <DLPerson: 0x60000041afe0> age property changed {kind = 1; new = 20; old = 10; } didChangeValueForKey - endCopy the code
- Call willChangeValueForKey:
- Call the original setter implementation
- Call didChangeValueForKey:
Conclusion: didChangeValueForKey: internal calls the observer observeValueForKeyPath: ofObject: change: context: method
The realization principle of KVC
1. What is KVC?
Key-value-coding, the full name of KVC, is commonly known as "key-value coding ", which can access a property through keysCopy the code
Common apis are:
- (void)setValue:(id)value forKey:(NSString *)key;
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
Copy the code
Simple code implementation: DLPerson and DLCat
/ / / > DLPersin. H file
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** DLCat */
@interface DLCat : NSObject
@property (nonatomic.assign) int weight;
@end
/** DLPerson */
@interface DLPerson : NSObject
@property (nonatomic.assign) int age;
@property (nonatomic.strong) DLCat *cat;
@end
NS_ASSUME_NONNULL_END
Copy the code
1.1 – (void)setValue:(id)value forKey:(NSString *)key;
/ / / > ViewController. M file
- (void)viewDidLoad {
[super viewDidLoad];
DLPerson *person = [[DLPerson alloc]init];
[person setValue:@20 forKey:@"age"];
NSLog(@"%d",person.age);
}
Copy the code
1.1 – (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
/ / / > ViewController. M file
- (void)viewDidLoad {
[super viewDidLoad];
DLPerson *person = [[DLPerson alloc]init];
person.cat = [[DLCat alloc]init];
[person setValue:@20 forKeyPath:@"cat.weight"];
NSLog(@"%d",person.age);
NSLog(@"%d",person.cat.weight);
}
Copy the code
SetValue :(id)value forKeyPath:(NSString *)keyPath and setValue:(id)value forKey:(NSString *) Key
- KeyPath is the equivalent of finding properties by path, layer by layer,
- Key is the name of the property directly set, if you look by path will report an error
1.3 – (id) valueForKey (nsstrings *) key;
/ / / > ViewController. M file
- (void)viewDidLoad {
[super viewDidLoad];
DLPerson *person = [[DLPerson alloc]init];
person.age = 10;
NSLog(@ "% @",[person valueForKey:@"age"]);
}
Copy the code
1.4 – (id) valueForKeyPath (keyPath nsstrings *);
/ / / > ViewController. M file
- (void)viewDidLoad {
[super viewDidLoad];
DLPerson *person = [[DLPerson alloc]init];
person.age = 10;
NSLog(@ "% @",[person valueForKey:@"cat.weight"]);
}
Copy the code
(id)valueForKey:(NSString *)key; And (id)valueForKeyPath:(NSString *)keyPath:
- Similarly, 1.2 to 1.3
2. Principle of setValue:forKey
- When we setValue:forKey:
- SetKey:, _setKey: (in order)
- If there’s a direct call
- If not, check the accessInstanceVariablesDirectly method
+ (BOOL)accessInstanceVariablesDirectly{ return YES; ///> You can access member variables directly // return NO; ///> Cannot access member variables directly, ///> Direct access to the NSUnkonwKeyException error } Copy the code
- If accessible, member variables are searched in _key, _isKey, key, and ISkey order
- Find direct copy
- No NSUnkonwKeyException error found
_key, _isKey, key, iskey replication sequence
/ / / > DLPerson. H file
@interface DLPerson : NSObject{
@public
int _age;
int _isAge;
int age;
int isAge;
}
@end
Copy the code
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[DLPerson alloc]init];
///> person1 adds kVO listener
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
///> Modify the value of person.age via KVC
[self.person1 setValue:@20 forKey:@"age"];
NSLog(@ "-- -- -- -- -- -");
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey.id> *)change context:(void *)context{
NSLog(@" listening for %@ property change %@",object,keyPath,change);
}
- (void)dealloc{
///> Remember to remove it after use
[self.person1 removeObserver:self forKeyPath:@"age"];
}
Copy the code
- In the NSLog (@ “– — — — — -“); Type a short comment to see the order in which each is worth copying
- Then check the order of copy lookups in the console.
- It doesn’t matter if the order in the DLPerson file is reversed.
3. Principle of valueForKey
- The KVC value is searched in the sequence of getKey, key, iskey, and _key
- There are direct calls
- Couldn’t find the same, check the accessInstanceVariablesDirectly method
+ (BOOL)accessInstanceVariablesDirectly{ return YES; ///> You can access member variables directly // return NO; ///> Cannot access member variables directly, ///> Direct access to the NSUnkonwKeyException error } Copy the code
- If accessible, member variables are searched in _key, _isKey, key, and ISkey order
- Find direct copy
- No NSUnkonwKeyException error found
Three. Knowledge supplement
1. _NSSetIntValueAndNotify(); Methods the source
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[DLPerson alloc]init];
self.person1.age = 10;
self.person2 = [[DLPerson alloc]init];
self.person2.age = 20;
///> person1 adds kVO listener
NSLog(@"person before adding KVO - person1: %@, person2: %@",object_getClass(self.person1), object_getClass(self.person2));
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
NSLog(@"person before adding KVO - person1: %@, person2: %@",object_getClass(self.person1), object_getClass(self.person2));
}
Copy the code
Output result:
Person before KVO - person1: DLPerson, person2: DLPerson person after KVO - person1: NSKVONotifying_DLPerson, person2: DLPersonCopy the code
This shows that the person1.isa pointer is still DLPerson until KVO is added to person1
So we can use - (IMP)methodForSelector:(SEL)aSelector; To view the address of the implementation method, the specific method code is as follows:
///> person1 adds kVO listener
NSLog(@"person before adding KVO - person1: %p, person2: %p \n"[self.person1 methodForSelector:@selector(setAge:)], [self.person2 methodForSelector:@selector(setAge:)]);
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
NSLog(@"person after adding KVO - person1: %p, person2: %p \n"[self.person1 methodForSelector:@selector(setAge:)], [self.person2 methodForSelector:@selector(setAge:)]);
}
Copy the code
Output result:
Person before KVO - person1:0x10852a560, person2:0x10852a560 Person after KVO - person1:0x108883fc2, person2:0x10852a560
Copy the code
The setAge method for person1 has changed since the addition of the same setAge method for person2.
Then we type in the short to see the implementation method:
P (IMP) method address
setAge:
_NSSetIntValueAndNotify()
If the definition of the attribute is type is double is called _NSSetDoubleValueAndNotify () you can test by yourselves. This method in the frame of the Foundtion have corresponding NSSetDoubleValueAndNotify NSSetIntValueAndNotify () () NSSetCharValueAndNotify ()...Copy the code
At present, reverse engineering has not been deeply touched. After learning to explain to you in detail.
2. Where is the ISA pointer to NSKVONotifying_DLPerson?
In the analysis of the nature of KVO, we know that the isa pointer of the instance object with KVO listener added points to the class NSKVONotifying_DLPerson. What is the isa pointer of the class NSKVONotifying_DLPerson?
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[DLPerson alloc]init];
self.person1.age = 10;
self.person2 = [[DLPerson alloc]init];
self.person2.age = 20;
///> person1 adds kVO listener
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
NSLog(@" Class object - person1: %@<%p> person2: %@<%p>,
object_getClass(self.person1), / / / > self. Person1. Isa class name
object_getClass(self.person1), ///> self.person1.isa
object_getClass(self.person2), / / / > self. Person1. Isa class name
object_getClass(self.person2) ///> self.person1.isa
);
NSLog(@" Meta object - person1: %@<%p> person2: %@<%p>",
object_getClass(object_getClass(self.person1)), / / / > self. Person1. Isa. Isa class name
object_getClass(object_getClass(self.person1)), ///> self.person1.isa.isa
object_getClass(object_getClass(self.person2)), / / / > self. Person2. Isa. Isa class name
object_getClass(object_getClass(self.person2)) ///> self.person2.isa.isa
);
}
Copy the code
Output result:
Class object - person1: NSKVONotifying_DLPerson< 0x6000002CEF40 > Person2: DLPerson< 0x1002C9048 > Metaclass object - person1: NSKVONotifying_DLPerson<0x6000002cf210> person2: DLPerson<0x1002c9020>Copy the code
The results showed that: the address of each class object is different, and yuan the address of the class object is different also, so we can think NSKVONotifying_DLPerson class has its own metaclass object, NSKVONotifying_DLPerson. Isa object points to its own metaclass.
4. Answers to interview questions
-
How does iOS implement KVO for an object? (What is the nature of KVO?)
Use the RuntimeAPI to dynamically generate a subclass and have instance object's ISA point to the new subclass. When modifying an instance object's properties, It calls Foundation's _NSSetXXXValueAndNotify function willChangeValueForKey, the original setter for the parent class, didChangeValueForKey, Internal will trigger the listener (Oberser) surveillance methods (observeValueForKeyPath: ofObject: change: context:)Copy the code
-
How do I trigger KVO manually?
Manually call willChangeValueForKey and didChangeValueForKey:Copy the code
-
Does modifying a member variable directly trigger KVO?
KVO is not triggered because modifying a member variable directly does not go through the set method.Copy the code
KVC related:
-
Does modifying properties via KVC trigger KVO?
KVO is triggered, as shown in the flowchart aboveCopy the code
-
What is the assignment and evaluation process of KVC? How does it work?
Flow chart aboveCopy the code
- The article is summarized from MJ teacher’s bottom video.