Summary of basic principles of iOS

  • The context context parameter in the observer prevents duplication of names (attributes of the same name observed by multiple objects), performance, code readability, and security
  • The observer must be removed in the dealloc method, otherwise the program will crash.
[self.student addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
- (void)dealloc{
    [self.student removeObserver:self forKeyPath:@"name" context:NULL];
}
Copy the code
  • The singleton’s attribute observer observes the same attribute name in both controllers. If it is not removed, it will cause a wild pointer, which observer cannot be determined and crash

[self.student addObserver:self forKeyPath:@”name” options:(NSKeyValueObservingOptionNew) context:NULL]; In, no circular reference is generated, and the attribute observer added underneath is the weak-saved self

The singleton object always exists in the memory, and the name attribute is a copy. When the name value changes, both observers will receive the notification, and it is not clear which object needs to be processed, which belongs to the wild notification.

KVO’s first experience

KVO steps:

  • Add observation
  • observeThe callback
  • Change the value of the observation property in the appropriate place
  • indeallocInside remove observation
- (void)viewDidLoad {
    [super viewDidLoad];
    self.person  = [LGPerson new];
    [self.person addObserver:self forKeyPath:@"nick" options:NSKeyValueObservingOptionNew context:NULL];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.person.nick = [NSString stringWithFormat:@"%@+",self.person.nick];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@",change);
}
- (void)dealloc{
    [self.person removeObserver:self forKeyPath:@"dateArray"];
}
Copy the code

Other uses of KVO

1. Switch between manual and automatic

  • Auto switch (default)
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
    return YES;
}
Copy the code
  • Manual switch
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
    return NO;
}

- (void)setNick:(NSString *)nick{
    [self willChangeValueForKey:@"nick"];
    _nick = nick;
    [self didChangeValueForKey:@"nick"];
}
Copy the code

2. Path processing

@implementation LGPerson -- writtenData/totalData + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{ NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; if ([key isEqualToString:@"downloadProgress"]) { NSArray *affectingKeys = @[@"totalData", @"writtenData"]; keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys]; } return keyPaths; } - (NSString *)downloadProgress{if (self.writtenData == 0) {self.writtenData = 1.0; } if (self.totalData == 0) { self.totalData = 100; } return [[nsstrings alloc] initWithFormat: @ "% f", 1.0 f * self writtenData/self totalData]; } @end @implementation LGViewController - (void)viewDidLoad { [super viewDidLoad]; self.person = [LGPerson new]; / / 4: [self.person addObserver:self forKeyPath:@"downloadProgress" options:(NSKeyValueObservingOptionNew) context:NULL]; } @endCopy the code

3. Listen on array properties

  • For collection type, it belongs to key-value observation, based on KVC, can not directly add elements, need to save the array mutableArrayValueForKey
@interface LGPerson : NSObject @property (nonatomic, strong) NSMutableArray *dateArray; @end @implementation LGViewController - (void)viewDidLoad { [super viewDidLoad]; self.person = [LGPerson new]; Self.person. dateArray = [NSMutableArray arrayWithCapacity:1]; [self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL]; } - (void)touch began :(NSSet< touch *> *)touches withEvent:(UIEvent *)event{// KVC set array [[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"1"]; }Copy the code
/* Possible values in the NSKeyValueChangeKindKey entry in change dictionaries. See the comments for -observeValueForKeyPath:ofObject:change:context: for more information. */ typedef NS_ENUM(NSUInteger, NSKeyValueChange) { NSKeyValueChangeSetting = 1, Attribute NSKeyValueChangeInsertion = 2, collection types NSKeyValueChangeRemoval = 3, NSKeyValueChangeReplacement = 4,};Copy the code

Principle analysis of KVO

KVO can only observe properties, which have setter methods, not member variables

  • Click the screen below to output only property changes, no changes observed in member variables

KVO forms the intermediate class

  • The isa of the person object points to the LGPerson class. After the middle class is generated, ISA no longer points to the LGPerson class. After the observer is added, isa of the Person object points to the derived middle class NSKVONotifying_LGPerson
  • To obtain the current ISA status, use object_getClassName(self.person), print the following

