• An overview of the
  • How do I provide a clean traversal method
  • Magic on NSObject
  • Transform UIKit

Keep an eye on the repository for updates: ios-source-code-analyze

High Alert: This article is very long, because BlocksKit implementation is complex and intentional. This article is not about dissecting the implementation of blocks in iOS development and how they are composed or even used. If you want to understand the implementation of blocks through this article, it won’t help.

What exactly is a Block? This is a problem that may trouble many iOS beginners. If you Google a similar question, you’ll get hundreds of thousands of results. Blocks are a very important part of iOS development, and they’re becoming more and more important.


An overview of the

This article only analyzes the source code of BlocksKit V2.2.5 to understand how the following functions are implemented from the inside of the framework:

  • forNSArray,NSDictionaryNSSetAnd the corresponding mutable collection typeNSMutableArray,NSMutableDictionaryNSMutableSetaddbk_each:And other methods to complete the set of elementsRapid traverse
  • Using blockNSObjectObject KVO
  • forUIViewObject to addbk_whenTapped:And other methods to quickly add gestures
  • Block replacementUIKitIn thedelegate, involving the core moduleDynamicDelegate.

The BlocksKit framework includes but is not limited to the above features. This article is an analysis of the v2.2.5 version of the source code. Other features will not be discussed in detail in this article.

How do I provide a clean traversal method

The simplest functionality implemented by BlocksKit is to add methods to traverse the elements of a collection for collection types.

[@ [@ 1, @ 2, @ 3] bk_each: ^ (id obj) {NSLog (@ "% @", obj);}].Copy the code

This code is very simple, we can use enumerateObjectsUsingBlock: alternative bk_each method: methods:

[@ [@ 1, @ 2, @ 3] enumerateObjectsUsingBlock: ^ (id obj, NSUInteger independence idx, BOOL * stop) {NSLog (@ "% @", obj);}]. Draveness[10725:453402] 1 2016-03-05 16:02:57.296 Draveness[10725:453402] 2 2016-03-05 16:02:57. 297 Draveness [10725-453402] 3Copy the code

This part of the code is not too difficult to implement:

- (void)bk_each:(void (^)(id obj))block { NSParameterAssert(block ! = nil); [the self enumerateObjectsUsingBlock: ^ (id obj, NSUInteger independence idx, BOOL * stop) {block (obj);}]. }Copy the code

It determines whether the block passed in is empty before the block executes, and then calls a traversal method that passes each obJ in the array to the block.

Some of the methods that BlocksKit adds to these collection classes also exist in Ruby, Haskell, and other languages. If you are exposed to the above language, it will be easier to understand the functionality of the methods here, but that is not the main focus of this article.

// NSArray+BlocksKit.h - (void)bk_each:(void (^)(id obj))block; - (void)bk_apply:(void (^)(id obj))block; - (id)bk_match:(BOOL (^)(id obj))block; - (NSArray *)bk_select:(BOOL (^)(id obj))block; - (NSArray *)bk_reject:(BOOL (^)(id obj))block; - (NSArray *)bk_map:(id (^)(id obj))block; - (id)bk_reduce:(id)initial withBlock:(id (^)(id sum, id obj))block; - (NSInteger)bk_reduceInteger:(NSInteger)initial withBlock:(NSInteger(^)(NSInteger result, id obj))block; - (CGFloat)bk_reduceFloat:(CGFloat)inital withBlock:(CGFloat(^)(CGFloat result, id obj))block; - (BOOL)bk_any:(BOOL (^)(id obj))block; - (BOOL)bk_none:(BOOL (^)(id obj))block; - (BOOL)bk_all:(BOOL (^)(id obj))block; - (BOOL) bk_pile :(NSArray *)list withBlock:(BOOL (^)(id obj1, id obj2))block;Copy the code

Magic on NSObject

NSObject is the “God class” in iOS.

Methods added to NSObject are added to almost all classes in Cocoa Touch. The discussion of NSObject is divided into three parts:

  1. AssociatedObject
  2. BlockExecution
  3. BlockObservation

Add AssociatedObject

AssociatedObject is used when we want to add attributes to an existing class. BlocksKit provides an easier way to do this. There is no need to create a new category.

NSObject *test = [[NSObject alloc] init]; [test bk_associateValue:@"Draveness" withKey:@" name"]; NSLog (@ "% @", [test bk_associatedValueForKey: @ "name")); The 2016-03-05 16:02:25. 761 Draveness Draveness [10699-452125]Copy the code

We use bk_associateValue:withKey: and bk_associatedValueForKey: to set and obtain the value of NAME draP.

