OC是一门动态语言,方法的调用本质上是利用objc_msgSend进行"发消息",也就是某类或某对象调用其某方法,本质上是向某个对象的指针发送了一条消息,在此之前方法和对象(或类)都没有真正确定下来即动态绑定,消息与方法的真正实现是在执行阶段绑定的,而非编译阶段.
所谓动态绑定,我举一个简单的C语言例子
#import <stdio.h>
void Chinese() {
printf("Chinese book");
}
void Math() {
printf("Math book");
}
void doTheThing(int type) {
if (type == 0) {
Chinese();
} else {
Math();
}
return 0;
}
对于上面这一类型到底是调用hello函数还是goodbye函数,这两个函数都是已经确定的,就像是有一本语文书一本数学书放在你面前,而你已经知道这两本中的一本最终会被你拿到手上,语文书和数学书已经是放在那里的了,不是动态改变啊的.
#import <stdio.h>
void Chinese() {
printf("Chinese book");
}
void Math() {
printf("Math book");
}
void doTheThing(int type) {
void (*func)();
if (type == 0) {
func = Chinese;
} else {
func = Math;
}
func();
return 0;
}
而对于这一种类型,我们可以看到我们声明了一个函数指针func,就好比你拿到了够买其中一本书的钱,只是钱在你手里,到底是买语文书还是数学书还没有确定,手上的钱是会动态改变的,这叫做动态绑定.
当向一个对象发送消息时,objc_msgSend方法根据对象的isa指针找到对象的类,然后在类的调度表(dispatch table)中查找selector。如果无法找到selector,objc_msgSend通过指向父类的指针找到父类,并在父类的调度表(dispatch table)中查找selector,以此类推直到NSObject类。一旦查找到selector,objc_msgSend方法根据调度表的内存地址调用该实现。 通过这种方式,message与方法的真正实现在执行阶段才绑定。
为了保证消息发送与执行的效率,系统会将全部selector和使用过的方法的内存地址缓存起来。每个类都有一个独立的缓存,缓存包含有当前类自己的 selector以及继承自父类的selector。查找调度表(dispatch table)前,消息发送系统首先检查receiver对象的缓存。
我们现在看一下objc_msgSend如何使用
先在控制器中定义一个方法
- (void)changeColor:(UIColor *)colorOne colorTwo:(UIColor *)colorTwo colorThree:(UIColor *)colorThree colorFour:(UIColor *)colorFour{
static NSUInteger count = 0;
NSUInteger k = count %4 ;
switch (k) {
case 0:
self.view.backgroundColor = colorOne;
break;
case 1:
self.view.backgroundColor = colorTwo;
break;
case 2:
self.view.backgroundColor = colorThree;
break;
case 3:
self.view.backgroundColor = colorFour;
break;
default:
break;
}
count ++;
}
然后对其进行调用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
SEL change = @selector(changeColor:colorTwo:colorThree:colorFour:);
UIColor *colorOne = [UIColor blueColor];
UIColor *colorTwo = [UIColor greenColor];
UIColor *colorThree = [UIColor redColor];
UIColor *colorFour = [UIColor yellowColor];
// 这里到底有几个参数你就放几个id,当然你也可以直接指定类型
((void(*)(id,SEL, id,id,id,id))objc_msgSend)(self,change , colorOne, colorTwo,colorThree,colorFour);
}
交换两个方法
在实际开发中我们会遇到这样一个问题,当项目开发得差不多的时候,或者说到了项目迭代的时候,我们发现了内存泄露(如block的不规范使用导致),不知道到底是哪个View或者说是哪个控制器没有正常被回收.那么我们常用的做法就是在- (void)dealloc方法中打印某些字样,去控制台看到底是在哪些界面跳转或者回跳的时候哪些对象没有调用dealloc方法.
在这个时候我们要是去一个个文件中重写dealloc方法就太繁琐了,而且容易漏掉一些类.面对这种情景,使用分类,在分类中交换方法是最好的解决办法,而且它的实现不需要引入分类头文件
#import "UIView+dealloc.h"
#import <objc/runtime.h>
@implementation UIView (dealloc)
+ (void)load{
Method m2 = class_getInstanceMethod([self class], @selector(myDealloc));
Method m1 = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
method_exchangeImplementations(m2, m1);
}
//系统调用dealloc方法的时候会调用该方法
- (void)myDealloc{
NSLog(@"%@挂了", self);
//此刻实际是在调用dealloc方法
[self myDealloc];
}
我这里写了一个UIView的分类,也就是说所有UIView的子类被回收的时候都能够调用这个犯法,其中+ (void)load 方法会被调用一次,它并不需要该文件被使用才会被调用,也就是在能内存中加载的时候就会被调用,且仅有一次,在这一次调用中我们把UIView的两个对象方法进行了替换(也就是在最早的时候就交换了方法,相当于把这两条神经给交换接上了).
以上就是一个经常遇到的的runtime替换两个方法的使用场景,若有写的不好的地方欢迎指出.
版权声明:本文版权归本文作者所有,始发于简书,如需转载请联系作者,违者必究.
网友评论