需求背景
這次有個(gè)需求,就是滑動(dòng)tableView的時(shí)候需要記錄一些事件,停止的時(shí)候也要記錄一些事件,而項(xiàng)目里面的tableView很多,而且以后加的tableView也需要監(jiān)聽這些事件,所以需要統(tǒng)一處理一下,現(xiàn)在的方案就是通過交換tableView的相關(guān)代理方法來做統(tǒng)一的處理
實(shí)現(xiàn)思路
- swizzle的事件都為scrollView的事件,從scrollView這個(gè)類下手
- swizzle scrollView的setDelegate:方法
- 在自定義的setDelegate:方法中swizzle相應(yīng)的代理方法
- 在自定義的swizzle方法中做相關(guān)的處理
最初實(shí)現(xiàn)(有問題版本)
思路很簡(jiǎn)單,接下來看看實(shí)現(xiàn)
- 添加用于交換方法的函數(shù)
/**
* class : 替換方法的類
* originalSelector : 原始方法SEL
* swizzledSelector : 用于交換的SEL
* noneSelector : 原方法SEL對(duì)應(yīng)IMP不存在的時(shí)候用的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);
//已經(jīng)交換過了
if (method_getImplementation(originalMethod) == method_getImplementation(swizzledMethod)) {
return;
}
BOOL originMethodExist = originalMethod != nil;
//源方法不存在就直接添加noneSEL對(duì)應(yīng)的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) {
//當(dāng)前類不存在originalSelector而父類存在的時(shí)候didAddMethod為YES,避免影響父類的相關(guān)方法功能走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相應(yīng)的代理方法
- (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方法中做相關(guān)的處理
#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:方法會(huì)調(diào)用多次,對(duì)應(yīng)的delegate會(huì)執(zhí)行多次方法交換,調(diào)用兩次setDelegate:后相當(dāng)于沒有交換
- 自定義的tableView和UITableView是父子關(guān)系,會(huì)存在子類父類重復(fù)交換的問題
最終實(shí)現(xiàn)
分析
- 需要解決相同類多次交換的問題;可以通過給setDelegate:進(jìn)來的每個(gè)類添加帶類名的SEL,讓這個(gè)SEL指向?qū)?yīng)的swizzle方法的IMP,對(duì)這個(gè)SEL進(jìn)行交換,然后再次交換的時(shí)候判斷是否存在這個(gè)SEL就行
- 需要解決子類父類重復(fù)交換的問題;可以判定originSEL和swizzleSEL的IMP是否相等判斷
- 在前兩條的情況下,子類可能不去做方法交換的邏輯,所以存在子類可以調(diào)用了自定義處理邏輯,但是調(diào)用不到父類方法源方法的問題;可以通過superClass向上查找父類的swizzle方法進(jìn)行調(diào)用
代碼
- UIScrollView分類的改動(dòng)
#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分類的改動(dòng)
@implementation NSObject (LHScrollSwizzleMethod)
- (id)lh_performSelector:(SEL)selector withObjects:(NSArray *)objects
{
// 方法簽名(方法的描述)
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector];
if (signature == nil) {
return nil;
//可以拋出異常也可以不操作。
}
// NSInvocation : 利用一個(gè)NSInvocation對(duì)象包裝一次方法調(diào)用(方法調(diào)用者、方法名、方法參數(shù)、方法返回值)
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
invocation.selector = selector;
// 設(shè)置參數(shù)
NSInteger paramsCount = signature.numberOfArguments - 2; // 除self、_cmd以外的參數(shù)個(gè)數(shù)
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];
}
// 調(diào)用方法
[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