runtime: 运行时机制
学的有点凌乱,记一下
runtime是OC的底层,一套纯C的api,我们平时写的OC代码,都会通过runtime编译成C语言
导入#import <obj/message.h>包含#include<obj/runtime>
图1在build setting中搜索msg把选项改为NO否者会不出现提示且报错。
OC通过消息发送进行传递的 objc_msgSend(class,SEL,arg1, arg2, ...);
class:消息的接收者,SEL:方法名,arg...参数列表
[self sel];运行方法时如果遇到没有的方法会出现报错编译不通过,通过[self performSelector:@selector(sel)];
预加载
+(void)load;
在程序一开始就执行,在main方法之前,想更改全局的一些方法可以放在这里
比如你想重写系统的方法可愿意在这
先写一个url的分类
然后写一个和系统方法类似的方法
+(instancetype)J_URLWithString:(NSString *)URLString{
NSURL *url = [NSURL URLWithString:URLString];
if (url == nil) {
NSLog(@"nil");
}
return url;
}
然后就是重写load方法
+(void)load{
//交换放方法的调用顺序
Method urlWithStr = class_getClassMethod(self, @selector(URLWithString:));
Method urlWithJStr = class_getClassMethod(self, @selector(J_URLWithString:));
method_exchangeImplementations(urlWithStr, urlWithJStr);
}
这样就可以通过交货IMP来实现两个方法的调用了,运行一下
你会发现递归了应为要把自己写的方法中系统方法改为自己写的方法
+(instancetype)JSC_URLWithString:(NSString *)URLString{
NSURL *url = [NSURL JSC_URLWithString:URLString];
if (url == nil) {
NSLog(@"nil");
}
return url;
}
Cache
Cache 为方法调用的性能进行优化,每当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。
Runtime 系统会把被调用的方法存到 Cache 中,如果一个方法被调用,那么它有可能今后还会被调用,下次查找的时候就会效率更高。就像计算机组成原理中 CPU 绕过主存先访问 Cache 一样。
IMP
它就是一个函数指针,方法编号,指向方法。
ISA
objc_object 结构体包含一个 isa 指针,根据 isa 指针就可以找到对象所属的类。
通过kvo可以看到
建一个Person类
[p addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];来监听他的name属性,打个断点这句话运行完,发现p的isa指向的Person变成了系统通过runtime创建的子类NSKVONOtyfing_Person;
我们创建一个NSObject的分类
-(void)J_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
//
NSString *oldName = NSStringFromClass(self.class);
NSString *newName = [@"JKVO_" stringByAppendingString:oldName];
Class newClass = objc_allocateClassPair(self.class/*继承自*/, newName.UTF8String/*类名*/, 0);
objc_registerClassPair(newClass);//注册该类
object_setClass(self, newClass);把该类指向NewClass
//动态添加方法
class_addMethod(newClass, @selector(setName:), (IMP)setName, "v@:@");
}
void setName(id self,SEL _cmd,NSString *newName){
NSLog(@"拿到了%@",newName);
}
Method
Method 代表类中某个方法的类型
方法中的隐藏参数
id self,SEL _cmd;
接受消息的对象(self 所指向的内容,当前方法的对象指针)
方法选择器(_cmd 指向的内容,当前方法的 SEL 指针)
resolveInstanceMethod: 和 resolveClassMethod:
方法添加实例方法实现和类方法实现。runtime在类中找不到方法时会执行的方法
重定向
消息转发机制执行前,Runtime 系统允许我们替换消息的接收者为其他对象。
-(id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"sel = %@",NSStringFromSelector(aSelector));
return [super forwardingTargetForSelector:aSelector];
}
如果此方法返回 nil 或者 self,则会计入消息转发机制(forwardInvocation:),否则将向返回的对象重新发送消息。
转发
当动态方法解析不做处理返回 NO 时,则会触发消息转发机制。这时 forwardInvocation: 方法会被执行,我们可以重写这个方法来自定义我们的转发逻辑:
-(void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"---%@",anInvocation);
//拿SEL方法
SEL sel = [anInvocation selector];
//转发
Animation *anim = [Animation new];
if ([anim respondsToSelector:sel]) {
//调用这个对象
[anInvocation invokeWithTarget:anim];
}else{
[super forwardInvocation:anInvocation];
}
}
在 forwardInvocation: 消息发送前,Runtime 系统会向对象发送methodSignatureForSelector: 消息,并取到返回的方法签名用于生成 NSInvocation 对象。所以重写 forwardInvocation: 的同时也要重写 methodSignatureForSelector: 方法,否则会抛异常。
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSString *sel = NSStringFromSelector(aSelector);
if ([sel isEqualToString:@"run"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
当一个对象没有相应的方法实现而无法响应某消息的时候触发
-(void)doesNotRecognizeSelector:(SEL)aSelector{
NSString *sel = NSStringFromSelector(aSelector);
NSLog(@"---%@ 不存在",sel);
}
可以通过转发实现多继承
网友评论