- (void)bk_associateValue:(id)value withKey:(const void *)key {objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }Copy the code

OBJC_ASSOCIATION_RETAIN_NONATOMIC: retain nonatomic OBJC_ASSOCIATION_RETAIN_NONATOMIC: retain nonatomic

/** * Policies related to associative references. * These are options to objc_setAssociatedObject() */ typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */ OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. * The association is not made atomically. */ OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  /**< Specifies that the associated object is copied. * The association is not made atomically. */ OBJC_ASSOCIATION_RETAIN = 01401,  /**< Specifies a strong reference to the associated object. * The association is made atomically. */ OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied. * The association is made atomically. * /};Copy the code

There’s nothing to say about this NS_ENUM. Note that there’s no weak attribute here.

BlocksKit implements “weak attributes” in another way:

- (void)bk_weaklyAssociateValue:(__autoreleasing id)value withKey:(const void *)key { _BKWeakAssociatedObject *assoc = Objc_getAssociatedObject (self key); if (! assoc) { assoc = [_BKWeakAssociatedObject new]; [self bk_associateValue:assoc withKey:key]; } assoc.value = value; }Copy the code

Here we get a _BKWeakAssociatedObject object, assoc, and then update the value of the object’s property.

Because you can’t add a weak attribute to an object using AssociatedObject directly, we add an object here and let it hold a weak attribute:

@interface _BKWeakAssociatedObject : NSObject

@property (nonatomic,weak) id value;

@end

@implementation _BKWeakAssociatedObject

@end
Copy the code

This is how BlocksKit implements weak attributes, and I think the implementation method is relatively concise.

Getter methods are implemented very similarly:

- (id)bk_associatedValueForKey:(const void *)key
{
	id value = objc_getAssociatedObject(self,key);
	if (value && [value isKindOfClass:[_BKWeakAssociatedObject class]]) {
		return [(_BKWeakAssociatedObject *)value value];
	}
	return value;
}
Copy the code

Execute blocks on arbitrary objects

This class provides interfaces that allow you to quickly execute thread-safe, asynchronous blocks on any object, and these blocks can also be canceled before execution.

- (id < NSObject, NSCopying>)bk_performOnQueue:(dispatch_queue_t)queue afterDelay:(NSTimeInterval)delay usingBlock:(void (^)(id obj))block  { NSParameterAssert(block ! = nil); Return BKDispatchCancellableBlock (queue, delay, ^ {block (self); }); }Copy the code

To determine whether a block is empty here are the details, this method is the most critical, that is, it returns a block can cancel, and this block is BKDispatchCancellableBlock generated using static function.

