☰
Objective-C代理模式精讲:UITableView反向列表库ReverseExtension如何用NSProxy消息转发+DenyList实现多Delegate共存
2026/10/11 14:24:10 网站建设 项目流程

【免费下载链接】ReverseExtension

A UITableView extension that enables cell insertion from the bottom of a table view.

项目地址:https://gitcode.com/gh_mirrors/re/ReverseExtension
点击查看免费下载

这篇文章精讲 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 代理模式:

  1. 写一个继承NSObject的 Proxy,持有delegates数组(弱引用);
  2. 重写respondsToSelector:/methodSignatureForSelector:/forwardInvocation:三件套做广播转发;
  3. 用 DenyList 为"独占型"方法指定唯一实现者;
  4. 把它设为控件的真实 delegate,业务方只面向 Proxy 编程。

掌握这套 NSProxy 消息转发 + DenyList 的组合拳,你在任何需要"多个观察者共享一个 delegate 位"的场景(自定义组件库、埋点 SDK、键盘框架等)都能照方抓药。

【免费下载链接】ReverseExtension

A UITableView extension that enables cell insertion from the bottom of a table view.

项目地址:https://gitcode.com/gh_mirrors/re/ReverseExtension
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询