IMYAOPTableView 소스 학습 노트

12824 단어

Head


최근에 새로운 회사에 입사하여 정보 업무의 모듈을 접하게 되었습니다. 코드를 보니 정보 업무의 광고 삽입은 IMYAOPTableView에서 이루어졌습니다. 호기심 때문에 다음 코드를 탐색하고 한쪽 절차를 밟았습니다. 비록 구조에 아직 이해하지 못한 것이 많지만 [:(], 이쪽은 절차를 기록합니다.

Content

IMYAOPTableView은 전체적으로 업무 흐름과 광고 흐름을 분리하고 원 데이터 원본과 에이전트, 새로운 데이터 원본과 새로운 에이전트를 기록한 다음에 대응하는 흐름 구조의 세 줄 코드를 나누어 준다. 대응하는 위치는 YYFeedListExampletableView: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];이 왜 쓰는지 모르겠습니다. 어떤 선량한 분이 알아보시면 저에게 알려주세요...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을 다시 호출할 때 _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 *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 *)indexPaths {
    NSArray *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;
}

게으른 저를 용서해 주세요. 제가 직접 결과를 봤는데 광고의 indexPathsectionMap에 기록한 거예요. 네, 맞아요. 아마...마지막으로 호출 과정입니다.
[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+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 section
    section = [self feedsSectionByUser:section];

    NSMutableArray *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 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 *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


아직도 못 알아본 데가 많아요. 공부 많이 해야 돼요.

좋은 웹페이지 즐겨찾기