ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SLExpandableTableView源码解析(下):深入Objective-C Runtime的双重委托转发技巧

SLExpandableTableView源码解析(下):深入Objective-C Runtime的双重委托转发技巧 SLExpandableTableView源码解析(下)深入Objective-C Runtime的双重委托转发技巧【免费下载链接】SLExpandableTableViewSLExpandableTableView is a UITableView subclass that gives you easy access to expandable and collapsable sections by just implementing a few more delegate and dataSource protocols. (iPhone, iPad, iOS)项目地址: https://gitcode.com/gh_mirrors/sl/SLExpandableTableViewSLExpandableTableView 是一个 UITableView 子类开发者只需额外实现几个委托协议就能在 iPhone/iPad 上轻松获得分组列表的展开与折叠效果并支持展开前的异步数据加载。本篇源码解析下聚焦它最精妙的部分如何利用Objective-C Runtime 的消息转发机制把 delegate 与 dataSource 两个通道的回调双重转发给原始对象同时稳稳拦截住展开/折叠逻辑做到一行回调都不丢失。一、回顾子类为什么必须截胡委托回调上篇我们梳理过 SLExpandableTableView 的展开/折叠状态机。要控制这个开关子类必须抢先看到三类信息每个 section 显示几行、第 0 行显示什么 cell、用户点击了哪一行。但用户的控制器仍然是名义上的 delegate 与 dataSource它们原有的回调一个都不能丢。鱼和熊掌兼得的办法写在 SLExpandableTableView.m 的重写 setter 里- (void)setDelegate:(idSLExpandableTableViewDelegate)delegate { _myDelegate delegate; // 记住真正的委托 [super setDelegate:delegate ? self : nil]; // 告诉 UITableView委托是我自己 }dataSource 同理。从此 UITableView 的所有回调消息都先发给子类自己而子类的策略是需要拦截的方法自己实现处理完手动转发回去没实现的方法交给Runtime 自动转发兜底delegate 和 dataSource两个通道各走一遍这套逻辑。这就是标题中双重委托转发的含义双通道、双机制环环相扣。二、Runtime 判官如何判断一个选择器属于哪个协议自动转发的前提是回答一个问题这条消息本来该发给委托还是发给数据源答案由这个小函数给出SLExpandableTableView.mstatic BOOL protocol_containsSelector(Protocol *protocol, SEL selector) { return protocol_getMethodDescription(protocol, selector, YES, YES).name ! NULL || protocol_getMethodDescription(protocol, selector, NO, YES).name ! NULL; } 这里用 Runtime 的protocol_getMethodDescription直接读取协议元数据分别按必需方法和可选方法查一遍第四个参数YES表示连带继承的父协议一起搜索。只要查得到说明该选择器就是这个协议的方法。巧妙之处在于零硬编码项目里没有任何委托方法清单。将来 iOS 新版本给 UITableView 增加新的回调方法转发逻辑也依然正确源码一行不用改。三、第一重拦截respondsToSelector 的能力借用UITableView 调用可选回调前通常会先问一句respondsToSelector:。执行了setDelegate:self之后被问的对象变成了子类本身——如果子类对tableView:heightForRowAtIndexPath:之类的方法回答不会这些回调就会被静默跳过用户的代码永远收不到。于是子类在 respondsToSelector: 里做了一个能力借用- (BOOL)respondsToSelector:(SEL)aSelector { if (protocol_containsSelector(protocol(UITableViewDataSource), aSelector)) { return [super respondsToSelector:aSelector] || [_myDataSource respondsToSelector:aSelector]; } else if (protocol_containsSelector(protocol(UITableViewDelegate), aSelector)) { return [super respondsToSelector:aSelector] || [_myDelegate respondsToSelector:aSelector]; } return [super respondsToSelector:aSelector]; }选择器属于数据源协议就借用原始数据源的应答能力属于委托协议就借用原始委托的两者都不是则交回父类默认行为。注意这一步只影响查询真正的消息投递由下一节保证。四、核心转发forwardingTargetForSelector 自动投递当消息发到一个没有实现该方法的对象时Runtime 不会立刻崩溃而是进入消息转发流程第一步就是询问forwardingTargetForSelector:——有没有别人能处理这条消息子类的回答SLExpandableTableView.m正是整个库的灵魂- (id)forwardingTargetForSelector:(SEL)aSelector { if (protocol_containsSelector(protocol(UITableViewDataSource), aSelector)) { return _myDataSource; } else if (protocol_containsSelector(protocol(UITableViewDelegate), aSelector)) { return _myDelegate; } return [super forwardingTargetForSelector:aSelector]; }完整流程是这样一条流水线回调消息发给子类子类没有实现该方法常规分发落空Runtime 询问forwardingTargetForSelector:拿到用户的原始 delegate 或 dataSourceRuntime 自动把消息原样重发给目标对象返回值再沿原路带回。⚠️ 这意味着子类没有写一行代码的那几百个回调方法行高、视图、滚动事件……全部被自动转发维护成本为零。五、手动转发被拦截的方法如何还回去Runtime 转发兜底的是没重写的方法而子类重写的那几个方法必须自己处理完逻辑后手动转发这就是第二套机制。以点击回调为例SLExpandableTableView.m- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (/* 可展开 section 的第 0 行 */) { // 触发展开/折叠或先发起数据下载 } else { [self.myDelegate tableView:tableView didSelectRowAtIndexPath:indexPath]; // 手动转发 } }其他被拦截的方法也都是先办事、再转发numberOfRowsInSection:折叠中的可展开 section 只报 1 行展开后才报真实行数cellForRowAtIndexPath:row 0 返回专门的可展开头 cell其余行转发给原始数据源willDisplayCell:区分展开动画进行中与普通显示两种场景再分发给对应回调。两套机制合起来形成闭环任意一条回调要么被子类拦截后手动归还要么被 Runtime 自动投递——消息永不丢失。六、细节与容易踩的坑弱引用防循环_myDelegate、_myDataSource均为__weakSLExpandableTableView.h。表格视图通常由 VC 持有而 VC 恰恰是委托方强引用会直接成环。别用 getter 读原始对象拦截之后super.delegate返回的是子类自己。头文件里特意留下注释never use tableView.delegate as a getter想拿原始对象请用myDelegate/myDataSourceSLExpandableTableView.h。reloadData 也被接管子类的reloadData不直接走父类实现而是经由内部的reloadDataAndResetExpansionStates:同步头部/底部视图与展开状态SLExpandableTableView.m。顺序与兜底判断时先数据源后委托避免同一选择器被抢注不属于两个协议的选择器一律return [super ...]保住 NSObject 的默认行为。七、总结SLExpandableTableView 用约 20 行 Runtime 代码优雅地解决了UITableView 子类如何窥探全部委托消息这个经典难题protocol_containsSelector读取协议元数据判断选择器归属零清单、可随系统演进respondsToSelector:借用原始对象的应答能力防止可选回调被静默丢弃forwardingTargetForSelector:将未实现的方法自动转发给原始 delegate/dataSource重写方法中手动转发兜底与 Runtime 自动转发共同构成双机制闭环。这套拦截 双通道转发的套路并不局限于表格视图——任何需要子类窥探消息流的 UIKit 控件都可以直接套用。想看完整集成方式可以阅读示例工程中的 SLExpandableTableViewController.m。【免费下载链接】SLExpandableTableViewSLExpandableTableView is a UITableView subclass that gives you easy access to expandable and collapsable sections by just implementing a few more delegate and dataSource protocols. (iPhone, iPad, iOS)项目地址: https://gitcode.com/gh_mirrors/sl/SLExpandableTableView创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表