Head
近期由于入职了新公司,接触到了资讯业务的模块,看了看代码发现资讯业务的广告植入是由IMYAOPTableView 实现的,出于好奇,探索了下源码,走了一边流程,虽然框架中还有挺多东西没看懂[ :( ],这边先将流程记录下来
Content
IMYAOPTableView
总体是将业务流与广告流分开,再记录原数据源以及代理、新数据源以及新代理,最后再分发给对应的流 </br>
框架中的三行代码,对应的位置就是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];
是干嘛用的,麻烦哪位好心人看懂了告诉我下....</br>
走进aopUtils
的injectTableView
方法:
- (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
的时候,会走到_IMYAOPTableView
的aop_reloadData
方法实现,再往下看bindingFeedsView:aopClass:
</br>
啊....这些是什么,不懂不懂,看懂的快告诉我....</br>
到这里,就配置好了原始的数据源、代理、动态创建子类、子类覆盖方法等,接下来就看广告类的设置
点击左边的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];
会走到_IMYAOPTableView
的aop_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+UITableViewDataSource
的numberOfRowsInSection
方法,核心方法就在于
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 section
section = [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数量</br>
再往下看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;
}
///如果该位置不是广告, 则转为逻辑index
section = [self userSectionByFeeds:section];
NSIndexPath *userIndexPath = [NSIndexPath indexPathForRow:row - cutCount inSection:section];
return userIndexPath;
}
END
还有很多地方没看明白,还需要多学习 :)