iOS 的那些事儿

iOS swizzle 系统的代理方法

2019-06-22  本文已影响0人  站在下一刻

需求背景

这次有个需求,就是滑动tableView的时候需要记录一些事件,停止的时候也要记录一些事件,而项目里面的tableView很多,而且以后加的tableView也需要监听这些事件,所以需要统一处理一下,现在的方案就是通过交换tableView的相关代理方法来做统一的处理

实现思路

最初实现(有问题版本)

思路很简单,接下来看看实现

/**
 * class : 替换方法的类
 * originalSelector : 原始方法SEL
 * swizzledSelector : 用于交换的SEL
 * noneSelector : 原方法SEL对应IMP不存在的时候用的SEL
**/
void classInstanceMethodSwizzle(Class class, SEL originalSelector, SEL swizzledSelector,SEL noneSelector)
{
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    Method noneMethod = class_getInstanceMethod(class, noneSelector);
    //已经交换过了
    if (method_getImplementation(originalMethod) == method_getImplementation(swizzledMethod)) {
        return;
    }
    
    BOOL originMethodExist = originalMethod != nil;
    //源方法不存在就直接添加noneSEL对应的IMP
    if (!originMethodExist&&noneMethod) {
            class_addMethod(class, originalSelector, method_getImplementation(noneMethod), method_getTypeEncoding(noneMethod));
        return;
    }
    
    BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {
        //当前类不存在originalSelector而父类存在的时候didAddMethod为YES,避免影响父类的相关方法功能走replaceMethod
        class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

#import "LHSwizzle.h"

@implementation UIScrollView (LHExtension)
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        classInstanceMethodSwizzle(self.class,
                                   @selector(setDelegate:),
                                   @selector(cs_setDelegate:),
                                   nil);
    });
}

- (void)cs_setDelegate:(id<UIScrollViewDelegate>)delegate
{
    if ([self isKindOfClass:UITableView.class]) {
        if (delegate) {
            classInstanceMethodSwizzle([delegate class],
                                       @selector(scrollViewWillBeginDragging:),
                                       @selector(swizzling_tableViewWillBeginDragging:),
                                       @selector(none_tableViewWillBeginDragging:));
            
            classInstanceMethodSwizzle([delegate class],
                                       @selector(scrollViewDidEndDecelerating:),
                                       @selector(swizzling_tableViewDidEndDecelerating:),
                                       @selector(none_tableViewDidEndDecelerating:));
            
            
            classInstanceMethodSwizzle([delegate class],
                                       @selector(scrollViewDidEndDragging:willDecelerate:),
                                       @selector(swizzling_tableViewDidEndDragging:willDecelerate:),
                                       @selector(none_tableViewDidEndDragging:willDecelerate:));
            
            
            classInstanceMethodSwizzle([delegate class],
                                       @selector(tableView:didEndDisplayingCell:forRowAtIndexPath:),
                                       @selector(swizzling_tableView:didEndDisplayingCell:forRowAtIndexPath:),
                                       @selector(none_tableView:willDisplayCell:forRowAtIndexPath:));
            
            
        }
        
    }else if ([self isKindOfClass:UICollectionView.class]) {
        if (delegate) {
            classInstanceMethodSwizzle([delegate class],
                                       @selector(scrollViewWillBeginDragging:),
                                       @selector(swizzling_collectionViewWillBeginDragging:),
                                       @selector(none_collectionViewWillBeginDragging:));
            
            
            
            
            classInstanceMethodSwizzle([delegate class],
                                       @selector(scrollViewDidEndDecelerating:),
                                       @selector(swizzling_collectionViewDidEndDecelerating:),
                                       @selector(none_collectionViewDidEndDecelerating:));
            
            
            
            classInstanceMethodSwizzle([delegate class],
                                       @selector(scrollViewDidEndDragging:willDecelerate:),
                                       @selector(swizzling_collectionViewDidEndDragging:willDecelerate:),
                                       @selector(none_collectionViewDidEndDragging:willDecelerate:));
            
            
            
            
            
            classInstanceMethodSwizzle([delegate class],
                                       @selector(collectionView:willDisplayCell:forItemAtIndexPath:),
                                       @selector(none_collectionView:willDisplayCell:forItemAtIndexPath:),
                                       @selector(none_collectionView:willDisplayCell:forItemAtIndexPath:));
            
            
        }
    }
    
    [self cs_setDelegate:delegate];
}
#import "NSObject+LHScrollSwizzleMethod.h"

