BlocksKit(1)-基本类型的分类

BlocksKit是一个用block的方式来解决我们日常用对集合对象遍历、对象快速生成使用、block作为委托对象的一种综合便利封装库。这个库主要分三个大块Core、DynamicDelegate、UIKit三块内容,先分析一下第一部分Core,这个部门里面都是对基本类型的提供的分类接口,能够提供使用的便利。

集合类型提供的分类

代码提供了数组、字典、集合等类型的封装,以最常用的NSMutableArray+BlockKit来作为事例看一下:

- (void)bk_performSelect:(BOOL (^)(id obj))block;
- (void)bk_performReject:(BOOL (^)(id obj))block;
- (void)bk_performMap:(id (^)(id obj))block;

这些个方法,都是利用enumerateObjectsUsingBlock或者indexesOfObjectsPassingTest这些个方法去把block放到里面去进行遍历,然后决定计算出那些值或者是返回那些路径,在整个集合部分的话大概都是这种思路


便利的AssociatedObjects

在"NSObject+BKAssociatedObjects.h文件中提供了,便利的绑定方法,我们可以提供一个value和一个key直接就可以在NSObject中绑定属性,省去自己去编写objc_setAssociatedObject和 objc_getAssociatedObject(self, key)的不便利,这里面感觉虽然是放在BlockKit里面其实感觉并无和BlockKit关系不是很大,毕竟NSObject是非常常用的。另外,提供了不同关键字类型的绑定,亮点是实现了weak类型的属性的绑定,这里并没有使用OBJC_ASSOCIATION_ASSIGN这个关键字去绑定,而是把这个weak的value去绑定到一个OBJC_ASSOCIATION_RETAIN_NONATOMIC的对象上。避免使用了OBJC_ASSOCIATION_ASSIGN对使用的时候weak值的不确定性,原因移步(http://www.cocoachina.com/ios/20150629/12299.html),所以作者这个种做法非常的聪明。看一下代码:

- (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;
} - (id)bk_associatedValueForKey:(const void *)key
{
id value = objc_getAssociatedObject(self, key);
if (value && [value isKindOfClass:[_BKWeakAssociatedObject class]]) {
return [(_BKWeakAssociatedObject *)value value];
}
return value;
}

_BKWeakAssociatedObject就是weak对象寄存的对象。


便利的block操作

在开发的时候,block需要不同的执行的情况不同执行。这里为我们提供一些便利的方案,比如,指定block在某个线程上延迟多少时间执行或者在后台执行这些场景。看一下这部分实现的核心的代码:

- (id)bk_performBlock:(void (^)(id obj))block onQueue:(dispatch_queue_t)queue afterDelay:(NSTimeInterval)delay
{
NSParameterAssert(block != nil); __block BOOL cancelled = NO; void (^wrapper)(BOOL) = ^(BOOL cancel) {
if (cancel) {
cancelled = YES;
return;
}
if (!cancelled) block(self);
}; dispatch_after(BKTimeDelay(delay), queue, ^{
wrapper(NO);
}); return [wrapper copy];
}

代码很简单,用了dispatch_after来处理接口传入的数据,然后返回一个叫wrapper的block,这点很好,这个任务能不能被取消就靠它了。一般在使用的时候我们都会保存这个返回的block,当需要取消这个操作的时候,那么就需要调用:

+ (void)bk_cancelBlock:(id)block
{
NSParameterAssert(block != nil);
void (^wrapper)(BOOL) = block;
wrapper(YES);
}

这个时候传入进去了一个yes,导致在执行的时候,直接就返回,block里面内容就不会被执行,从而达到取消的情况。


Block版的KVO

在日常的操作中,使用KVO的地方还是很多,但是KVO使用起来,苹果原生的API并没有那么便利,在这里面就提供了另外一种方式的封装,然我们作为开发者能够方便的使用KVO。

看一下最简单的添加观察的接口:

- (NSString *)bk_addObserverForKeyPath:(NSString *)keyPath task:(void (^)(id target))task
{
NSString *token = [[NSProcessInfo processInfo] globallyUniqueString];
[self bk_addObserverForKeyPaths:@[ keyPath ] identifier:token options:0 context:BKObserverContextKey task:task];
return token;
}

