Head

近期由于入职了新公司,接触到了资讯业务的模块,看了看代码发现资讯业务的广告植入是由IMYAOPTableView 实现的,出于好奇,探索了下源码,走了一边流程,虽然框架中还有挺多东西没看懂[ :( ],这边先将流程记录下来

Content

IMYAOPTableView 总体是将业务流与广告流分开,再记录原数据源以及代理、新数据源以及新代理,最后再分发给对应的流
框架中的三行代码,对应的位置就是YYFeedListExample 中的 tableView:didSelectRowAtIndexPath:

UITableView *feedsTableView = [ctrl valueForKey:@"feedsView"];
self.aopDemo = [IMYAOPTableDemo new];
self.aopDemo.aopUtils = feedsTableView.aop_utils;
复制代码

这里使用kvc取出业务流,精髓就在于设置aop_utils这个属性上,我们点击右边的aop_utils进入:

- (IMYAOPTableViewUtils *)aop_utils {IMYAOPTableViewUtils *aopUtils = objc_getAssociatedObject(self, kIMYAOPTableUtilsKey);if (!aopUtils) {@synchronized(self) {aopUtils = objc_getAssociatedObject(self, kIMYAOPTableUtilsKey);if (!aopUtils) {///初始化部分配置[_IMYAOPTableView aop_setupConfigs];// 获取aop utils,设置aopUtils的tableView对象aopUtils = [IMYAOPTableViewUtils aopUtilsWithTableView:self];// 设置关联objc_setAssociatedObject(self, kIMYAOPTableUtilsKey, aopUtils, OBJC_ASSOCIATION_RETAIN_NONATOMIC);// 注入tableView[aopUtils injectTableView];}}}return aopUtils;
}
复制代码

这里创建单例对象,使用runtime关联,没看懂[_IMYAOPTableView aop_setupConfigs];是干嘛用的,麻烦哪位好心人看懂了告诉我下....
走进aopUtilsinjectTableView方法:

- (void)injectTableView {UITableView *tableView = self.tableView;// 记录原始的数据源以及代理对象// 这里如果你点Twitter 就是 :T1HomeTimelineItemsViewController_origDataSource = tableView.dataSource;_origDelegate = tableView.delegate;[self injectFeedsView:tableView];
}
复制代码

这边把原数据源、原代理存储在aopUtils_origDataSource以及_origDelegate,这边也就是T1HomeTimelineItemsViewController 对象,再走进injectFeedsView方法:

- (void)injectFeedsView:(UIView *)feedsView {struct objc_super objcSuper = {.super_class = [self msgSendSuperClass], .receiver = feedsView};// 设置新的数据源以及代理对象: objcSuper结构体的地址即为第一个成员(receiver)的地址// objc_msgSendSuper 消息接收者还是 feedsView 只是查询方法是去父类查找// feedsView.delegate = self;// feedsView.dataSource = self;((void (*)(void *, SEL, id))(void *)objc_msgSendSuper)(&objcSuper, @selector(setDelegate:), self);((void (*)(void *, SEL, id))(void *)objc_msgSendSuper)(&objcSuper, @selector(setDataSource:), self);self.origViewClass = [feedsView class];Class aopClass = [self makeSubclassWithClass:self.origViewClass];if (![self.origViewClass isSubclassOfClass:aopClass]) {[self bindingFeedsView:feedsView aopClass:aopClass];}
}
复制代码

这里构造了一个结构体objcSuper,使用objc_msgSendSuper发送消息,个人感觉

((void (*)(void *, SEL, id))(void *)objc_msgSendSuper)(&objcSuper, @selector(setDelegate:), self);
((void (*)(void *, SEL, id))(void *)objc_msgSendSuper)(&objcSuper, @selector(setDataSource:), self);
复制代码

等价于:

feedsView.delegate = self;
feedsView.dataSource = self;
复制代码

接下来,走进makeSubclassWithClass

- (Class)makeSubclassWithClass:(Class)origClass {NSString *className = NSStringFromClass(origClass);NSString *aopClassName = [kA`setupAopClass`PFeedsViewPrefix, stringByAppendingString:className];Class aopClass = NSClassFromString(aopClassName);if (aopClass) {return aopClass;}aopClass = objc_allocateClassPair(origClass, aopClassName.UTF8String, 0);[self setupAopClass:aopClass];objc_registerClassPair(aopClass);return aopClass;
}
复制代码

这里动态创建子类kIMYAOP_ClassName,并注入实现该子类方法的类为_IMYAOPTableView,覆盖父类的实现,比如进入到setupAopClass,查看

[self addOverriteMethod:@selector(reloadData) aopClass:aopClass];
复制代码
- (void)addOverriteMethod:(SEL)seletor aopClass:(Class)aopClass {NSString *seletorString = NSStringFromSelector(seletor);NSString *aopSeletorString = [NSString stringWithFormat:@"aop_%@", seletorString];SEL aopMethod = NSSelectorFromString(aopSeletorString);[self addOverriteMethod:seletor toMethod:aopMethod aopClass:aopClass];
}- (void)addOverriteMethod:(SEL)seletor toMethod:(SEL)toSeletor aopClass:(Class)aopClass {Class implClass = [self implAopViewClass];Method method = class_getInstanceMethod(implClass, toSeletor);if (method == NULL) {method = class_getInstanceMethod(implClass, seletor);}const char *types = method_getTypeEncoding(method);IMP imp = method_getImplementation(method);class_addMethod(aopClass, seletor, imp, types);
}
复制代码

这里动态生成aop_seletor,并添加到子类kIMYAOP_ClassName的方法列表中:

class_addMethod(aopClass, seletor, imp, types);
复制代码

所以当你再调用aopUtils.tableView.reloadData的时候,会走到_IMYAOPTableViewaop_reloadData方法实现,再往下看bindingFeedsView:aopClass:
啊....这些是什么,不懂不懂,看懂的快告诉我....

到这里,就配置好了原始的数据源、代理、动态创建子类、子类覆盖方法等,接下来就看广告类的设置

点击左边的aopUtils

 self.aopDemo.aopUtils = feedsTableView.aop_utils;
复制代码

进入injectTableView

- (void)injectTableView {[self.aopUtils.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"AD"];///广告回调,跟TableView的Delegate,DataSource 一样。self.aopUtils.delegate = self;self.aopUtils.dataSource = self;dispatch_async(dispatch_get_main_queue(), ^{[self insertRows];});
}
复制代码

这里,就将aopUtils的代理设置成了广告类,用于最后的分发,往下看insertRows:

- (void)insertRows {NSMutableArray<IMYAOPTableViewInsertBody *> *insertBodys = [NSMutableArray array];///随机生成了5个要插入的位置for (int i = 0; i < 5; i++) {NSIndexPath *indexPath = [NSIndexPath indexPathForRow:arc4random() % 10 inSection:0];[insertBodys addObject:[IMYAOPTableViewInsertBody insertBodyWithIndexPath:indexPath]];}///清空 旧数据[self.aopUtils insertWithSections:nil];[self.aopUtils insertWithIndexPaths:nil];///插入 新数据, 同一个 row 会按数组的顺序 row 进行 递增[self.aopUtils insertWithIndexPaths:insertBodys];///调用tableView的reloadData,进行页面刷新[self.aopUtils.tableView reloadData];dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"%@", self.aopUtils.allModels);});
}
复制代码

进入insertWithIndexPaths方法:

- (void)insertWithIndexPaths:(NSArray<IMYAOPBaseInsertBody *> *)indexPaths {NSArray<IMYAOPBaseInsertBody *> *array = [indexPaths sortedArrayUsingComparator:^NSComparisonResult(IMYAOPBaseInsertBody *_Nonnull obj1, IMYAOPBaseInsertBody *_Nonnull obj2) {return [obj1.indexPath compare:obj2.indexPath];}];NSMutableDictionary *insertMap = [NSMutableDictionary dictionary];[array enumerateObjectsUsingBlock:^(IMYAOPBaseInsertBody *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {NSInteger section = obj.indexPath.section;NSInteger row = obj.indexPath.row;NSMutableArray *rowArray = insertMap[@(section)];if (!rowArray) {rowArray = [NSMutableArray array];[insertMap setObject:rowArray forKey:@(section)];}while (YES) {BOOL hasEqual = NO;for (NSIndexPath *inserted in rowArray) {if (inserted.row == row) {row++;hasEqual = YES;break;}}if (hasEqual == NO) {break;}}NSIndexPath *insertPath = [NSIndexPath indexPathForRow:row inSection:section];[rowArray addObject:insertPath];obj.resultIndexPath = insertPath;}];self.sectionMap = insertMap;
}
复制代码

原谅我比较懒,我直接看了结果,就是把广告的indexPath记录到sectionMap里把,嗯没错,应该是。。 最后就是调用过程了

[self.aopUtils.tableView reloadData];
复制代码

会走到_IMYAOPTableViewaop_reloadData方法实现

- (void)aop_reloadData {AopDefineVars;aop_utils.isUICalling += 1;AopCallSuper(@selector(reloadData));aop_utils.isUICalling -= 1;
}
复制代码

这里会调用父类(YYTableView)的reloadData方法,YYTableView又调用了[super reloadData],所以最终是也是调用[UITableView]reloadData,即走到aop_utils的数据源方法上,查看IMYAOPTableViewUtils+UITableViewDataSourcenumberOfRowsInSection方法,核心方法就在于

NSIndexPath *feedsIndexPath = [self feedsIndexPathByUser:[NSIndexPath indexPathForRow:rowCount inSection:section]];
复制代码
- (NSIndexPath *)feedsIndexPathByUser:(NSIndexPath *)userIndexPath {if (userIndexPath == nil) {return nil;}NSInteger section = userIndexPath.section;NSInteger row = userIndexPath.row;///转为table sectionsection = [self feedsSectionByUser:section];NSMutableArray<NSIndexPath *> *array = self.sectionMap[@(section)];for (NSIndexPath *obj in array) {if (obj.row <= row) {row += 1;} else {break;}}NSIndexPath *feedsIndexPath = [NSIndexPath indexPathForRow:row inSection:section];return feedsIndexPath;
}
复制代码

这里计算出了最终业务流+广告流的cell数量
再往下看tableView:cellForRowAtIndexPath:方法:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {kAOPUICallingSaved;kAOPUserIndexPathCode;UITableViewCell *cell = nil;if ([dataSource respondsToSelector:@selector(tableView:cellForRowAtIndexPath:)]) {cell = [dataSource tableView:tableView cellForRowAtIndexPath:indexPath];}if (![cell isKindOfClass:[UITableViewCell class]]) {cell = [UITableViewCell new];if (dataSource) {NSAssert(NO, @"Cell is Nil");}}kAOPUICallingResotre;return cell;
}
复制代码

核心在于kAOPUserIndexPathCode: 这里区分该indexPath是广告流还是业务流,查看userIndexPathByFeeds,最终取出dataSource,然后分发

#define kAOPUserIndexPathCode                                           \NSIndexPath *userIndexPath = [self userIndexPathByFeeds:indexPath]; \id<IMYAOPTableViewDataSource> dataSource = nil;                     \if (userIndexPath) {                                                \dataSource = (id)self.origDataSource;                           \indexPath = userIndexPath;                                      \} else {                                                            \dataSource = self.dataSource;                                   \isInjectAction = YES;                                           \}                                                                   \if (isInjectAction) {                                               \self.isUICalling += 1;                                          \}
复制代码
- (NSIndexPath *)userIndexPathByFeeds:(NSIndexPath *)feedsIndexPath {if (!feedsIndexPath) {return nil;}NSInteger section = feedsIndexPath.section;NSInteger row = feedsIndexPath.row;NSMutableArray<NSIndexPath *> *array = self.sectionMap[@(section)];NSInteger cutCount = 0;for (NSIndexPath *obj in array) {if (obj.row == row) {cutCount = -1;break;}if (obj.row < row) {cutCount++;} else {break;}}if (cutCount < 0) {return nil;}///如果该位置不是广告, 则转为逻辑indexsection = [self userSectionByFeeds:section];NSIndexPath *userIndexPath = [NSIndexPath indexPathForRow:row - cutCount inSection:section];return userIndexPath;
}
复制代码

END

还有很多地方没看明白,还需要多学习 :)

转载于:https://juejin.im/post/5cf8d3a8f265da1b6028f3c7

IMYAOPTableView 源码学习笔记相关推荐

  1. Java多线程之JUC包:Semaphore源码学习笔记

    若有不正之处请多多谅解,并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/go2sea/p/5625536.html Semaphore是JUC ...

  2. RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的?

    RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 文章目录 RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 前言 项目 ...

  3. Vuex 4源码学习笔记 - 通过Vuex源码学习E2E测试(十一)

    在上一篇笔记中:Vuex 4源码学习笔记 - 做好changelog更新日志很重要(十) 我们学到了通过conventional-changelog来生成项目的Changelog更新日志,通过更新日志 ...

  4. Vuex 4源码学习笔记 - Vuex是怎么与Vue结合?(三)

    在上一篇笔记中:Vuex源码学习笔记 - Vuex开发运行流程(二) 我们通过运行npm run dev命令来启动webpack,来开发Vuex,并在Vuex的createStore函数中添加了第一个 ...

  5. jquery源码学习笔记三:jQuery工厂剖析

    jquery源码学习笔记二:jQuery工厂 jquery源码学习笔记一:总体结构 上两篇说过,query的核心是一个jQuery工厂.其代码如下 function( window, noGlobal ...

  6. 雷神FFMpeg源码学习笔记

    雷神FFMpeg源码学习笔记 文章目录 雷神FFMpeg源码学习笔记 读取编码并依据编码初始化内容结构 每一帧的视频解码处理 读取编码并依据编码初始化内容结构 在开始编解码视频的时候首先第一步需要注册 ...

  7. Apache log4j-1.2.17源码学习笔记

    (1)Apache log4j-1.2.17源码学习笔记 http://blog.csdn.net/zilong_zilong/article/details/78715500 (2)Apache l ...

  8. PHP Yac cache 源码学习笔记

    YAC 源码学习笔记 本文地址 http://blog.csdn.net/fanhengguang_php/article/details/54863955 config.m4 检测系统共享内存支持情 ...

  9. Vuex 4源码学习笔记 - 通过dispatch一步步来掌握Vuex整个数据流(五)

    在上一篇笔记中:Vuex 4源码学习笔记 - Store 构造函数都干了什么(四) 我们通过查看Store 构造函数的源代码可以看到主要做了三件事情: 初始化一些内部变量以外 执行installMod ...

最新文章

  1. 使用maven构建Spring工程的一些重点
  2. @Component、@Repository、@Service、@Controller区别
  3. boost::get_deleter相关的测试程序
  4. input输入框获取焦点时,光标置于最右
  5. 经典语句,看看让心灵宁静
  6. 腾讯公布5G开放平台全景图,定义12大场景,引入45个应用
  7. 印度首颗 CPU 横空出世:软件开发已开动
  8. iphone 内部函数使用 (函数可能无法响应的部分解决方案)
  9. PostgreSQL数据库常用SQL语句
  10. [CSS3] 使用边框和背景(设置元素的背景)
  11. 鸿蒙音频低延迟,鸿蒙OS 音频播放开发指导
  12. 运维学python用不上_运维朋友们,别再问需不需要学 Python 了!
  13. CSP题目:小明种苹果树
  14. matlab用牛顿迭代法求解方程,牛顿迭代法求方程解 程序如下
  15. EyeKey王姝琦:生物识别的6大误区
  16. JVM Runtime Data Area(运行时数据区中的堆/栈/方法区讲解)
  17. ReportViewer在Chrome 浏览器中无法显示的解决方法
  18. python对txt文本文件进行读写操作
  19. 《数据结构》课程设计任务书[2023-01-24]
  20. 程序员工作2年月薪12K,在线面试指南

热门文章

  1. cfd计算linux windows,CFD计算分析时常用的数值模拟方法 | 坐倚北风
  2. nodejs path.parse()
  3. vscode安装sftp控制文件自动上传
  4. 微信小程序页面跳转的方法
  5. 安卓使用ImageView显示OpenCV-Mat
  6. deeplearning4j – 分布式DL开源项目
  7. Bugku-CTF之你必须让他停下+头等舱
  8. 《Java从小白到大牛》之第11章 对象
  9. 梓益C语言学习笔记之指针
  10. java23中设计模式——行为模式——Memento(备忘机制)