@implementation NSObject (LHScrollSwizzleMethod)

- (void)swizzling_collectionViewWillBeginDragging:(UICollectionView *)collectionView
{
    [self none_collectionViewWillBeginDragging:collectionView];
    [self swizzling_collectionViewWillBeginDragging:collectionView];
}

- (void)none_collectionViewWillBeginDragging:(UICollectionView *)collectionView
{
    [GBEventRecord event:@"scrollBegin"];
}


- (void)swizzling_collectionViewDidEndDragging:(UICollectionView *)collectionView willDecelerate:(BOOL)decelerate
{
    [self none_collectionViewDidEndDecelerating:collectionView];
    [self swizzling_collectionViewDidEndDragging:collectionView willDecelerate:decelerate];
    
}

- (void)none_collectionViewDidEndDragging:(UICollectionView *)collectionView willDecelerate:(BOOL)decelerate
{
    [self none_collectionViewDidEndDecelerating:collectionView];
}

- (void)swizzling_collectionViewDidEndDecelerating:(UICollectionView *)collectionView
{
    [self none_collectionViewDidEndDecelerating:collectionView];
    [self swizzling_collectionViewDidEndDecelerating:collectionView];
}

- (void)none_collectionViewDidEndDecelerating:(UICollectionView *)collectionView
{
    [GBEvent event:@"scrollEnd"];
}

- (void)swizzling_collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
    [self none_collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath];
    [self swizzling_collectionView:collectionView
                   willDisplayCell:cell
                forItemAtIndexPath:indexPath];
}

- (void)none_collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
    [GBEvent event:@"cellWillShow"];
}

- (void)swizzling_tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self none_tableView:tableView willDisplayCell:cell forRowAtIndexPath:indexPath];
    [self swizzling_tableView:tableView didEndDisplayingCell:cell forRowAtIndexPath:indexPath];
}

- (void)none_tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self none_tableViewWillBeginDragging:tableView];
}

- (void)swizzling_tableViewWillBeginDragging:(UITableView *)tableView
{
    [self none_tableViewWillBeginDragging:tableView];
    [self swizzling_collectionViewWillBeginDragging:tableView];
}

- (void)none_tableViewWillBeginDragging:(UITableView *)tableView
{
    [GBEvent event:@"scrollBegin"];
}

- (void)swizzling_tableViewDidEndDragging:(UITableView *)tableView willDecelerate:(BOOL)decelerate
{
    [self none_tableViewDidEndDecelerating:tableView];
    [self swizzling_tableViewDidEndDragging:tableView willDecelerate:decelerate];
}

- (void)none_tableViewDidEndDragging:(UITableView *)tableView willDecelerate:(BOOL)decelerate
{
    [GBEvent event:@"scrollEnd"];
}

- (void)swizzling_tableViewDidEndDecelerating:(UITableView *)tableView
{
    [self none_tableViewDidEndDecelerating:tableView];
    [self swizzling_collectionViewDidEndDecelerating:tableView];
}

- (void)none_tableViewDidEndDecelerating:(UITableView *)tableView
{
    [GBEvent event:@"scrollEnd"];
}

@end

遇到的问题

最终实现

分析

代码



#define GET_CLASS_CUSTOM_SEL(sel,class)  NSSelectorFromString([NSString stringWithFormat:@"%@_%@",NSStringFromClass(class),NSStringFromSelector(sel)])

- (BOOL)isContainSel:(SEL)sel inClass:(Class)class
{
    unsigned int count;
    Method *methodList = class_copyMethodList(class,&count);
    for (int i = 0; i < count; i++) {
        Method method = methodList[i];
        NSString *tempMethodString = [NSString stringWithUTF8String:sel_getName(method_getName(method))];
        if ([tempMethodString isEqualToString:NSStringFromSelector(sel)]) {
            free(methodList);
            return YES;
        }
    }
    free(methodList);
    return NO;
}

