BlocksKit初见:一个支持将delegate转换成block的Cocoa库

简介

BlocksKit 是一个开源的框架,对 Cocoa 进行了扩展,将许多需要通过 delegate 调用的方法转换成了 block。在很多情况下,blocks 比 delegate 要方便简单,因为 block 是紧凑的,可以使代码简洁,提高代码可读性,另外 block 还可以进行异步处理。使用 block 要注意避免循环引用。

目录结构

BlocksKit 的所有方法都以bk_开头,这样可以方便地列出所有 BlocksKit 的所有方法。BlocksKit 主要目录结构

  • Core:存放 Foundation 相关的 Block category,如 NSObject、NSTimer、NSarray、NSDictionary、NSSet、NSIndexSet、NSMutableArray等
  • DynamicDelegate:动态代理(消息转发机制)
  • UIKit:扩展了 UIAlertView,UIActionView,UIButton 等

最常用的是 UIKit Category,它为 UIAlertView,UIActionSheet,UIButton,UITapGestureRecognizer 等提供了 blocks。

用法实例

UIAlertView 和 UIActionSheet 用法示例:

UIAlertView *alertView = [[UIAlertView alloc] bk_initWithTitle:@"提示" message:@"提示信息"];
[alertView bk_setCancelButtonWithTitle:@"取消" handler:nil];
[alertView bk_addButtonWithTitle:@"确定" handler:nil];
[alertView bk_setDidDismissBlock:^(UIAlertView *alert, NSInteger index) {
    if (index == 1) {
        NSLog(@"%ld clicked",index);
    }
}];
[alertView show];
[[UIActionSheet bk_actionSheetCustomWithTitle:nil buttonTitles:@[@"查看", @"退出"] destructiveTitle:nil cancelTitle:@"取消" andDidDismissBlock:^(UIActionSheet *sheet, NSInteger index) {

}] showInView:self.view];

UIButton 和 UITapGestureRecognizer 用法示例:

UIButton *button = [[UIButton alloc] init];
[button bk_addEventHandler:^(id sender) {

} forControlEvents:UIControlEventTouchUpInside];
UITapGestureRecognizer *tapGestureRecognizer = [UITapGestureRecognizer bk_recognizerWithHandler:^(UIGestureRecognizer *sender, UIGestureRecognizerState state, CGPoint location) {
    if (state == UIGestureRecognizerStateRecognized) {
        ...
    }
}];

UIButton 和 UIGesture 将 target-action 转换成 block,实现较简单:

- (id)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;
}

- (void)bk_handleAction:(UIGestureRecognizer *)recognizer
{
    void (^handler)(UIGestureRecognizer *sender, UIGestureRecognizerState state, CGPoint location) = recognizer.bk_handler;
    if (!handler) return;

    ...

    if (!delay) {
        block();
        return;
    }

    ...
}

delegate 转换成 block 实际上使用了消息转发机制,是 BlocksKit 源码中最难理解的部分。

原理分析: 消息转发机制

当一个对象收到它没实现的消息的时候,通常会发生如下的情况。

  1. 调用+(BOOL)resolveInstanceMethod:(SEL)aSEL,如果对象在这里动态添加了selector 的实现方法,则消息转发结束,否则执行步骤2
  2. 调用 - (id)forwardingTargetForSelector:(SEL)aSelector,在这里你可以将消息转发给其他对象,如果实现则消息转发结束,否则执行步骤3
  3. 执行完整的消息转发机制,调用-(void)forwardInvocation:(NSInvocation *)invocation 在这一步,你可以修改消息的任何内容,包括目标(target),selector,参数。如果没有实现在这里还未实现转发则程序将抛出异常。

原理实例分析

BlocksKit 动态代理实现方式是最后一步,即-(void)forwardInvocation:(NSInvocation *)invocation,使得动态代理能够接受任意消息。

以UIAlertView为例,UIAlertView在运行时动态关联了A2DynamicUIAlertViewDelegate

@implementation UIAlertView (BlocksKit)

@dynamic bk_willShowBlock, bk_didShowBlock, bk_willDismissBlock, bk_didDismissBlock, bk_shouldEnableFirstOtherButtonBlock;

+ (void)load
{
    @autoreleasepool {
        [self bk_registerDynamicDelegate];
        [self bk_linkDelegateMethods:@{
            @"bk_willShowBlock": @"willPresentAlertView:",
            @"bk_didShowBlock": @"didPresentAlertView:",
            @"bk_willDismissBlock": @"alertView:willDismissWithButtonIndex:",
            @"bk_didDismissBlock": @"alertView:didDismissWithButtonIndex:",
            @"bk_shouldEnableFirstOtherButtonBlock": @"alertViewShouldEnableFirstOtherButton:"
        }];
    }
}

A2DynamicUIAlertViewDelegate 是 A2DynamicDelegate 的子类,并实现了UIAlertViewDelegate 的方法

代理消息的转发由 A2DynamicDelegate 完成

- (void)forwardInvocation:(NSInvocation *)outerInv
{
    SEL selector = outerInv.selector;
    A2BlockInvocation *innerInv = nil;
    if ((innerInv = [self.invocationsBySelectors bk_objectForSelector:selector])) {
        [innerInv invokeWithInvocation:outerInv];
    } else if ([self.realDelegate respondsToSelector:selector]) {
        [outerInv invokeWithTarget:self.realDelegate];
    }
}

