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 NSKeyValueObservingCallback 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:

  • FBKVOControlleruse
 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:

  • usecontrollerWithObserverInitialize theFBKVOControllerInstance.
  • useobserve: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

  • rewriteisEqualwithhashmethods
- (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 judgekayPathWhether it has been registered, registered directly back, here is the deduplicated processing, this wave of operation is very detailed.
  • Will be constructed_FBKVOInfoAdd information to_objectInfosMapIn the.
  • call_FBKVOSharedControllerDo 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

  • FBKVOControllerUsing thebrokerMode, throughFunctional programmingThe idea of putting the observation of attribute changes to useblockNotification callback
  • FBKVOController registration, internal use of singletons, reuse, passkeyPath 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 🌹