Objective-C 如何用 Blocks 代替 Target-Action

代码放在GitHub
ELAutoSelector
CocoaPods 可用

pod 'ELAutoSelector', '~> 1.0.2'

要解决的问题

Objective-C 开发中经常会遇到带有 target 和 action 两个参数的方法,例如

- (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;

本文中统称这类方法为 Target-Action 方法。

Target-Action 方法调用时必须提供一个明确的接受者 target 和调用的方法 action,常常给开发工作带来不便,比如:

  • UIControl
- (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;

此方法用于注册 UIControlEvent 事件回调。

一般使用此方法的情景是:先调用 UIControl 的 Target-Action 方法,发现需要在本类实现一个 action 方法;去类实现里写一个带有空实现的 action 方法;有了 action 会后再回来补全 Target-Action 方法的参数;之后去实现并调试 action 方法的实现。整个过程灭什么逻辑可言,使用体验很差

如果当前类不适合接受 action,(比如在类方法中创建的 UIControl 对象),需要找到合适的 target 和 action,而且这种写法会造成不不要的逻辑耦合。

action 方法只接受 0~2 个固定的参数,不能自定义参数,如果需要获取其他变量的值,必须将需要获取的值作为属性保存下来,不如 blocks 的自动捕获更加灵活。

这个方法不会产生对 target 的强引用,因此需要开发者自己维护 target 的生命周期。

  • NSTimer
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;

NSTimer 的这个方法通过 Target 和 Action 注册一个在 ti 时间后的回调。注册后 NSTimer 对象会强引用 target ,容易导致引用循环。因此需要仔细斟酌 NSTimer 的释放时机,防止产生内存泄漏,不过手动释放 NSTimer 让开发者仿佛回到了 MRC 时代。

尚不能落地的 Apple 的解决方案

苹果提供了一系列带有 block 的回调方法,但是要求 iOS 10.0+ 才可以使用,短期内还是用不了。

现实的需求

既然苹果的新方法用不上,就只自力更生了。最好的情况是能不用寻找合适的 Target;不用写 Action 方法,用 block 代替;不需要管理 Target 的生命周期;接口简单易用,不需要对原有方法做修改。

解决的思路

构造一个单例 TargetSelectorHelper 来负责作为 target 并保存要执行的 block,运行时将 block 转化为方法,并将方法对应的 SEL 作为 action。显式指定 block 的生命周期依赖者,管理 block 生命周期。

关键问题

  • block 怎样存储?
  • 如何将 block 转化为方法?
  • 怎样保证接口简洁易用?
  • block 回收机制怎样设计?

block 的存储

将传入的 block 连同相关信息放在一个对象内,将对象的内存地址与辅助字符拼装后作为储存 block 的 key。

调用 block 的方法签名也与 key 绑定。通过 NSSelectorFromString()函数将 key 转化成 SEL。

SEL 会被返回,用于作为 action 参数注册。

如果 key 出现碰撞,说明较早存储的 block 已经被释放,因此可以将新的 block 直接替换掉旧的,碰撞不会导致异常。

block 转化为方法

利用 Objective-C 强大的动态特性,使用objc/runtime.h中的函数 imp_implementationWithBlock将 block 转化为 IMP。

将 block 存储对应的key作为方法签名。在首次方法调用时通过+ (BOOL)resolveInstanceMethod:(SEL)sel拦截 SEL 并动态的将 block 转化成的 IMP 与方法绑定

接口简洁易用

block 的语法较复杂,并且需要写入到其他方法的参数内部,因此通过宏定义和函数等手段使项目接口易于调用。

  • Target:
#define ELTarget [ELAutoSelectorHelder shared]
  • Action:
SEL ELAction(ELAutoSelectorAction action, id _Nullable dependency)

block 回收机制的设计

如果开发者在项目中使用了 ELAutoSelectorELAutoSelector 会在每次调用的时候动态创建新方法,同一行代码被调用两次会创建两个不同的新方法作为 Action 的返回值。

block 捕获到的 __strong 类型的对象都会被 block 持有,这将导致这些对象无法被释放。如果不对 block 的生命周期做控制, block 捕获对象的生命周期会影响代码逻辑,使用 ELAutoSelector 有可能产生开发者所不期望的结果。

让 block 捕获声明为 __weak 类型的变量,可以解决这个问题。

除此之外,ELAutoSelector 还有生命周期依赖机制来控制 block 的回收。

SEL ELAction(ELAutoSelectorAction action, id _Nullable dependency)
是通过 block 创建动态方法的接口。其中的第一个参数 action 是希望被执行的 block, 第二个参数 dependency 是 block 的生命周期依赖者。每个 action 会和对应的 dependency 绑定。

如果 dependency 传入的是 nil,对应的 action 将不会被释放。

ELAutoSelector 不会强引用 dependency,它会每 0.5 秒左右轮询一遍所有的 block 对应的 dependency,如果 dependency 被释放了,ELAutoSelector 会用一个空实现替代掉 block,并释放 block。block 通过强引用捕获的变量也会被释放掉。如果在 block 释放后调用这个 block,不会执行任何动作(block 被空实现所替代),也不会引起崩溃,仅会调用 NSLog()打印一行警告信息通知开发者。

如果需要为一个 UIButton 添加 event 的响应事件,比较好的方法是调用 SEL ELAction(ELAutoSelectorAction action, id _Nullable dependency) 时将 dependency 指定为这个 button。需要注意的是: 如果 block 通过强引用捕获了 dependency 会导致内存泄漏。

关注微信订阅号:iOS开发笔记本

http://weixin.qq.com/r/Kji_plrEqwfUrR7F9204

关注知乎专栏:iOS开发学记笔记

https://zhuanlan.zhihu.com/iOSDevNote

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容