IMYAOPTableView 源碼學(xué)習(xí)筆記

Head

近期由于入職了新公司,接觸到了資訊業(yè)務(wù)的模塊,看了看代碼發(fā)現(xiàn)資訊業(yè)務(wù)的廣告植入是由IMYAOPTableView 實(shí)現(xiàn)的,出于好奇,探索了下源碼,走了一邊流程,雖然框架中還有挺多東西沒看懂[ :( ],這邊先將流程記錄下來

Content

IMYAOPTableView 總體是將業(yè)務(wù)流與廣告流分開,再記錄原數(shù)據(jù)源以及代理、新數(shù)據(jù)源以及新代理,最后再分發(fā)給對應(yīng)的流 </br>
框架中的三行代碼,對應(yīng)的位置就是YYFeedListExample 中的 tableView:didSelectRowAtIndexPath:

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

這里使用kvc取出業(yè)務(wù)流,精髓就在于設(shè)置aop_utils這個屬性上,我們點(diǎn)擊右邊的aop_utils進(jìn)入:

- (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,設(shè)置aopUtils的tableView對象
                aopUtils = [IMYAOPTableViewUtils aopUtilsWithTableView:self];
                // 設(shè)置關(guān)聯(lián)
                objc_setAssociatedObject(self, kIMYAOPTableUtilsKey, aopUtils, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
                // 注入tableView
                [aopUtils injectTableView];
            }
        }
    }
    return aopUtils;
}

這里創(chuàng)建單例對象,使用runtime關(guān)聯(lián),沒看懂[_IMYAOPTableView aop_setupConfigs];是干嘛用的,麻煩哪位好心人看懂了告訴我下....</br>
走進(jìn)aopUtilsinjectTableView方法:

- (void)injectTableView {
    UITableView *tableView = self.tableView;
    // 記錄原始的數(shù)據(jù)源以及代理對象
    // 這里如果你點(diǎn)Twitter 就是 :T1HomeTimelineItemsViewController
    _origDataSource = tableView.dataSource;
    _origDelegate = tableView.delegate;

    [self injectFeedsView:tableView];
}

這邊把原數(shù)據(jù)源、原代理存儲在aopUtils_origDataSource以及_origDelegate,這邊也就是T1HomeTimelineItemsViewController 對象,再走進(jìn)injectFeedsView方法:

- (void)injectFeedsView:(UIView *)feedsView {
    struct objc_super objcSuper = {.super_class = [self msgSendSuperClass], .receiver = feedsView};
    // 設(shè)置新的數(shù)據(jù)源以及代理對象: objcSuper結(jié)構(gòu)體的地址即為第一個成員(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];
    }
}

這里構(gòu)造了一個結(jié)構(gòu)體objcSuper,使用objc_msgSendSuper發(fā)送消息,個人感覺

((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;

接下來,走進(jìn)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;
}

這里動態(tài)創(chuàng)建子類kIMYAOP_ClassName,并注入實(shí)現(xiàn)該子類方法的類為_IMYAOPTableView,覆蓋父類的實(shí)現(xiàn),比如進(jìn)入到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);
}

這里動態(tài)生成aop_seletor,并添加到子類kIMYAOP_ClassName的方法列表中:

class_addMethod(aopClass, seletor, imp, types);

所以當(dāng)你再調(diào)用aopUtils.tableView.reloadData的時候,會走到_IMYAOPTableViewaop_reloadData方法實(shí)現(xiàn),再往下看bindingFeedsView:aopClass:</br>
啊....這些是什么,不懂不懂,看懂的快告訴我....</br>

到這里,就配置好了原始的數(shù)據(jù)源、代理、動態(tài)創(chuàng)建子類、子類覆蓋方法等,接下來就看廣告類的設(shè)置

點(diǎn)擊左邊的aopUtils

 self.aopDemo.aopUtils = feedsTableView.aop_utils;

進(jìn)入injectTableView

- (void)injectTableView {
    [self.aopUtils.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"AD"];

    ///廣告回調(diào),跟TableView的Delegate,DataSource 一樣。
    self.aopUtils.delegate = self;
    self.aopUtils.dataSource = self;

    dispatch_async(dispatch_get_main_queue(), ^{
        [self insertRows];
    });
}

這里,就將aopUtils的代理設(shè)置成了廣告類,用于最后的分發(fā),往下看insertRows:

- (void)insertRows {
    NSMutableArray<IMYAOPTableViewInsertBody *> *insertBodys = [NSMutableArray array];
    ///隨機(jī)生成了5個要插入的位置
    for (int i = 0; i < 5; i++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:arc4random() % 10 inSection:0];
        [insertBodys addObject:[IMYAOPTableViewInsertBody insertBodyWithIndexPath:indexPath]];
    }
    ///清空 舊數(shù)據(jù)
    [self.aopUtils insertWithSections:nil];
    [self.aopUtils insertWithIndexPaths:nil];

    ///插入 新數(shù)據(jù), 同一個 row 會按數(shù)組的順序 row 進(jìn)行 遞增
    [self.aopUtils insertWithIndexPaths:insertBodys];

    ///調(diào)用tableView的reloadData,進(jìn)行頁面刷新
    [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);
    });
}

進(jìn)入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;
}

原諒我比較懶,我直接看了結(jié)果,就是把廣告的indexPath記錄到sectionMap里把,嗯沒錯,應(yīng)該是。。
最后就是調(diào)用過程了

[self.aopUtils.tableView reloadData];

會走到_IMYAOPTableViewaop_reloadData方法實(shí)現(xiàn)

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

這里會調(diào)用父類(YYTableView)的reloadData方法,YYTableView又調(diào)用了[super reloadData],所以最終是也是調(diào)用[UITableView]reloadData,即走到aop_utils的數(shù)據(jù)源方法上,查看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;

    ///轉(zhuǎn)為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;
}

這里計(jì)算出了最終業(yè)務(wù)流+廣告流的cell數(shù)量</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:
這里區(qū)分該indexPath是廣告流還是業(yè)務(wù)流,查看userIndexPathByFeeds,最終取出dataSource,然后分發(fā)

#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;
    }
    ///如果該位置不是廣告, 則轉(zhuǎn)為邏輯index
    section = [self userSectionByFeeds:section];
    NSIndexPath *userIndexPath = [NSIndexPath indexPathForRow:row - cutCount inSection:section];
    return userIndexPath;
}

END

還有很多地方?jīng)]看明白,還需要多學(xué)習(xí) :)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容