下午接到一个有趣的问题:
问这种情况要怎么处理?看到这个问题的第一想法就是利用runtime
的方法交换,通过自己的方法替换系统方法,在自己的方法里面添加判断。
当列表滑动的时候会去调用scrollViewDidScroll
这个代理方法,需求实现的切入点应该就是这里。但是Method Swizzling
方法只能替换类本身的方法,对于delegate
这种虚函数一样的东西就无从下手了。所以Method Swizzling
就暂且放在一边,那通过kvo
呢?在扩展里面去监听某个变化的值?scrollView
刚好有个contentOffset
符合要求,但是kvo
是不能写在+ (void)load
方法里面的,这时候灵光一闪,我们可以用Method Swizzling
来替换contentOffset
的set
方法啊!
思路有了,那就上代码:
#import "UIScrollView+Additions.h"
#import <objc/runtime.h>
@implementation UIScrollView (Additions)
+ (void)load {
Method contentOffsetMethod = class_getInstanceMethod([UIScrollView class], @selector(setContentOffset:));
Method class_contentOffsetMethod = class_getInstanceMethod([UIScrollView class], @selector(class_setContentOffset:));
method_exchangeImplementations(contentOffsetMethod, class_contentOffsetMethod);
}
-(void)class_setContentOffset:(CGPoint)contentOffset{
if (contentOffset.y > self.bounds.size.height) {
NSLog(@"change sucess!");
} else {
NSLog(@"change fail!");
}
[self class_setContentOffset:contentOffset];
}
@end
运行效果如下:
image.png
没毛病~
干货: Method Swizzling
这种我们既不需要源代码,也不需要通过继承子类类覆写方法就能改变这个类本身功能的方案被称为——方法调配(method swizzling)
类的方案列表会把选择子的名称映射到相关方法的实现之上,是的“动态消息派发系统”能够根据此找到应该调用的方法。这些方法均以函数指针的形式来表示,这种指针叫做IMP
。原型如下:
id (*IMP)(ID,SEL,...)
NSString
类可以相应lowercaseString
,uppercaseString
,capitalizedString
等选择子。这张映射表中的每个选择子都映射到了不同的IMP之上,如图:
runtime
运行时提供的几个方法都能来操作这张表。deveolper可以向其中新增selector
,也可以改变某个selector
对应的方法实现,还可以交换两个selector
所映射到的指针。经过几次操作之后,类的方法表就会变成下图的样子:
在新的映射表中,多了一个名为newSelector
的selector
,capitalizedString
的实现也变了,而lowercaseString
与uppercaseString
的实现则互相转换了。上述修改均无需编写子类,只要修改了“方法表”的布局,就会反映到程序中所有的NSString
实例指向。这下大家见识到此特性的强大之处了吧?
本条将会谈到如何互换两个方法实现。通过此操作,可为已有的方法添加新功能。不过在讲解怎样添加新功能之前,我们先来看看怎样互换两个已经写好的方法实现。想交换方法实现,可用下列函数:
void method_exchangeImplementations(Method m1, Method m2)
此函数的两个参数表示待交换的两个方法实现,而方法实现则可通过下列函数获得:
//得到类的实例方法(-号方法)
Method class_getInstanceMethod(Class aClass, SEL aSelector)
//得到类的类方法(+号方法)
Method class_getInstanceMethod(Class aClass, SEL aSelector)
此函数根据给定的选择从类中取出与之相关的方法。会执行下列代码,即可交换前面提到的两个方法实现:
Method contentOffsetMethod = class_getInstanceMethod([UIScrollView class], @selector(setContentOffset:));
Method class_contentOffsetMethod = class_getInstanceMethod([UIScrollView class], @selector(class_setContentOffset:));
method_exchangeImplementations(contentOffsetMethod, class_contentOffsetMethod);
从现在开始,如果UIScrollView
上调用了setContentOffset:
,那么将会执行class_contentOffsetMethod:
的方法实现。然后我们可以在class_contentOffsetMethod
方法里面去实现所需的附加功能,并调用原有实现。
-(void)class_setContentOffset:(CGPoint)contentOffset{
NSLog(@"这里调用的先后顺序??");
if (contentOffset.y > self.bounds.size.height) {
NSLog(@"change sucess!");
} else {
NSLog(@"change fail!");
}
[self class_setContentOffset:contentOffset];
}
这段代码看上去好像会陷入递归调用的死循环,不过请记住,此方法是准备和setContentOffset:
方法互换的。所以在运行时,class_setContentOffset:
实际上对应的是setContentOffset:
方法实现。
网友评论