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. BZOJ4840 NEERC2016 Binary Code

    Problem BZOJ Solution 可能是因为快要省选了,所以最近更博的频率好像高了点_(:зゝ∠)_ 每个字符串最多有两个状态,然后要满足一些依赖关系,考虑2sat.可以先把字符串的结束节点 ...

  2. jQuery简单介绍

    一.jQuery介绍 jQuery是一个轻量级的.兼容多浏览器的JavaScript库. jQuery使用户能够更方便地处理HTML Document.Events.实现动画效果.方便地进行Ajax交 ...

  3. 从一个局长使用BS系统的无奈看测试点

    今天我点名买了个B/S系统,听说只要有浏览器就能用.我最讨厌装客户端了,用浏览器就是方便啊. 下面就是我使用这个系统碰到的麻烦事: 我登录失败的时候没有任何提示,这没什么,反正提示也只是说失败…… 进 ...

  4. vue实现结算淘宝购物车效果

    实现单选时的价格,全选时价格 单选效果图 全选效果图 html <template> <!-- 淘宝结算购物车 --> <div class="settleme ...

  5. image配准,发布geoserver服务

    1.arcmap配准:注:png只保留需要显示范围,多余部分删除,,配准后再进行栅格裁剪(为了去除偏移后出现的NoData值) 2.导出tif:注:NoData值设置,一般为256(有时候经过裁剪会默 ...

  6. python面向对象(七)属性方法的添加

    ​ 通常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性.下来我就讲下添加属性和方法,同时也将下限值添加属性方法. 添加属性 ...

  7. Docker中安装wiki Confluence

    一下内容在centos 7安装成功. 一.安装docker 1.yum安装docker yum update # 更新yum yum install docker # yum安装docker 2.开启 ...

  8. Java HashCode详解

    一.为什么要有Hash算法 Java中的集合有两类,一类是List,一类是Set.List内的元素是有序的,元素可以重复.Set元素无序,但元素不可重复.要想保证元素不重复,两个元素是否重复应该依据什 ...

  9. 关于主键的设计、primary key

    主键:用于唯一标识一个表中一行数据. 外键:用于建立两个表之间的关系,A表中有一列是B表中的主键,那么A表中这列的数据就受到B表主键的约束. 那么关于主键应该如何设计呢,这里我说下优缺点: 1.用自动 ...

  10. 触发器中的inserted表和deleted表

    触发器语句中使用了两种特殊的表:deleted 表和 inserted 表.Microsoft? SQL Server 2000 自动创建和管理这些表.可以使用这两个临时的驻留内存的表测试某些数据修改 ...