This is the 8th day of my participation in the August More text Challenge. For details, see: August More Text Challenge
review
After covering the basic use of KVO and how to customize KVO in previous posts, this blog will look at FBKVOController, an excellent KVO tripartite library.
KVO for iOS (1) — Introduction to KVO
Exploration of iOS Underlying KVO(ii) — Analysis of KVO principle
KVO for iOS (3) – Custom KVO
KVO for iOS (4) – Custom KVO
FBKVOController is a functional programming implementation that does not remove observers.
1. Brief introduction to FBKVOController
FBKVOController is a Facebook open source framework based on system KVO implementation. Supports Objective-C and Swift languages.
Making the address
Key-value observation is a particularly useful technique for communicating between layers in a model-view-controller application. KVOController is built on Cocoa’s time-tested implementation of key-value observation. It provides a simple, modern API, which is also thread-safe.
The advantages of KVOController are as follows:
1.1 KVOController advantages
- Use blocks, custom operations, or
NSKeyValueObserving
Callback notifications. - No additional removal observers are required
- Implicitly remove the observer from the controller dealloc.
- Has a special thread-safe protection mechanism to prevent observer resurrection
- For more information about KVO, see Apple’s Key-Value Observation Overview.
1.2 FBKVOControlleruse
The FBKVOController is very simple to use, with very little code. The FBKVOController simply uses the following code:
FBKVOController
use
self.person = [[JPPerson alloc] init];
self.person.name = @"RENO";
self.person.age = 18;
self.person.mArray = [NSMutableArray arrayWithObject:@ "1"];
[self.kvoCtrl observe:self.person keyPath:@"age" options:0 action:@selector(jp_observerAge)];
[self.kvoCtrl observe:self.person keyPath:@"name" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
NSLog(% @ "* * * * @ * * * *",change);
}];
[self.kvoCtrl observe:self.person keyPath:@"mArray" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
NSLog(% @ "* * * * @ * * * *",change);
}];
Copy the code
The code is very clean, and we don’t have to remove observers from dealloc like we did with KVO on the system, so this wave of use is great!
- Lazy load initialization
#pragma mark - lazy
- (FBKVOController *)kvoCtrl{
if(! _kvoCtrl) { _kvoCtrl = [FBKVOController controllerWithObserver:self];
}
return _kvoCtrl;
}
Copy the code
2. KVOController implementation analysis
2.1 Intermediary mode
We usually buy a house, rent a house will find the intermediary, through the intermediary can find the right house faster and more efficient, also a lot of things intermediary to help us to do, we do not have to find the housing.
The KVOController mainly uses the mediator mode. The trouble with the official KVO is that it requires a trilogy. The core of KVOController is to encapsulate the trilogy at the bottom level, and the upper level only needs to care about the business logic.
The FBKVOController handles registration, removal, and callbacks (including the block, action, and observe callback for the compatible system). Is the exposed interaction class. Using FBKVOController involves two steps:
- use
controllerWithObserver
Initialize theFBKVOController
Instance. - use
observe:
Register.
2.2 Initialization of FBKVOController
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
{
self = [super init];
if (nil! =self) {
_observer = observer;
NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
_objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
pthread_mutex_init(&_lock, NULL);
}
return self;
}
Copy the code
The _observer is a property of FBKVOController, which is characterized by weak
@property (nullable.nonatomic.weak.readonly) id observer;
Copy the code
Because the FBKVOController itself is held by the observer, it is a modifier of the weak type.
_objectInfosMap according to retainObserved for NSMapTable memory management/initial configuration, FBKVOController member variable. It holds multiple _FBKVOInfo for each observed object (that is, multiple keyPath for the observed object) :
NSMapTable<id.NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
Copy the code
The _FBKVOInfo is placed in NSMutableSet, indicating that it is deweighted.
2.3 FBKVOController registered
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
{
NSAssert(0! = keyPath.length &&NULL! = block,@"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);
if (nil == object || 0 == keyPath.length || NULL == block) {
return;
}
// create info
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
// observe object with info
[self _observe:object info:info];
}
Copy the code
-
The first step is to make some judgements, fault-tolerant judgements.
-
Construct _FBKVOInfo and save FBKVOController, keyPath, options, and block.
-
Call _observe:(id)object info:(_FBKVOInfo *)info
-
_FBKVOInfo
@implementation _FBKVOInfo
{
@public
__weak FBKVOController *_controller;
NSString *_keyPath;
NSKeyValueObservingOptions _options;
SEL _action;
void *_context;
FBKVONotificationBlock _block;
_FBKVOInfoState _state;
}
Copy the code
Some relevant data information is stored in _FBKVOInfo
- rewrite
isEqual
withhash
methods
- (NSUInteger)hash
{
return [_keyPath hash];
}
- (BOOL)isEqual:(id)object
{
if (nil == object) {
return NO;
}
if (self == object) {
return YES;
}
if(! [object isKindOfClass:[self class]]) {
return NO;
}
return [_keyPath isEqualToString:((_FBKVOInfo *)object)->_keyPath];
}
Copy the code
It’s the same object as long as the _keyPath is the same
- _observe: info:
- (void)_observe:(id)object info:(_FBKVOInfo *)info
{
// lock
pthread_mutex_lock(&_lock);
// Obtain the set corresponding to object from TableMap
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// check for info existence
// Check whether the corresponding keypath info exists
_FBKVOInfo *existingInfo = [infos member:info];
if (nil! = existingInfo) {// There is a direct return, which is equivalent to excluding the same keypath for the same observer
// observation info already exists; do not observe it again
// unlock and return
pthread_mutex_unlock(&_lock);
return;
}
// lazilly create set of infos
// Create TableMap with empty data
if (nil == infos) {
infos = [NSMutableSet set];
//< observer - keypaths info>
[_objectInfosMap setObject:infos forKey:object];
}
// add info and oberve
//keypath info Add the keypath info
[infos addObject:info];
// unlock prior to callout
pthread_mutex_unlock(&_lock);
/ / register
[[_FBKVOSharedController sharedController] observe:object info:info];
}
Copy the code
- In the first place to judge
kayPath
Whether it has been registered, registered directly back, here is the deduplicated processing, this wave of operation is very detailed. - Will be constructed
_FBKVOInfo
Add information to_objectInfosMap
In the. - call
_FBKVOSharedController
Do real registration. member:
instructions
Member calls the hash in _FBKVOInfo and isEqual to determine whether the object exists, that is, whether the object corresponding to the keyPath exists.
[[_FBKVOSharedController sharedController] Observe: Object Info :info] is registered as a singleton
Why use singletons here? Instead of using singletons when an outside call is initialized? If we use it in our VC, we won’t be able to release it, we’ll be bloated, and we’re using singletons in this method, so our FBKVOController will be destroyed along with the VC. This wave of operation, again very detailed, amazing!
What is the object argument passing in here? Is the self?
Not self, self. Person, why? Do you have a lot of question marks now?
We are under the impression that adding an observer using KVO is self! But not here, pretty boy!
What our VC needs is a callback to the block, and adding an observer is to watch the properties of self.person change, so just pass in self.person. How do you operate inside, I don’t care about VC, you just tell me the result after the change, throw a block callback notice VC OK!
The self in the figure here is the singleton, just for reuse, that is, any observation that adds a property is going to use this singleton, and it’s going to be separated by keyPath, and it’s going to observe different properties.
2. 4 KVOController Destruction
The destruction of the KVOController is actually done internally for us, so we don’t need to destroy it manually.
- dealloc
- unobserveAll
- (void)unobserveAll
{
[self _unobserveAll];
}
Copy the code
- _unobserveAll
- _unobserve:(id)object info:
- (void)_unobserve:(id)object info:(_FBKVOInfo *)info
{
// lock
pthread_mutex_lock(&_lock);
// get observation infos
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// lookup registered info instance
_FBKVOInfo *registeredInfo = [infos member:info];
if (nil! = registeredInfo) { [infos removeObject:registeredInfo];// remove no longer used infos
if (0== infos.count) { [_objectInfosMap removeObjectForKey:object]; }}// unlock
pthread_mutex_unlock(&_lock);
// unobserve
[[_FBKVOSharedController sharedController] unobserve:object info:registeredInfo];
}
Copy the code
The singleton calls the internal destruction implementation
The code in the red box is the system’s method removeObserver: forKeyPath:
Since the FBKVOController instance is owned by VC, the FBKVOController instance is dealloc when our VC is destroyed by dealloc. Calling it here is the same thing as calling it remove in VC dealloc.
3. Explore KVO through GNustep
Kvo and KVC belong to the Foundation framework, because The Foundation related code Apple does not open source, for their exploration can be seen through gnustep principle, gnuStep has some of apple’s early low-level implementation.
So that’s the FBKVOController analysis.
Through [gnustep] specific exploration, here is not much of the description, interested in the old iron, you can download the source code of Foundation, look at the implementation of the inside, the idea is similar!
4. To summarize
FBKVOController
Using thebroker
Mode, throughFunctional programming
The idea of putting the observation of attribute changes to useblock
Notification callback- FBKVOController registration, internal use of singletons, reuse, pass
keyPath
I’m looking at different properties. - The controller dealloc implicitly removes the observer, but internally calls the system’s remove method.
More to come
🌹 just like it 👍🌹
🌹 feel have harvest, can come a wave, collect + concern, comment + forward, lest you can’t find me next 😁🌹
🌹 welcome everyone to leave a message to exchange, criticize and correct, learn from each other 😁, improve self 🌹