IMYAOPTableView 源码学习笔记
Head
近期由于入職了新公司,接觸到了資訊業務的模塊,看了看代碼發現資訊業務的廣告植入是由IMYAOPTableView 實現的,出于好奇,探索了下源碼,走了一邊流程,雖然框架中還有挺多東西沒看懂[ :( ],這邊先將流程記錄下來
Content
IMYAOPTableView 總體是將業務流與廣告流分開,再記錄原數據源以及代理、新數據源以及新代理,最后再分發給對應的流
框架中的三行代碼,對應的位置就是YYFeedListExample 中的 tableView:didSelectRowAtIndexPath:
這里使用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];是干嘛用的,麻煩哪位好心人看懂了告訴我下....
走進aopUtils的injectTableView方法:
這邊把原數據源、原代理存儲在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:
啊....這些是什么,不懂不懂,看懂的快告訴我....
到這里,就配置好了原始的數據源、代理、動態創建子類、子類覆蓋方法等,接下來就看廣告類的設置
點擊左邊的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 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:方法:
核心在于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 源码学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: spring-boot-devtools
- 下一篇: 【转载】linux-查询rpm包相关安装