hook相关代码
以下都是很标准的写法了
//
// NSObject+ScrollDelegateMethodSwizzle.m
// xxx
//
// Created by yestinZhao on 2022/3/28.
//
#import "NSObject+ScrollDelegateMethodSwizzle.h"
#import "NSObject+zy_swizzle.h"
#import <objc/runtime.h>
@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));
}];
网友评论