- (void)cs_setDelegate:(id<UIScrollViewDelegate>)delegate
{
    if ([self isKindOfClass:UITableView.class]) {
        if (delegate) {
            BOOL hasSwizzled = NO;
            SEL delegateSEL = GET_CLASS_CUSTOM_SEL(@selector(scrollViewWillBeginDragging:),[delegate class]);
            if (class_getMethodImplementation(delegate.class, @selector(scrollViewWillBeginDragging:)) ==
                class_getMethodImplementation(delegate.class, @selector(swizzling_tableViewWillBeginDragging:))) {
                hasSwizzled = YES;
            }
            if (!hasSwizzled&&![self isContainSel:delegateSEL inClass:[delegate class]]) {
                [self class_addMethod:[delegate class]
                             selector:delegateSEL
                                  imp:method_getImplementation(class_getInstanceMethod([delegate class],@selector(swizzling_tableViewWillBeginDragging:)))
                                types:"v@:@"];
                classInstanceMethodSwizzle([delegate class],
                                           @selector(scrollViewWillBeginDragging:),
                                           delegateSEL,
                                           @selector(none_tableViewWillBeginDragging:));
            }
            hasSwizzled = NO;
            if (class_getMethodImplementation(delegate.class, @selector(scrollViewDidEndDecelerating:)) ==
                class_getMethodImplementation(delegate.class, @selector(swizzling_tableViewDidEndDecelerating:))) {
                hasSwizzled = YES;
            }
            delegateSEL = GET_CLASS_CUSTOM_SEL(@selector(scrollViewDidEndDecelerating:),[delegate class]);
            if (!hasSwizzled&&![self isContainSel:delegateSEL inClass:[delegate class]]) {
                [self class_addMethod:[delegate class]
                             selector:delegateSEL
                                  imp:method_getImplementation(class_getInstanceMethod([delegate class],@selector(swizzling_tableViewDidEndDecelerating:)))
                                types:"v@:@"];
                
                classInstanceMethodSwizzle([delegate class],
                                           @selector(scrollViewDidEndDecelerating:),
                                           delegateSEL,
                                           @selector(none_tableViewDidEndDecelerating:));
            }
            
            hasSwizzled = NO;
            if (class_getMethodImplementation(delegate.class, @selector(scrollViewDidEndDragging:willDecelerate:)) ==
                class_getMethodImplementation(delegate.class, @selector(swizzling_tableViewDidEndDragging:willDecelerate:))) {
                hasSwizzled = YES;
            }
            delegateSEL = GET_CLASS_CUSTOM_SEL(@selector(scrollViewDidEndDragging:willDecelerate:),[delegate class]);
            if (!hasSwizzled&&![self isContainSel:delegateSEL inClass:[delegate class]]) {
                [self class_addMethod:[delegate class]
                             selector:delegateSEL
                                  imp:method_getImplementation(class_getInstanceMethod([delegate class],@selector(swizzling_tableViewDidEndDragging:willDecelerate:)))
                                types:"v@:@"];
                
                classInstanceMethodSwizzle([delegate class],
                                           @selector(scrollViewDidEndDragging:willDecelerate:),
                                           delegateSEL,
                                           @selector(none_tableViewDidEndDragging:willDecelerate:));
            }
            hasSwizzled = NO;
            if (class_getMethodImplementation(delegate.class, @selector(tableView:didEndDisplayingCell:forRowAtIndexPath:)) ==
                class_getMethodImplementation(delegate.class, @selector(swizzling_tableView:didEndDisplayingCell:forRowAtIndexPath:))) {
                hasSwizzled = YES;
            }
            delegateSEL = GET_CLASS_CUSTOM_SEL(@selector(tableView:didEndDisplayingCell:forRowAtIndexPath:),[delegate class]);
            if (!hasSwizzled&&![self isContainSel:delegateSEL inClass:[delegate class]]) {
                [self class_addMethod:[delegate class]
                             selector:delegateSEL
                                  imp:method_getImplementation(class_getInstanceMethod([delegate class],@selector(swizzling_tableView:didEndDisplayingCell:forRowAtIndexPath:)))
                                types:"v@:@"];
                
                classInstanceMethodSwizzle([delegate class],
                                           @selector(tableView:didEndDisplayingCell:forRowAtIndexPath:),
                                           delegateSEL,
                                           @selector(none_tableView:willDisplayCell:forRowAtIndexPath:));
            }
            
        }
        
    }else if ([self isKindOfClass:UICollectionView.class]) {
        if (delegate) {
            BOOL hasSwizzled = NO;
            SEL delegateSEL = GET_CLASS_CUSTOM_SEL(@selector(scrollViewWillBeginDragging:),[delegate class]);
            if (class_getMethodImplementation(delegate.class, @selector(scrollViewWillBeginDragging:)) ==
                class_getMethodImplementation(delegate.class, @selector(swizzling_collectionViewWillBeginDragging:))) {
                hasSwizzled = YES;
            }
            if (!hasSwizzled&&![self isContainSel:delegateSEL inClass:[delegate class]]) {
                [self class_addMethod:[delegate class]
                             selector:delegateSEL
                                  imp:method_getImplementation(class_getInstanceMethod([delegate class],@selector(swizzling_collectionViewWillBeginDragging:)))
                                types:"v@:@"];
                classInstanceMethodSwizzle([delegate class],
                                           @selector(scrollViewWillBeginDragging:),
                                           delegateSEL,
                                           @selector(none_collectionViewWillBeginDragging:));
            }
            
            hasSwizzled = NO;
            if (class_getMethodImplementation(delegate.class, @selector(scrollViewDidEndDecelerating:)) ==
                class_getMethodImplementation(delegate.class, @selector(swizzling_collectionViewDidEndDecelerating:))) {
                hasSwizzled = YES;
            }
            delegateSEL = GET_CLASS_CUSTOM_SEL(@selector(scrollViewDidEndDecelerating:),[delegate class]);
            if (!hasSwizzled&&![self isContainSel:delegateSEL inClass:[delegate class]]) {
                [self class_addMethod:[delegate class]
                             selector:delegateSEL
                                  imp:method_getImplementation(class_getInstanceMethod([delegate class],@selector(swizzling_collectionViewDidEndDecelerating:)))
                                types:"v@:@"];
                
                classInstanceMethodSwizzle([delegate class],
                                           @selector(scrollViewDidEndDecelerating:),
                                           delegateSEL,
                                           @selector(none_collectionViewDidEndDecelerating:));
            }
            
            hasSwizzled = NO;
            if (class_getMethodImplementation(delegate.class, @selector(scrollViewDidEndDragging:willDecelerate:)) ==
                class_getMethodImplementation(delegate.class, @selector(swizzling_collectionViewDidEndDragging:willDecelerate:))) {
                hasSwizzled = YES;
            }
            delegateSEL = GET_CLASS_CUSTOM_SEL(@selector(scrollViewDidEndDragging:willDecelerate:),[delegate class]);
            if (!hasSwizzled&&![self isContainSel:delegateSEL inClass:[delegate class]]) {
                [self class_addMethod:[delegate class]
                             selector:delegateSEL
                                  imp:method_getImplementation(class_getInstanceMethod([delegate class],@selector(swizzling_collectionViewDidEndDragging:willDecelerate:)))
                                types:"v@:@i"];
                classInstanceMethodSwizzle([delegate class],
                                           @selector(scrollViewDidEndDragging:willDecelerate:),
                                           delegateSEL,
                                           @selector(none_collectionViewDidEndDragging:willDecelerate:));
                
                
            }
            
            hasSwizzled = NO;
            if (class_getMethodImplementation(delegate.class, @selector(collectionView:willDisplayCell:forItemAtIndexPath:)) ==
                class_getMethodImplementation(delegate.class, @selector(swizzling_collectionView:willDisplayCell:forItemAtIndexPath:))) {
                hasSwizzled = YES;
            }
            delegateSEL = GET_CLASS_CUSTOM_SEL(@selector(collectionView:willDisplayCell:forItemAtIndexPath:),[delegate class]);
            if (!hasSwizzled&&![self isContainSel:delegateSEL inClass:[delegate class]]) {
                [self class_addMethod:[delegate class]
                             selector:delegateSEL
                                  imp:method_getImplementation(class_getInstanceMethod([delegate class],@selector(swizzling_collectionView:willDisplayCell:forItemAtIndexPath:)))
                                types:"v@:@@@"];
                classInstanceMethodSwizzle([delegate class],
                                           @selector(collectionView:willDisplayCell:forItemAtIndexPath:),
                                           delegateSEL,
                                           @selector(none_collectionView:willDisplayCell:forItemAtIndexPath:));
            }
            
        }
    }
    
    [self cs_setDelegate:delegate];
}
@implementation NSObject (LHScrollSwizzleMethod)