就是传一个keypath和一个block实现绑定的功能,这里面生成token的方法很有意思,生成一个唯一的字符串,作为这一次观察的标记。后面用这个标记加上keypath还可以移除这个观察。

看一下具体的实现代码:

- (void)bk_addObserverForKeyPaths:(NSArray *)keyPaths identifier:(NSString *)identifier options:(NSKeyValueObservingOptions)options context:(BKObserverContext)context task:(id)task
{
NSParameterAssert(keyPaths.count);
NSParameterAssert(identifier.length);
NSParameterAssert(task);
// 第一部分
Class classToSwizzle = self.class;
NSMutableSet *classes = self.class.bk_observedClassesHash;
@synchronized (classes) {
NSString *className = NSStringFromClass(classToSwizzle);
if (![classes containsObject:className]) {
SEL deallocSelector = sel_registerName("dealloc"); __block void (*originalDealloc)(__unsafe_unretained id, SEL) = NULL; 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); 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, in case it just changed.
originalDealloc = (void(*)(__unsafe_unretained id, SEL))method_setImplementation(deallocMethod, newDeallocIMP);
} [classes addObject:className];
}
}
// 第二部分
NSMutableDictionary *dict;
_BKObserver *observer = [[_BKObserver alloc] initWithObservee:self keyPaths:keyPaths context:context task:task];
[observer startObservingWithOptions:options]; @synchronized (self) {
dict = [self bk_observerBlocks]; if (dict == nil) {
dict = [NSMutableDictionary dictionary];
[self bk_setObserverBlocks:dict];
}
} dict[identifier] = observer;
}

这个代码主要分成两个部分:

一个部分是如何去把观察的类的dealloc去swizzle掉,为要这样做呢,就是为了让添加的observer去执行一个remove的操作,去掉之前观察的那些path,防止由于使用者没有移除而导致的崩溃。如果做过这样的处理,那么就会把这个类的名字记录到self.class.bk_observedClassesHash这个NSMutableSet里面去。用于控制不至于把方法替换多遍。

第二部分是如何添加一个_BKObserver对象,这个对象就是一个具体的实例,用来持有我们需要的观察对象,并具体的进行观察的动作,这里要特别注意的是在_BKObserver的对象里面self.observee到底是谁。这个_BKObserver会被通过AssociatedObject方法去绑定到对象上,如果想要彻底的移除,一方面是要移除_BKObserver里面观察的那些个keypath的对象,另一方面是吧_BKObserver从对象绑定的字典里面移除去。

那么看一下_BKObserver的如何实现对keypath的观察:

- (void)startObservingWithOptions:(NSKeyValueObservingOptions)options
{
@synchronized(self) {
if (_isObserving) return; [self.keyPaths bk_each:^(NSString *keyPath) {
[self.observee addObserver:self forKeyPath:keyPath options:options context:BKBlockObservationContext];
}]; _isObserving = YES;
}
}

这里面self.observee就是我们现在操作观察的对象本身,然后呢self就是上面说的生成的_BKObserver(这个对象属性是unsafe_unretained),接着keyPath就是那些传进去的值,这里就是真正添加观察的地方了。然后就,值变化,再接着就回调传进来的block了。最后移除的流程就很简单了,按照上面说的移除流程,就可以了。

到这里,关于Core部门的代码就差不多都看了,很多的时候,这种通过分类去解决问题的思路是需要好好的体会的。通过绑定属性作为中间值,完成一些复杂的操作,按照这种思路都能在开发的过程中解决很多的稍微复杂而且重复的工作。