  • nickNameaddaddObserverThe ISA pointing class changes before and after

View the subclass before and after adding observer first traverses the class and view the subclass

Prints subclass changes before and after addObserver is added

What’s in the middle class NSKVONotifying_LGPerson? We iterate through the method -ivar-property

Print result:

All methods in NSKVONotifying_LGPerson:setNickName:.class.dealloc(Release monitor),_isKVOA(Whether KVO or not)

SetNickName: is the method inherited from LGPerson or overridden?

  • Create a subclass of LGPerson, LGStudent, and check the methods in LGStudent. If LGStudent has all the methods in NSKVONotifying_LGPerson (setNickName:.class.dealloc(Release monitor),_isKVOA(KVO), then all methods in NSKVONotifying_LGPerson come from inheritance

The print finds that there are no methods in LGStudent, so all methods in NSKVONotifying_LGPerson come from overrides

Both of these methods are derived from overwriting, so overwriting setName in LGStudent: can be verified by printing out the setName: method in LGStudent

When you add an observer isa points to the middle class NSKVONotifying_LGPerson, when will ISA come back to LGPerson? Was it when you removed the observer? See the pointer before and after removing the observer in the dealloc destructor.

  • After removing the observer, continue traversingLGPersonClasses and subclasses

Print result:

After removing the observer, NSKVONotifying_LGPerson will always exist after it is registered in memory, so as to prevent the program from adding observers and repeatedly creating NSKVONotifying_LGPerson memory space, which will waste performance

  • So we’re going to look at subclassessetter, or superclasssetterchangenickName

Watchpoint set variable self->_person->_nickName

Find setNickName by setting the watch point: between willChange and didChange

Call [super setNickName:] from NSKVONotifying_LGPerson

Custom KVO

Realize the addObserver

Verify that setter methods exist

  • Do not let instance member variables in. If you observe the member variable name, an error is reported

Dynamically subclassing

  • The ivAR space is initialized and allocated in read_images. The ivar space exists in RO, clean memory, and cannot be added.
  • Methods and attributes added In RWE, dirty Memory, can be added.

Pointing to the isa

Changes in property values are notified to custom methods

  • Came tolg_setterMethod, change the nickName value of the parent class, and send a message to the parent classsetNickName:, redefineobjc_msgSendSuper, pass in three parametersobjc_superParent structure pointer,_cmd(setNickNameMethod:,newValueThe newly changed value goes into the custom subclassLGKVONotifying_LGPersonThe parent classLGPersonthesetNickName:In the method

  • Disable the number of strict LLVM verification parameters. No further action is requiredobjc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)Methods an error

  • The system observer self.person.class should point to LGPerson before and after the observation, while the custom observer self.person.class points to LGKVONotifying_LGPerson

Class(object_getClass(self));

One note: replacing object_getClass(self) with [self class] in the following two codes will cause an error

struct objc_super superStruct = {
   .receiver = self,
   .super_class = class_getSuperclass(object_getClass(self)),
};
Copy the code
Class lg_class(id self,SEL _cmd){
    return class_getSuperclass(object_getClass(self));
}
Copy the code
  • The benefits of objc_msgSendSuper message sending: generic, encapsulated, resolved dependencies that don’t depend on the class LGPerson or NSObject, whether the class is NSObject or LGPerson

  • The method in setNickName: receives the nickName and sends a message to notify the observer controller VC when the attribute value changes

  • Does adding an associated object create a circular reference? No, because the associated object is stored in a hash table, there is no holding, one-to-one correspondence saving, controller VCpop can still enter the dealloc method, there is no circular reference

If you want to view multiple attributes, this will save multiple observer VC, so you need to optimize.

  • Create a LGKVOInfo class to hold the observer information

  • Changed the logic for saving observer information in categories

Remove observer

The above custom KVO is through the method of value transmission. After the change of value is observed, it is notified in different methods. Is there a functional way y=f(x), which is coupled with the adding observer?

Custom function KVO