- (id)lh_performSelector:(SEL)selector withObjects:(NSArray *)objects
{
    // 方法签名(方法的描述)
    NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector];
    if (signature == nil) {
        return nil;
        //可以抛出异常也可以不操作。
    }
    
    // NSInvocation : 利用一个NSInvocation对象包装一次方法调用(方法调用者、方法名、方法参数、方法返回值)
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = self;
    invocation.selector = selector;
    
    // 设置参数
    NSInteger paramsCount = signature.numberOfArguments - 2; // 除self、_cmd以外的参数个数
    paramsCount = MIN(paramsCount, objects.count);
    for (NSInteger i = 0; i < paramsCount; i++) {
        id object = objects[i];
        if ([object isKindOfClass:[NSNull class]]) continue;
        [invocation setArgument:&object atIndex:i + 2];
    }
    
    // 调用方法
    [invocation invoke];
    
    // 获取返回值
    id returnValue = nil;
    if (signature.methodReturnLength) { // 有返回值类型,才去获得返回值
        [invocation getReturnValue:&returnValue];
    }
    
    return returnValue;
}

- (void)callOriginSEL:(SEL)sel params:(NSArray *)arr
{
    Class curClass = self.class;
    while (curClass) {
        SEL delegateSEL = GET_CLASS_CUSTOM_SEL(sel,curClass);
        if ([self respondsToSelector:delegateSEL]) {
            [self snk_performSelector:delegateSEL withObjects:arr];
            break;
        }
        curClass = [curClass superclass];
    }
}

