需求背景
这次有个需求,就是滑动tableView的时候需要记录一些事件,停止的时候也要记录一些事件,而项目里面的tableView很多,而且以后加的tableView也需要监听这些事件,所以需要统一处理一下,现在的方案就是通过交换tableView的相关代理方法来做统一的处理
实现思路
- swizzle的事件都为scrollView的事件,从scrollView这个类下手
- swizzle scrollView的setDelegate:方法
- 在自定义的setDelegate:方法中swizzle相应的代理方法
- 在自定义的swizzle方法中做相关的处理
最初实现(有问题版本)
思路很简单,接下来看看实现
- 添加用于交换方法的函数
/**
* 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);
}
}
- 添加scrollView的分类,swizzle setDelegate:方法
#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);
});
}
- 在自定义的setDelegate:方法中swizzle相应的代理方法
- (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];
}
- 在自定义的swizzle方法中做相关的处理
#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
遇到的问题
- 相同的scrollView 的 setDelegate:方法会调用多次,对应的delegate会执行多次方法交换,调用两次setDelegate:后相当于没有交换
- 自定义的tableView和UITableView是父子关系,会存在子类父类重复交换的问题
最终实现
分析
- 需要解决相同类多次交换的问题;可以通过给setDelegate:进来的每个类添加带类名的SEL,让这个SEL指向对应的swizzle方法的IMP,对这个SEL进行交换,然后再次交换的时候判断是否存在这个SEL就行
- 需要解决子类父类重复交换的问题;可以判定originSEL和swizzleSEL的IMP是否相等判断
- 在前两条的情况下,子类可能不去做方法交换的逻辑,所以存在子类可以调用了自定义处理逻辑,但是调用不到父类方法源方法的问题;可以通过superClass向上查找父类的swizzle方法进行调用
代码
- UIScrollView分类的改动
#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];
}
- NSObject分类的改动
@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