神奇的 BlocksKit(1):源码分析(下)
私有类 _BKObserver
_BKObserver 是用来观测属性的对象,它在接口中定义了 4 个属性:
@property (nonatomic,readonly,unsafe_unretained) id observee;
@property (nonatomic,readonly) NSMutableArray *keyPaths;
@property (nonatomic,readonly) id task;
@property (nonatomic,readonly) BKObserverContext context;
上面四个属性的具体作用在这里不说了,上面的 bk_addObserverForKeyPaths:identifier:options:context: 方法中调用_BKObserver 的初始化方法 initWithObservee:keyPaths:context:task: 太简单了也不说了。
_BKObserver *observer = [[_BKObserver alloc] initWithObservee:self keyPaths:keyPaths context:context task:task];
[observer startObservingWithOptions:options];
上面的第一行代码生成一个 observer 实例之后立刻调用了 startObservingWithOptions: 方法开始观测对应的 keyPath:
- (void)startObservingWithOptions:(NSKeyValueObservingOptions)options
{
@synchronized(self) {
if (_isObserving) return;
#1:遍历 keyPaths 实现 KVO
_isObserving = YES;
}
}
startObservingWithOptions: 方法最重要的就是第 #1 部分:
[self.keyPaths bk_each:^(NSString *keyPath) {
[self.observee addObserver:self forKeyPath:keyPath options:options context:BKBlockObservationContext];
}];
遍历自己的 keyPaths 然后让 _BKObserver 作观察者观察自己,然后传入对应的 keyPath。
关于 _stopObservingLocked 方法的实现也十分的相似,这里就不说了。
[keyPaths bk_each:^(NSString *keyPath) {
[observee removeObserver:self forKeyPath:keyPath context:BKBlockObservationContext];
}];
到目前为止,我们还没有看到实现 KVO 所必须的方法 observeValueForKeyPath:ofObject:change:context,这个方法就是每次属性改变之后的回调:
- (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,NSString *) = self.task;
task(object,keyPath);
break;
}
case BKObserverContextManyKeysWithChange: {
void (^task)(id,NSString *,NSDictionary *) = self.task;
task(object,keyPath,change);
break;
}
}
}
}
这个方法的实现也很简单,根据传入的 context 值,对 task 类型转换,并传入具体的值。
这个模块倒着就介绍完了,在下一节会介绍 BlocksKit 对 UIKit 组件一些简单的改造。
改造 UIKit
在这个小结会具体介绍 BlocksKit 是如何对一些简单的控件进行改造的,本节大约有两部分内容:
UIGestureRecongizer + UIBarButtonItem + UIControl
UIView
改造 UIGestureRecongizer,UIBarButtonItem 和 UIControl
先来看一个 UITapGestureRecognizer 使用的例子
UITapGestureRecognizer *singleTap = [UITapGestureRecognizer bk_recognizerWithHandler:^(id sender) {
NSLog(@"Single tap.");
} delay:0.18];
[self addGestureRecognizer:singleTap];
代码中的 bk_recognizerWithHandler:delay: 方法在最后都会调用初始化方法 bk_initWithHandler:delay: 生成一个UIGestureRecongizer 的实例
- (instancetype)bk_initWithHandler:(void (^)(UIGestureRecognizer *sender,UIGestureRecognizerState state,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;
}
它会在这个方法中传入 target 和 selector。 其中 target 就是 self,而 selector 也会在这个分类中实现:
- (void)bk_handleAction:(UIGestureRecognizer *)recognizer
{
void (^handler)(UIGestureRecognizer *sender,UIGestureRecognizerState state,CGPoint location) = recognizer.bk_handler;
if (!handler) return;
NSTimeInterval delay = self.bk_handlerDelay;
#1: 封装 block 并控制 block 是否可以执行
self.bk_shouldHandleAction = YES;
[NSObject bk_performAfterDelay:delay usingBlock:block];
}
因为在初始化方法 bk_initWithHandler:delay: 中保存了当前手势的 bk_handler,所以直接调用在 Block Execution 一节中提到过的 bk_performAfterDelay:usingBlock: 方法,将 block 派发到指定的队列中,最终完成对 block 的调用。
封装 block 并控制 block 是否可以执行
这部分代码和前面的部分有些相似,因为这里也用到了一个属性 bk_shouldHandleAction 来控制 block 是否会被执行:
CGPoint location = [self locationInView:self.view];
void (^block)(void) = ^{
if (!self.bk_shouldHandleAction) return;
handler(self,self.state,location);
};
同样 UIBarButtonItem 和 UIControl 也是用了几乎相同的机制,把 target 设置为 self,让后在分类的方法中调用指定的 block。
UIControlWrapper
稍微有些不同的是 UIControl。因为 UIControl 有多种 UIControlEvents,所以使用另一个类 BKControlWrapper 来封装handler 和 controlEvents
@property (nonatomic) UIControlEvents controlEvents;
@property (nonatomic,copy) void (^handler)(id sender);
其中 UIControlWrapper 对象以 {controlEvents,wrapper} 的形式作为 UIControl 的属性存入字典。
改造 UIView
因为在上面已经改造过了 UIGestureRecognizer,在这里改造 UIView 就变得很容易了:
- (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;
[self.gestureRecognizers enumerateObjectsUsingBlock:^(id obj,NSUInteger 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];
}
UIView 分类只有这一个核心方法,其它的方法都是向这个方法传入不同的参数,这里需要注意的就是。它会遍历所有的gestureRecognizers,然后把对所有有冲突的手势调用 requireGestureRecognizerToFail: 方法,保证添加的手势能够正常的执行。
神奇的 BlocksKit(1):源码分析(下)的更多相关文章
- 知识小罐头07(tomcat8请求源码分析 下)
感觉最近想偷懒了,哎,强迫自己也要写点东西,偷懒可是会上瘾的,嘿嘿!一有写博客的想法要赶紧行动起来,养成良好的习惯. ok,继续上一篇所说的一些东西,上一篇说到Connector包装了那两个对象,最后 ...
- 知识小罐头09(tomcat8启动源码分析 下)
初始化已经完成,现在就是启动这些组件,Tomcat中的start方法就是用于启动的,其实start的原理还是和上一篇说的初始化几乎一样!这里我就大概说一下,看几个比较关键的地方就行了. 前面的步骤就大 ...
- memcached学习笔记——存储命令源码分析上篇
原创文章,转载请标明,谢谢. 上一篇分析过memcached的连接模型,了解memcached是如何高效处理客户端连接,这一篇分析memcached源码中的process_update_command ...
- 内核通信之Netlink源码分析-用户内核通信原理
2017-07-05 本节从一个小案例入手,结合源码分析下通过netlink进行内核和用户通信的流程. 内核端 按照传统CS模式,其实内核端可以作为是服务器端,用以接收用户的请求并作出处理,但是从ne ...
- illuminate/routing 源码分析之注册路由
我们知道,在 Laravel 世界里,外界传进来一个 Request 时,会被 Kernel 处理并返回给外界一个 Response.Kernel 在处理 Request 时,会调用 illumina ...
- commons-logging + log4j源码分析
分析之前先理清楚几个概念 Log4J = Log For Java SLF4J = Simple Logging Facade for Java 看到Facade首先想到的就是设计模式中的门面(Fac ...
- Windows平台下源码分析工具
最近这段时间在阅读 RTKLIB的源代码,目前是将 pntpos.c文件的部分看完了,准备写一份文档记录下这些代码的用处.处理过程.理论公式来源.注意事项,自己还没有弄明白的地方.目前的想法是把每一个 ...
- SURF算法与源码分析、下
上一篇文章 SURF算法与源码分析.上 中主要分析的是SURF特征点定位的算法原理与相关OpenCV中的源码分析,这篇文章接着上篇文章对已经定位到的SURF特征点进行特征描述.这一步至关重要,这是SU ...
- STL 源码分析《1》---- list 归并排序的 迭代版本, 神奇的 STL list sort
最近在看 侯捷的 STL源码分析,发现了以下的这个list 排序算法,乍眼看去,实在难以看出它是归并排序. 平常大家写归并排序,通常写的是 递归版本..为了效率的考虑,STL库 给出了如下的 归并排序 ...
- QTimer源码分析(以Windows下实现为例)
QTimer源码分析(以Windows下实现为例) 分类: Qt2011-04-13 21:32 5026人阅读 评论(0) 收藏 举报 windowstimerqtoptimizationcallb ...
随机推荐
- !!Python字典增删操作技巧简述+Python字典嵌套字典与排序
http://developer.51cto.com/art/201003/186006.htm Python编程语言是一款比较容易学习的计算机通用型语言.对于初学者来说,首先需要掌握的就是其中的一些 ...
- Android handler 报错处理Can't create handler inside thread that has not called Looper.prepare()
问题: 写了一个sdk给其他人用,提供一个回调函数,函数使用了handler处理消息 // handler监听网络请求,完成后操作回调函数 final Handler trigerGfHandler ...
- tlplayer,wzplayer所有平台通用加密测试视频
此视频文件为通用版本,支持tlplayer,wzplayer,能在ios,android,windows,mac等平台上使用,发布此文件紧为方便用户测试. 下载地址:http://www.coolra ...
- How to: Implement a Windows Communication Foundation Service Contract
This is the second of six tasks required to create a basic Windows Communication Foundation (WCF) se ...
- WCF - Self Hosting
WCF - Self Hosting Here, the WCF service is hosted in a console application. Given below is the proc ...
- 个人所得税计算器2016 by Jacksile
个人所得税计算器2016 // (83500+i)) { var to=(all*45/100-13505).toFixed(2); document.getElementById("int ...
- hdu4597Play Game(记忆化)
链接 通化邀请赛的题 貌似不怎么难 记忆化DP 状态方程类似于2维的 只是变成了4维 每次有四种方向去搜 取最棒的 判断好边界条件 #include <iostream> #includ ...
- addlinkedserver
http://blog.sina.com.cn/s/blog_5321db9d0100f89g.html --创建链接服务器 exec sp_addlinkedserver 'ITSV ', ' ' ...
- kernel_task占用大量CPU
- 【转】Eclipse提示No java virtual machine(转载)
原文网址:http://blog.sina.com.cn/s/blog_6cd73dfb01013zkg.html 第一次运行Eclipse,经常会提示下面的问题:... No java virtua ...