method swizzle 发生死循环调用

hook相关代码

以下都是很标准的写法了

//
//  NSObject+ScrollDelegateMethodSwizzle.m
//  xxx
//
//  Created by yestinZhao on 2022/3/28.
//

#import "NSObject+ScrollDelegateMethodSwizzle.h"
#import "NSObject+zy_swizzle.h"
#import 

@implementation NSObject (ScrollDelegateMethodSwizzle)


+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      [self zy_swizzleSEL:@selector(scrollViewDidEndDragging:willDecelerate:)
                  withSEL:@selector(zy_swizzled_scrollViewDidEndDragging:willDecelerate:)];
      [self zy_swizzleSEL:@selector(scrollViewDidEndDecelerating:)
                  withSEL:@selector(zy_swizzled_scrollViewDidEndDecelerating:)];
    });
}

#pragma mark - swizzle delegate method

-(void)zy_swizzled_scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
  if (!decelerate) {
    BOOL dragToDragStop = scrollView.tracking && !scrollView.dragging && !scrollView.decelerating;
    if (dragToDragStop) {
      [self scrollViewDidEndScroll];
    }
  }
  [self zy_swizzled_scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
}

-(void)zy_swizzled_scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
  BOOL scrollToScrollStop = !scrollView.tracking && !scrollView.dragging && !scrollView.decelerating;
  if (scrollToScrollStop) {
    [self scrollViewDidEndScroll];
  }
  [self zy_swizzled_scrollViewDidEndDecelerating:scrollView];
}

- (void)scrollViewDidEndScroll {
   //在这里写监听滑动停止要做的事
  if (self.scrollDidEnd) {
    self.scrollDidEnd();
  }
}



static const void *kScrollDidEndKey = @"kScrollDidEndKey"; //scrollDidEnd
- (void)setScrollDidEnd:(void(^)(void))scrollDidEnd {
  objc_setAssociatedObject(self, kScrollDidEndKey, scrollDidEnd, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (void(^)(void))scrollDidEnd {
  return objc_getAssociatedObject(self, kScrollDidEndKey);
}


@end

But,若运行如上代码,会死循环到 zy_swizzled_的方法里。

难道是因为 给NSObject添加了一个 他本身没有的方法导致的?

zy_swizzleSEL:实现

+ (void)zy_swizzleSEL:(SEL)originalSEL withSEL:(SEL)swizzledSEL {
  
  Class class = [self class];
    Method originalMethod = class_getInstanceMethod(class, originalSEL);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSEL);
    
    BOOL didAddMethod =
    class_addMethod(class,
                    originalSEL,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethod) {
        class_replaceMethod(class,
                            swizzledSEL,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

分析上面的代码

目的:对scrollView的delegate对象的代理方法进行hook。

注意

  • 代理对象的类是未知的,一般是ViewController
  • 代理对象,是否实现了 (optional)代理方法,也是未知的。

搜索solution

搜到了 实战 Method Swizzle 的循环Hook问题

下载其github Demo后,运行 hook有效。但是,若把delegate对象HZCategoryView.m中的didSelect...方法注释掉,那么hook就无效了。

于是,在其基础上,探索解决方案,后收获成功。并提交了PR - optimize: 当collectionView的delegate未实现代理方法'collectionView:didSelectIt… #1

successful solution

判断'delegate对象'未实现'代理方法',若未实现,则为其动态
添加一个(兜底、可交换的)'代理方法'的实现。然后,接下来swizzle交换方法后,就不会发生死循环(自己调自己)了。

Demo代码

见 github ScrollDidEndHook
iOS没有提供ScrollView滚动停止的回调的api,然而许多场景是需要它的,eg:

  • 淘宝商品信息流列表,达到一定的滚动步进 / 滚动停止时,封面为可播放的商品卡片们,会以一定规律切换播放next
  • 滚动停止时,控制一些视图的展示隐藏,如去顶部按钮
    ...

Usage

 __weak typeof(self) weakSelf = self;
  [self.tableView setScrollDidEnd:^{
    NSLog(@"滚动停止 -------tableView.contentOffset.y: %@", @(weakSelf.tableView.contentOffset.y));
  }];

你可能感兴趣的:(method swizzle 发生死循环调用)