The original article start my blog: blog.cocosdever.com/2019/07/03/…

What’s New

  • This page was last updated on 05/07/2019
  • First updated Jul. 03, 2019

preface

OC provides users with a set of observer modes (KVO). When certain properties of an object change, messages are broadcast to all observers. The basic usage of KVO is not mentioned here. The idea of adding blocks to the KVO function of the system is described below. First, take a look at the final API:

UIView *v = [[UIView alloc] init];
NSObject *obj = [[NSObject alloc] init];

[obj cc_easyObserve:v forKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionNew block:^(id object, NSDictionary<NSKeyValueChangeKey.id> *change) {
	NSLog(@"hello");
}];

Copy the code

Method of passing blocks in KVO

To add a block function to the system’s KVO, the first thing to do is pass the block pointer to the KVO and bring the block back when the message is broadcast. Take a look at the system API:

/ / NSObject class
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

// An observer must implement the following method to receive a broadcast
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey.id> *)change context:(nullable void *)context;
Copy the code

One of the parameters is content, which allows a void * pointer to be passed in, so we can just take the block that the user passed in and turn it into a void *, pass it into KVO, and when the message is broadcast, we can get the address of the block from this context, Just call block again.

Use internal observers to create convenient apis

After the above analysis, it is theoretically possible to add a block feature to the KVO function of the system, so let’s start with the implementation part of the code.

The whole point of adding a block property is to make it easy to use the KVO function of the system, so we’re going to do it in categories, directly extend NSObject, so that all of our objects have easy manipulation.

// NSObject+CCEasyKVO.h

/** @param object @param object @param change information */

typedef void (^CC_EasyBlock)(id object, NSDictionary<NSKeyValueChangeKey.id> *change);

@interface NSObject (CCEasyKVO)

/** simple KVO @param observe @param keyPath key @param options options @param block callback */
- (void)cc_easyObserve:(id)observe forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(CC_EasyBlock) block;

- (void)cc_easyRemoveAllKVO;

@end
Copy the code

The above is our header section, relatively simple, mainly is to provide a convenient KVO API, where CC_EasyBlock is the user needs to pass in the block.

The first problem

Then there’s an important problem to solve. Can we directly use the currently classified object as the observer to directly observe? The answer is no. You can try this for yourself. Reason is that when a user in the class also implements the system observeValueForKeyPath KVO accept radio method… The user’s class can no longer receive broadcasts from the system. To solve this problem, we can use a custom class (CCInternalObserver) in the class to act as an observer. This way, even if the user implements a method to receive a broadcast for his class, it will not affect our code. We implement in CCInternalObserver observeValueForKeyPath… When the broadcast arrives, the block to which the context points is called.

The second problem encountered

How do I prevent block memory from being released? How do I manage block memory? There are three types of oc blocks: global block NSGlobalBlock, heap block NSMallocBlock, stack block NSStackBlock. Here, by the way, are some of the differences:

(1) The block type does not refer to any external variables (except static variables), creating an NSGlobalBlock; Except for NSGlobalBlock, when it's created it's an NSStackBlock, and when it's assigned to a strong variable it's an NSMallocBlock, which is also called a copy operation; If the condition of NSStatckBlock is met, you can obtain the NSStatckBlock in either of the following ways: 1. The anonymous block is created when the method is called. The block variable inside the method is NSStatckBlock 2. (2) Memory management: Blocks of type NSStackBlock are released when stack memory is freed. They need to be stored with strong variables before being used; otherwise, they will crash. Blocks of type NSGlobalBlock will not be released; The NSMallocBlock type, like any other reference type, is released when no reference is made; Except for the NSStackBlock type, no other type will duplicate copy when assigning to a variable.Copy the code

The user may pass a block of one of three types. To avoid memory problems, a little extra processing needs to be done when the block is converted to void * before it can be passed to the system’s KVO:

// The block passed in by the user may be an NSStackBlock, so ownership must be transferred to the CoreFoundatin layer when it is converted to an untyped pointer. In this way, the block type is converted to an NSMallocBlock and is held safely
[observe addObserver:self.observer forKeyPath:keyPath options:options context:(__bridge_retained void *)block];

Copy the code

By the way, self. Observer is the CCInternalObserver mentioned above:)

The third problem

The third problem is how to log off observers. Another problem with the SYSTEM’s KVO function is that it needs to be manually logged out every time it is used up. Otherwise, the observed object will then broadcast messages to the registered observers. EXC_BAD_ACCESS will be raised if the observer is freed from memory. Remove the observer from the observed in time. To solve this problem, create a hash table in the CCInternalObserver that stores all observed, and override the CCInternalObserver’s deALOc method to remove all observations.

Complete code

The core code details have been covered above. The complete code I’ve made into a Category NSObject+ cceasyKvo.h, which I can just plug into the project and use. CCEasyKVO source

Recommended reading

More complex KVO solutions