BlocksKit(1)-基本类型的分类的更多相关文章

  1. usb接口类型 简单分类辨识

    usb接口类型 简单分类辨识 - [相似百科] 庆欣 0.0 4 人赞同了该文章 1. 先放图,随着越来越多的接触智能设备,会遇到各种各样的usb接口,对于很多人来说,接口类型只有:usb接口,安卓接 ...

  2. ECMAScript---数据类型的分类

    数据值是一门编程语言生产的材料,JS中包含的值有以下类型: 1.基本数据类型(值类型):包含 数字 number.字符串string .布尔 boolean .null(其他语言都有的类型) .und ...

  3. Python标准类型的分类

    Python有3种不同的模型可以帮助对基本类型进行分类,这些类型更好的理解类型之间的相互关系以及他们的工作原理. 1 存储模型    能保存单个字面对象的类型,称为原子或标量存储:    能保存多个对 ...

  4. 112_Power Pivot 销售订单按 sku 订单类型特殊分类及占比相关

    博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一.背景 经过了一个双十一后,天天面对的都是订单.于是有了关于销售订单按sku类型分类的需求. 说明:(暂且不讨论这样分类 ...

  5. Python 类型的分类

    1.存储模型,对象可以保存多少个值.如果只能保存一个值,是原子类型.如果可以保存多个值,是容器类型.数值是原子类型,元组,列表,字典是容器类型.考虑字符串,按道理,字符串应该是容器类型,因为它包含多个 ...

  6. SQL string类型的数据按int类型排序 分类: SQL Server 2014-12-08 16:56 393人阅读 评论(0) 收藏

    说明: 我在做wms进销存软件时,发现一个问题:一张入库单(T_OutIn_BoxTop),入库扫描时要分成多箱,箱号(BoxTop_No)可以是数字也可以是字符串,所以箱号只能是字符串类型的,问题来 ...

  7. C#代码总结03---通过获取类型,分类对前台页面的控件进行赋值操作

    该方法: 一般用于将数据库中的基本信息字段显示到前台页面对应的字段控件中 private void InitViewZc(XxEntity model) { foreach (var info in ...

  8. API 接口设计中 Token 类型的分类与设计

    在实际的网站设计中我们经常会遇到用户数据的验证和加密的问题,如果实现单点,如果保证数据准确,如何放着重放,如何防止CSRF等等 其中,在所有的服务设计中,都不可避免的涉及到Token的设计. 目前,基 ...

  9. oracle 索引扫描类型的分类与构造

    1. INDEX RANGE SCAN--请记住这个INDEX RANGE SCAN扫描方式drop table t purge;create table t as select * from dba ...

随机推荐

  1. bootstrap-datetimepicker中设置中文

    1.引入插件文件,同时引入相应的语言文件 <script src="bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.j ...

  2. v8-su-root

    1.下载userdebug版本 2.设置模块打开develop options 3.勾选usb debugging 4.adb remount 5.解压SuperSU_N.7z(联系我索取)并push ...

  3. Linux下C程序的反汇编【转】

    转自:http://blog.csdn.net/u011192270/article/details/50224267 前言:本文主要介绍几种反汇编的方法. gcc gcc的完整编译过程大致为:预处理 ...

  4. vue总结 08状态管理vuex

      状态管理 类 Flux 状态管理的官方实现 由于状态零散地分布在许多组件和组件之间的交互中,大型应用复杂度也经常逐渐增长.为了解决这个问题,Vue 提供 vuex:我们有受到 Elm 启发的状态管 ...

  5. vue总结 01基础特性

    最近有时间来总结一下vue的知识: 一.vue.js 被定义成一个开发web界面的前端库,是一个非常轻量的工具.vue.js本身具有响应式和组件化的特点. 我们不需要在维护视图和数据的统一上花费大量的 ...

  6. Vue 实现loading进度条

    项目中遇到的,用vue实现下: <template> <div class="plLoading"> <div class="plLoadi ...

  7. 简单的TCP接受在转发到客户端的套接口

    //功能:客服端发送tcp包,服务器接受到并打印出来,并将包转换为大写后到客户端//2015.9.10成功 #include <stdio.h>#include <sys/socke ...

  8. require和import的区别

    require:是一种common协议,大家按照这个约定书写自己的代码,实现模块化. import:是ES6的模块语法实现.是语言自身的模块实现.

  9. C语言基础 - read()函数读取文本字节导致判断失误的问题

    工作了几个月,闲着没事又拿起了经典的C程序设计看了起来,看到字符计数一节时想到用read()去读文本作为字符输入,一切OK,直到行计数时问题出现 了,字符总计数没有问题,可行计算就是进行不了,思考了半 ...

  10. ASP.NET中Literal,只增加纯粹的内容,不附加产生html代码

    页面代码 <div style="float: right; color: #666; line-height: 30px; margin-right: 12px;" id= ...