方法的实质
在OC中,方法的实质其实是两部分组成:
1.方法的代号(SEL),
2.方法的实现(IMP),
对象调用方法,实际上就是一个发送消息的过程.
比如[person eat];其实等价于:
objc_msgSend(person, @selector(eat));
或
objc_msgSend(person, NSSelectorFromString(@"eat"));
或
objc_msgSend(person, sel_registerName("eat"));
再比如我们最熟悉的初始化方法
Person *person = [[Person alloc]init];
在底层其实是这样实现的:
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
ps:为能编译过需要把 Buid Setting msg 设置为NO并且导入<objc/runtime.h>
Runtime的作用
上面其实就是调用了苹果提供的RuntimeAPI,即程序运行时.对于我们开发者来讲,其实主要有3个用处:
1.在程序运行的过程中,动态的创建一个类
2.在程序运行的过程中,动态的为某个类添加/修改方法/属性
3.在程序运行的过程中,动态的遍历类的所有方法和成员变量
下面其实都是runtime这些用法的实现.
方法的"懒加载"
首先我们知道,NSObject有如下两个方法:
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
前者是当类调用了没有实现的(就是缺少sel/imp)类方法时调用,后者则是调用了未实现的实例方法时调用.
例如:上面的Person * p = [[Person alloc]init];
我并没有实现run:方法,但是当我
[p performSelector:@selector(run:) withObject:@"100"];
程序当然毫无悬念的崩溃了🤣.
然而现在操蛋的需求是,让person表面上在跑,实际上在飞,程序还不能崩...这怎么可能!!!!!!?
但是,为了实现需求,我突然想到之前提到的方法:
#import "Person.h"
@implementation Person
//当类调用了没有实现的实例方法,会到这来
+ (BOOL)resolveInstanceMethod:(SEL)sel{
//这是添加方法实现,类似懒加载
//prama:1.类,2.方法编号SEL,3.方法实现IMP(函数指针),4.返回值类型(可以为"")
class_addMethod(self, sel, (IMP)fly, "v@:@");
return [super resolveInstanceMethod:sel];
}
void fly(id self,SEL _cmd,NSString* distance){
NSLog(@"我就想飞!飞了%@米",distance);
};
以上代码的原理是:
我们调用run:方法其实是发消息:
objc_msgSend(p, @selector(run:), @"100");
所以实现fly函数时,要接收(p, @selector(run:), @"100")这3个参数
这时就实现了"我就想飞!飞了100米",因为fly是当我们调用时才去加载的,类似于属性的懒加载,故而我们可以说是方法的"懒加载"
ps:我们可以发现OC方法调用时会默认传递两个参数,id self 和 方法代号 sel.
Hook(钩子)
runtime最常见的用法,实现方法的欺骗,直接上代码:
//在load里下钩子(load = 编译前加载)
+ (void)load{
//方法欺骗(替换)
//本来的方法
Method sysMethod = class_getClassMethod([NSURL class], @selector(URLWithString:));
//自定义的方法
Method bbMethod = class_getClassMethod([NSURL class], @selector(BBURLWithString:));
//交换
method_exchangeImplementations(sysMethod, bbMethod);
}
//改URLWithString:方法,能判断空的URL,如果为空返回百度 (交换后如果再掉系统方法会递归奔溃 所以需要写注释)
+ (instancetype)BBURLWithString:(NSString *)URLString{
NSURL *url = [NSURL BBURLWithString:URLString];
if (url == nil || [URLString isEqualToString:@""]) NSLog(@"url 为 空");
return [NSURL URLWithString:@"http://www.baidu.com"];
}
return url;
}
KVO
我们都知道,一个KVO的实现,实际上就是观察属性的setter方法,因此我们实现一个KVO需要几个步骤:
1.动态派生 类的子类(NSKVONotyfing_person)
2.重写setter方法
3.方法中
[self willChangeValueForKey:@"name"]
[super setName:name];
[self didChangeValueForKey:@"name"];
未完待续................................