注: 文章由我们 iOS122(http://www.ios122.com)的小伙伴 @鱼 整理,喜欢就一起参与: iOS122 任务池

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
在实现ListView单选时,我们可以在 Adapter中自己创建一个selectPosition参数, 这样是能实现需求。 但加入要是再加一个多选 接着又在Adapter中创建了一个记录选中position的集合,也是可以实现的,但是实现起来还是相关繁琐的。过程很不乐观。 这里介绍一种相对简单的实现方式。 关键点,利用 ListView中的Choice来实现。 ListView中自带Choice相关功能,提供单选和多选两种Choice模式。 我们 可以在布局中设置 android:choiceMode
Android系统lunch一个当前的Product大概流程包含以下几个部分: 1. lunch确定TARGET_PRODUCT,一般位于vendor/device/build/target/product中的vendorsetup.sh脚本来定义分别有user/eng/userdebug。 2. 开发check product的合理性。 通过加载vendor/device/build/target/product中的AndroidProduct.mk文件,记录其包含的各个.mk文件以及其所在的路径,作为

android IPC通信(下)-AIDL - 2015-12-18 22:12:50

android IPC通信(上)-sharedUserIdMessenger android IPC通信(中)-ContentProviderSocket 这篇我们将会着重介绍AIDL的使用方式和原理,要介绍AIDL先要简单介绍一下Binder,而且Messenger,ContentProvider和AIDL的最底层都是使用的Binder。 Binder 直观来说,Binder是Android中的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式,Bi

Unity AssetBundles 使用指南 - 2015-12-18 19:12:02

0x00:简介 AssetBundles 是Unity使用的一种资源格式,AssetBundles资源可以在不同项目交叉单独使用,Unity中主要用AssetBundles使资源和可执行文件分离。 0x01:生成AssetBundles AssetBundle可以调用Unity接口: BuildPipeLine .BuildAssetBundle (Object mainAsset, Object[] assets, string pathName, BuildAssetBundleOptions ass
第七章、策略模式 通常如果一个问题有多个解决方案时,最简单的就是利用if-else或者switch-case方式根据不同的情景选择不同的解决方案,但是这样耦合性太高 、代码臃肿、难以维护等。这时就可以使用策略模式来解决。 1.定义 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。 2.使用场景 1.针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。 2.需要安全地封装多种同一类型的操作时。 3.出现同一抽象类有多个子类,而又需

百度推送代码备份 - 2015-12-18 17:12:11

首先查看百度推送开发者文档,这里主要是用Java实现。 http://push.biadu.com Java项目jar 引用,这里使用maven管理jar 包。 HomePage : https://github.com/featherfly/sorm.git dependency groupId cn.featherfly / groupId artifactId bccs-api / artifactId version 3.0.1 / version / dependency 推送工具类封装,这里直
Xcode 7.0 官方免费的真机开发 太阳火神的美丽人生 ( http://blog.csdn.net/opengl_es ) 本文遵循“ 署名-非商业用途-保持一致 ”创作公用协议 转载请保留此句: 太阳火神的美丽人生 -  本博客专注于  敏捷开发及移动和物联设备研究:iOS、Android、Html5、Arduino、pcDuino , 否则,出自本博客的文章拒绝转载或再转载,谢谢合作。 苹果开发需要跟进的另一篇文档:What's New in Xcode 关于免费的真机开发描述如下:

Android Context 到底是什么? - 2015-12-17 22:12:03

什么是Context? 一个Context意味着一个场景,一个场景就是我们和软件进行交互的一个过程。比如当你使用微信的时候,场景包括聊天界面、通讯录、朋友圈,以及背后的一些数据。 那么从程序的角度来看,Context是什么?其实一个Activity就是一个Context,一个Service也是一个Context。 一个应用程序可以认为是一个工作环境,用户在这个工作环境中会切换到不同的场景,这就像一个助理,他可能需要接待客人,可能还要打印文件,还可能接听电话,而这些就称之为不同的场景,助理可称之为一个应用程
美颜包含磨皮、美白、瘦脸等效果,其中磨皮算法在很多博客中均有介绍 例如: 双指数边缘平滑滤波器用于磨皮算法的尝试 选择性模糊及其算法的实现 基于局部均方差相关信息的图像去噪及其在实时磨皮美容算法中的应用 导向滤波磨皮 递归双边滤波磨皮 以上博客均有相关代码/公式,经试验若选取合适参数均有不错的效果,可惜水平有限尚未在shader中实现不卡顿的实时效果~ 观察美图秀秀和华为自带相机等相机APP,发现实时美颜效果均不如PC端和手机端后处理,可能在这一领域目前解决办法不多或者需求不高吧。 下面就探讨简单的美颜滤

闪屏(Splash) - 2015-12-17 19:12:52

好久没弄ReactNative了, 写个如何实现闪屏(Splash)的文章吧. 注意: (1) 如何切换页面. (2) 如何使用计时器TimerMixin. (3) 如何使用动画效果. (4) 如何加载Android的项目资源(图片). 1. 准备 新建项目, 添加主模块 index.android.js . /* @flow */ /** * 测试 * @author wangchenlong */ 'use strict' ; var React = require ( 'react-native'