- (void)swizzling_collectionViewWillBeginDragging:(UICollectionView *)collectionView
{
    [self none_collectionViewWillBeginDragging:collectionView];
    [self callOriginSEL:@selector(scrollViewWillBeginDragging:) params:@[collectionView]];
}

- (void)swizzling_collectionViewDidEndDragging:(UICollectionView *)collectionView willDecelerate:(BOOL)decelerate
{
    [self none_collectionViewDidEndDecelerating:collectionView];
    [self callOriginSEL:@selector(scrollViewDidEndDragging:willDecelerate:) params:@[collectionView,@(decelerate)]];
}

- (void)swizzling_collectionViewDidEndDecelerating:(UICollectionView *)collectionView
{
    [self none_collectionViewDidEndDecelerating:collectionView];
    [self callOriginSEL:@selector(scrollViewDidEndDecelerating:) params:@[collectionView]];
}

- (void)swizzling_collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
    [self none_collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath];
    [self callOriginSEL:@selector(collectionView:willDisplayCell:forItemAtIndexPath:) params:@[collectionView,cell,indexPath]];
}


- (void)swizzling_tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self none_tableView:tableView willDisplayCell:cell forRowAtIndexPath:indexPath];
    [self callOriginSEL:@selector(tableView:didEndDisplayingCell:forRowAtIndexPath:) params:@[tableView,cell,indexPath]];
}

- (void)swizzling_tableViewWillBeginDragging:(UITableView *)tableView
{
    [self none_tableViewWillBeginDragging:tableView];
    [self callOriginSEL:@selector(scrollViewWillBeginDragging:) params:@[tableView]];
}

- (void)swizzling_tableViewDidEndDragging:(UITableView *)tableView willDecelerate:(BOOL)decelerate
{
    [self none_tableViewDidEndDecelerating:tableView];
    [self callOriginSEL:@selector(scrollViewDidEndDragging:willDecelerate:) params:@[tableView,@(decelerate)]];
}

- (void)swizzling_tableViewDidEndDecelerating:(UITableView *)tableView
{
    [self none_tableViewDidEndDecelerating:tableView];
    [self callOriginSEL:@selector(scrollViewDidEndDecelerating:) params:@[tableView]];
}

- (void)swizzling_collectionViewWillBeginDragging:(UICollectionView *)collectionView
{
    [self none_collectionViewWillBeginDragging:collectionView];
    [self swizzling_collectionViewWillBeginDragging:collectionView];
}

- (void)none_collectionViewWillBeginDragging:(UICollectionView *)collectionView
{
    [GBEventRecord event:@"scrollBegin"];
}

- (void)none_collectionViewDidEndDragging:(UICollectionView *)collectionView willDecelerate:(BOOL)decelerate
{
    [self none_collectionViewDidEndDecelerating:collectionView];
}

- (void)none_collectionViewDidEndDecelerating:(UICollectionView *)collectionView
{
    [GBEvent event:@"scrollEnd"];
}

- (void)none_collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
    [GBEvent event:@"cellWillShow"];
}

- (void)none_tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self none_tableViewWillBeginDragging:tableView];
}

- (void)none_tableViewWillBeginDragging:(UITableView *)tableView
{
    [GBEvent event:@"scrollBegin"];
}

- (void)none_tableViewDidEndDragging:(UITableView *)tableView willDecelerate:(BOOL)decelerate
{
    [GBEvent event:@"scrollEnd"];
}

- (void)none_tableViewDidEndDecelerating:(UITableView *)tableView
{
    [GBEvent event:@"scrollEnd"];
}

@end
上一篇 下一篇

猜你喜欢

热点阅读