【免费下载链接】ReverseExtension
A UITableView extension that enables cell insertion from the bottom of a table view.
这篇文章精讲 Objective-C 代理模式(Delegate Pattern)的经典实战:开源组件 ReverseExtension 是一个让 UITableView 支持从底部插入 cell(聊天式反向列表)的扩展库。它的内部实现了一个"代理中的代理"——用NSProxy 消息转发机制(forwardInvocation)+ DenyList 黑名单,让库自身与开发者业务代码两个 Delegate 在同一个 tableView 上和谐共存。
一、为什么会有"多Delegate冲突"问题?
UITableView 的delegate属性只能指向一个对象。而反向列表库 ReverseExtension 为了监听滚动、翻转 cell 方向,自己也必须实现UITableViewDelegate:
tableView.re.delegate = self // 业务代码想当 delegate tableView.re.dataSource = self // 同时业务代码还要当 dataSource此时就出现了矛盾:
- 库需要
scrollViewDidScroll、willDisplay等方法来翻转视图; - 业务代码也想实现
scrollViewDidScroll、tableView(_:didSelectRowAt:)等方法。
如果库直接把自己设为tableView.delegate,业务回调就全部丢失;如果反过来,库的功能就失效。
💡 解决思路:再造一个"代理对象",把它设为真正的
tableView.delegate,由它把消息同时转发给所有感兴趣的实现者——这就是本文的主角 UITableViewDelegateProxy.swift。
ReverseExtension 的反向列表效果原理是:把 UITableView 和每个 UITableViewCell 整体旋转 180°,再对 indexPath 做镜像换算,从而"从底部插入"。
二、NSProxy消息转发三件套
Objective-C 中,一个对象收到自己不会处理的消息时,runtime 会依次询问它三件事:
| 步骤 | 方法 | 作用 |
|---|---|---|
| 1️⃣ | respondsToSelector: | 我能不能响应这个方法? |
| 2️⃣ | methodSignatureForSelector: | 给我这个方法的签名(参数/返回值类型) |
| 3️⃣ | forwardInvocation: | 帮我把这个NSInvocation转给真正干活的人 |
只要重写这三个方法,一个类就能"冒充"任意数量的 Delegate。核心实现都在 DelegateProxyBase.m:
- (void)forwardInvocation:(NSInvocation *)anInvocation { for (NSObject *delegate in self.delegates) { if ([delegate respondsToSelector:anInvocation.selector]) { if ([self isDenyDelegateForSelector:anInvocation.selector delegate:delegate]) { continue; // DenyList 命中,跳过这个实现者 } [anInvocation invokeWithTarget:delegate]; // 转发给它 } } }注意这里循环遍历所有 delegates,而不是找到第一个就返回——所以多个 Delegate 会依次收到同一条消息,实现"广播式"共存。
三、DenyList:给特定方法指定"唯一负责人"
广播转发有一个副作用:如果库和业务代码都实现了tableView(_:willDisplay:forRowAt:),两边都会被调用。但有些方法(如 cell 高度、翻转逻辑)只能由一方决定,否则视图会被翻转两次。
ReverseExtension 的解法是引入 DenyDelegateMethod——一条"某方法 + 某 delegate"的配对记录:
@interface DenyDelegateMethod : NSObject @property (readonly, weak) id delegate; // 被排除的实现者(弱引用,防循环引用) @property (readonly, assign) SEL selector; // 被排除的方法 @end它在 DenyDelegateMethod.m 中初始化后,被存入一个NSMapTable(selector 字符串 → delegate 的字典),转发前用isDenyDelegateForSelector:delegate:检查:命中黑名单的对象对该方法"隐身"。
在 Swift 侧,ReverseExtension.swift 组装了完整的 DenyList,把 9 个会翻转视图/产生冲突的方法(如willDisplay、heightForHeaderInSection、didSelectRowAt、iOS 11 的左右滑动手势等)全部指定为"仅库自己处理":
delegateProxy = UITableViewDelegateProxy( delegates: [delegate, self], // 先业务,后库 denyList: denied(delegate) { ... } )四、工程细节亮点
- 弱引用持有 delegates:
NSHashTable weakObjectsHashTable+NSMapTable strongToWeakObjectsMapTable,代理不会反向强持有业务对象,避免内存泄漏(见 DelegateProxyBase.m)。 - 自动接管:业务代码只需写
tableView.re.delegate = self,库就在didSet里创建 Proxy 并执行base?.delegate = delegateProxy(见 ReverseExtension.swift)。 - 可回退:把
re.delegate设为nil,Proxy 销毁,tableView 恢复原生行为,零侵入。
示例工程的完整用法见 ViewController.swift:
tableView.re.dataSource = self tableView.re.delegate = self tableView.re.scrollViewDidReachTop = { scrollView in print("scrollViewDidReachTop") }五、总结:这套模式能迁移到什么场景?
ReverseExtension 给出的是一个通用的Multi-Delegate 代理模式:
- 写一个继承
NSObject的 Proxy,持有delegates数组(弱引用); - 重写
respondsToSelector:/methodSignatureForSelector:/forwardInvocation:三件套做广播转发; - 用 DenyList 为"独占型"方法指定唯一实现者;
- 把它设为控件的真实 delegate,业务方只面向 Proxy 编程。
掌握这套 NSProxy 消息转发 + DenyList 的组合拳,你在任何需要"多个观察者共享一个 delegate 位"的场景(自定义组件库、埋点 SDK、键盘框架等)都能照方抓药。
【免费下载链接】ReverseExtension
A UITableView extension that enables cell insertion from the bottom of a table view.
相关推荐
PokemonGo-DesktopMap深度解析:Electron与Python的完美结合
PokemonGo DesktopMap深度解析:Electron与Python的完美结合 PokemonGo DesktopMap是一款基于Electron框
FastStream消息广播:一对多消息分发模式实现
FastStream消息广播:一对多消息分发模式实现 在现代分布式系统中,消息广播(Broadcast)是一种常见的一对多消息分发模式,它允许单个生产者将消息同
后端消息队列微服务OpenClaw 命令队列(Command Queue)深入解析:多会话并发、队列模式与消息转向机制
OpenClaw 命令队列(Command Queue)深入解析:多会话并发、队列模式与消息转向机制 本文系统讲解 OpenClaw 进程内的命令队列(Comm
AI 应用AI Agent交互助手后端即时通讯网关
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考