The static id < NSObject, NSCopying > BKDispatchCancellableBlock (dispatch_queue_t queue, NSTimeInterval delay, void(^block)(void)) { dispatch_time_t time = BKTimeDelay(delay); #if DISPATCH_CANCELLATION_SUPPORTED if (BKSupportsDispatchCancellation()) { dispatch_block_t ret = Dispatch_block_create (0, block); Dispatch_after (time, queue, ret); return ret; } #endif __block BOOL cancelled = NO; void (^wrapper)(BOOL) = ^(BOOL cancel) { if (cancel) { cancelled = YES; return; } if (! cancelled) block(); }; Dispatch_after (time, queue, ^{wrapper(NO); }); return wrapper; }Copy the code

This function is executed first BKSupportsDispatchCancellation to judge whether the current platform and version supports the use of the GCD cancel block, of course is usually supported:

  • The function returns zeroYES, so this is returned after the block is dispatched to the specified queuedispatch_block_tThe type of block,
  • The function returns zeroNO, then a block that can be unwrapped manually is implemented as follows:
__block BOOL cancelled = NO; void (^wrapper)(BOOL) = ^(BOOL cancel) { if (cancel) { cancelled = YES; return; } if (! cancelled) block(); }; Dispatch_after (time, queue, ^{wrapper(NO); }); return wrapper;Copy the code

This code creates a Wrapper block and sends it to the specified queue. The wrapper block sent to the specified queue must be executed.

If the current block is not executing, the Cancelled variable inside the block will be set to YES when we call wrapper(YES) once outside, so the block will not execute.

  1. dispatch_after --- cancelled = NO
  2. wrapper(YES) --- cancelled = YES
  3. wrapper(NO) --- cancelled = YESBlock will not execute

This is a key part of implementing cancellation:

+ (void)bk_cancelBlock:(id <NSObject,NSCopying>)block
{
    NSParameterAssert(block != nil);

#if DISPATCH_CANCELLATION_SUPPORTED
    if (BKSupportsDispatchCancellation()) {
        dispatch_block_cancel((dispatch_block_t)block);
        return;
    }
#endif

    void (^wrapper)(BOOL) = (void(^)(BOOL))block;
    wrapper(YES);
}
Copy the code
  • GCD supports unblock, then call directlydispatch_block_cancelFunction unblock
  • GCD does not support calling a block oncewrapper(YES)

Use blocks to encapsulate KVO

BlocksKit’s encapsulation of KVO consists of two parts:

  1. NSObjectThe classification is responsible for providing convenience methods
  2. Private class_BKObserverConcrete implementation of native KVO function

Provides interface and indeallocWhen stop BlockObservation

NSObject+BKBlockObservation Calls this method for most interfaces in this class:

- (void)bk_addObserverForKeyPaths:(NSArray *)keyPaths identifier:(NSString *)identifier Options: (NSKeyValueObservingOptions) options context: (BKObserverContext) context task: (id) task {# 1: check parameter, omit # 2: Overwrite dealloc NSMutableDictionary *dict in a classification using a magic method; _BKObserver *observer = [[_BKObserver alloc] initWithObservee:self keyPaths:keyPaths context:context task:task]; [observer startObservingWithOptions:options]; Dict [identifier] = observer; dict[identifier] = observer; }Copy the code

We won’t cover parts #1 and #3 here, but before we go through the code in Part #2, we’ll take a look at the core method, which omits most of the details.

Using the method of parameter creates a _BKObserver object, then calls the startObservingWithOptions: method start KVO observations corresponding attributes, and then in the form of {identifier, obeserver} to be kept in the dictionary.

There is nothing new, we will in the next section introduces startObservingWithOptions: this method.

Adjust the dealloc method in the classification

This is an issue that I think is very worthy of discussion, and it is also an issue that I have encountered in writing frameworks recently.

When we register some notifications in a category or use KVO, we may not find the time to unregister them.

Because you can’t implement the Dealloc method directly in the classification. In iOS8 and earlier versions, if an object is released, but the notification of the registration of the object is not removed, when the event occurs again, the released object will be notified and the whole program will crash.

The solution here is very clever:

Class classToSwizzle = self.class; NSMutableSet *classes = self.class.bk_observedClassesHash; @synchronized (classes) {// get the current class name, And determine if the dealloc method has been modified to reduce the number of times this code is called NSString *className = NSStringFromClass(classToSwizzle); if (! [classes containsObject:className]) {// the sel_registerName method returns dealloc's selector, Because dealloc already registers SEL deallocSelector = sel_registerName("dealloc"); __block void (*originalDealloc)(__unsafe_unretained ID, SEL) = NULL; NewDealloc = ^(__unsafe_unretained ID objSelf) {// Remove all observers before dealloc [objSelf] bk_removeAllBlockObservers]; If (originalDealloc == NULL) {// If (originalDealloc == NULL) {// If (originalDealloc == NULL) { Struct objc_super superInfo = {.receiver = objSelf, .super_class = class_getSuperclass(classToSwizzle) }; Void (*msgSend)(struct objc_super *, SEL) = (__typeof__(msgSend))objc_msgSendSuper; MsgSend (& superInfo deallocSelector); } else {// If the dealloc method is found, it is called directly with the argument originalDealloc(objSelf, deallocSelector); }}; IMP IMP newDeallocIMP = imp_implementationWithBlock(newDealloc); // Add a method to the current class, but it probably won't succeed because the class already has the dealloc method if (! Class_addMethod (classToSwizzle, deallocSelector, newDeallocIMP, "V @:")) {// Get the original dealloc instance Method deallocMethod = class_getInstanceMethod(classToSwizzle, deallocSelector); OriginalDealloc = (void(*)(__unsafe_unretained ID, SEL))method_getImplementation(deallocMethod); OriginalDealloc = (void(*)(__unsafe_unretained id, (SEL) method_setImplementation deallocMethod newDeallocIMP); } // add the current className to the collection of classes that have changed [classes addObject:className]; }}Copy the code

This part of the code is executed in the following order:

  1. First callbk_observedClassesHashClass method to get all changesdeallocA collection of classes for methodsclasses
  2. use@synchronized (classes)Ensure mutual exclusion and avoid simultaneous modificationclassesToo many classes in the collection have unexpected results
  3. Determine the class to which the method is to be deployedclassToSwizzleWhether it has been adjusteddeallocmethods
  4. ifdeallocIf the method has not been modified, it will passsel_registerName("dealloc")Method, this line of code is not actually registereddeallocThe selectors will get insteaddeallocFor specific reasons, see the implementation of this methodsel_registerName
  5. Add a method to remove the Observer in the new dealloc, and then call the old dealloc

    id newDealloc = ^(__unsafe_unretained id objSelf) { [objSelf bk_removeAllBlockObservers]; if (originalDealloc == NULL) { struct objc_super superInfo = { .receiver = objSelf, .super_class = class_getSuperclass(classToSwizzle) }; Void (*msgSend)(struct objc_super *, SEL) = (__typeof__(msgSend))objc_msgSendSuper; MsgSend (& superInfo deallocSelector); } else {originalDealloc(objSelf, deallocSelector); }}; IMP newDeallocIMP = imp_implementationWithBlock(newDealloc);Copy the code
    1. callbk_removeAllBlockObserversMethod removes all observers, which is the ultimate goal of this code
    2. According to theoriginalDeallocNull determines whether to send a message to the parent class or call it directlyoriginalDeallocAnd the incomingObjSelf, deallocSelectorAs a parameter
  6. Once we have the selectors and IMPs for the new Dealloc method, it’s time to change the old dealloc implementation

    if (! Class_addMethod (classToSwizzle, deallocSelector, newDeallocIMP, "v@:")) { // The class already contains a method implementation. Method deallocMethod = Class_getInstanceMethod (classToSwizzle deallocSelector); // We need to store original implementation before setting new implementation // in case method is called at the time of Setting. OriginalDealloc = (void(*)(__unsafe_unretained id, SEL))method_getImplementation(deallocMethod); We need to store original implementation again, OriginalDealloc = (void(*)(__unsafe_unretained ID, SEL))method_setImplementation(deallocMethod, newDeallocIMP); }Copy the code
    1. callclass_addMethodMethod to add a selector to the current classdealloc(99.99% of the time it won’t work)
    2. Get the originaldeallocInstance methods
    3. Save the original implementation tooriginalDeallocTo prevent usemethod_setImplementationIs called during the process of resetting the methoddeallocNo method is available
    4. To resetdeallocMethod implementation. Again, store the implementation tooriginalDeallocTo prevent changes from being implemented

The assignment of the dealloc methods in the classification ends here, and the next section continues with the analysis of the private _BKObserver class.

Private class_BKObserver

_BKObserver is an object used to observe properties. It defines four properties in the interface:

@property (nonatomic, readonly, unsafe_unretained) id observee; @property (nonatomic, readonly) NSMutableArray *keyPaths; @property (nonatomic, readonly) ID task; @property (nonatomic, readOnly) BKObserverContext Context;Copy the code

The above four properties of the specific role not here that the above bk_addObserverForKeyPaths: identifier: options: context: Method invocation _BKObserver initialization method initWithObservee: keyPaths: context: task: too simple also don’t say.

_BKObserver *observer = [[_BKObserver alloc] initWithObservee:self keyPaths:keyPaths context:context task:task];
[observer startObservingWithOptions:options];
Copy the code

The above the first line of code to generate a call immediately after the observer instance startObservingWithOptions: method to observe the corresponding keyPath:

- (void)startObservingWithOptions:(NSKeyValueObservingOptions)options { @synchronized(self) { if (_isObserving) return; #1: The keyPaths implementation KVO _isObserving = YES; }}Copy the code

StartObservingWithOptions: method is the most important is the first part # 1:

[self.keyPaths bk_each:^(NSString *keyPath) {
	[self.observee addObserver:self forKeyPath:keyPath options:options context:BKBlockObservationContext];
}];
Copy the code

Walk through your keyPaths and have _BKObserver look at you as an observer and pass in the corresponding keyPath.

The implementation of the _stopObservingLocked method is very similar, but I won’t mention it here.

[keyPaths bk_each:^(NSString *keyPath) {
	[observee removeObserver:self forKeyPath:keyPath context:BKBlockObservationContext];
}];
Copy the code

So far, we haven’t seen method to realize KVO observeValueForKeyPath: ofObject: change: the context, this method is the callback after every property change:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context ! = BKBlockObservationContext) return; @synchronized(self) { switch (self.context) { case BKObserverContextKey: { void (^task)(id) = self.task; task(object); break; } case BKObserverContextKeyWithChange: {void ^ (task) (id, NSDictionary *) = self. Task; Task (object, change); break; } case BKObserverContextManyKeys: {void ^ (task) (id, nsstrings *) = self. Task; Task (object, keyPath); break; } case BKObserverContextManyKeysWithChange: {void ^ (task) (id, nsstrings *, NSDictionary *) = self. The task; Task (object, keyPath, change); break; }}}}Copy the code

The implementation of this method is also simple. Based on the context value passed in, the task type is converted and the specific value is passed in.

This module is done backwards, and in the next section BlocksKit introduces some simple changes to UIKit components.

Transform UIKit

In this summary, I will introduce how BlocksKit transforms some simple controls. This section contains about two parts:

  • UIGestureRecongizer + UIBarButtonItem + UIControl
  • UIView

Modify UIGestureRecongizer, UIBarButtonItem, and UIControl

Let’s start with an example of the use of UITapGestureRecognizer

UITapGestureRecognizer *singleTap = [UITapGestureRecognizer bk_recognizerWithHandler:^(id sender) { NSLog(@"Single The tap. ");} delay: 0.18]; [self addGestureRecognizer:singleTap];Copy the code

In code bk_recognizerWithHandler: delay: method in the last method will be called initialization bk_initWithHandler: delay: generate a UIGestureRecongizer instance

- (instancetype)bk_initWithHandler:(void (^)(UIGestureRecognizer *sender, UIGestureRecognizerState, CGPoint location))block delay:(NSTimeInterval)delay { self = [self initWithTarget:self action:@selector(bk_handleAction:)]; if (! self) return nil; self.bk_handler = block; self.bk_handlerDelay = delay; return self; }Copy the code

It’s going to pass in a target and a selector in this method. Where target is self, and selector is implemented in this classification:

Recognleaction - (void) bk_recognleaction :(UIGestureRecognizer *)recognizer {void (^handler)(UIGestureRecognizer *sender, UIGestureRecognizerState State, CGPoint location) = recognizer.bk_handler; if (! handler) return; NSTimeInterval delay = self.bk_handlerDelay; #1: Encapsulate the block and control whether the block can execute self.bk_shouldHandleAction = YES; [NSObject bk_performAfterDelay:delay usingBlock:block]; }Copy the code

Bk_initWithHandler :delay: Save the current gestures bk_handler, so called directly in the Block Execution section mentioned bk_performAfterDelay: usingBlock: Method that dispatches a block to a specified queue and finally completes the call to the block.

Encapsulates a block and controls whether the block can be executed

This part of the code is similar to the previous part in that we also use a property called bk_shouldHandleAction to control whether the block is executed:

CGPoint location = [self locationInView:self.view]; void (^block)(void) = ^{ if (! self.bk_shouldHandleAction) return; Handler (self, self.state, location); };Copy the code

= = = =

Similarly UIBarButtonItem and UIControl use almost the same mechanism, setting target to self, and then calling the specified block in the classified method.

UIControlWrapper

A little bit different is UIControl. Because UIControl has multiple UIControlEvents, use another class, BKControlWrapper, to encapsulate the Handler and controlEvents

@property (nonatomic) UIControlEvents controlEvents; @property (nonatomic, copy) void (^handler)(id sender);Copy the code

The UIControlWrapper object is stored in the dictionary as a UIControl property in the form of {controlEvents, Wrapper}.

Transform UIView

Since we’ve modified UIGestureRecognizer above, it’s easy to modify UIView here:

- (void)bk_whenTouches:(NSUInteger)numberOfTouches tapped:(NSUInteger)numberOfTaps handler:(void (^)(void))block { if (! block) return; UITapGestureRecognizer *gesture = [UITapGestureRecognizer bk_recognizerWithHandler:^(UIGestureRecognizer *sender, UIGestureRecognizerState state, CGPoint location) {if (state = = UIGestureRecognizerStateRecognized) block ();}]; gesture.numberOfTouchesRequired = numberOfTouches; gesture.numberOfTapsRequired = numberOfTaps; [the self gestureRecognizers enumerateObjectsUsingBlock: ^ (id obj, NSUInteger independence idx, BOOL * stop) {if (! [obj isKindOfClass:[UITapGestureRecognizer class]]) return; UITapGestureRecognizer *tap = obj;  BOOL rightTouches = (tap.numberOfTouchesRequired == numberOfTouches);  BOOL rightTaps = (tap.numberOfTapsRequired == numberOfTaps);  if (rightTouches && rightTaps) { [gesture requireGestureRecognizerToFail:tap]; } }]; [self addGestureRecognizer:gesture]; }Copy the code

There’s only one core method for UIView classification, and all the other methods are passing in different parameters to this method, so here’s the thing to notice. It will iterate over all gestureRecognizers, and then put the call to all have a conflict of gestures requireGestureRecognizerToFail: method, make sure to add gestures can normal execution.

Since there is a lot to cover in this article, it has been split into two parts. The next part introduces the most important part of BlocksKit, dynamic proxies:

  • Magic BlocksKit (1)
  • BlocksKit (2)

Blog: Draveness

About comments and comments

Does not process comments and comments from other platforms