说到runtime,无论是作为一个资深iOS工程师,还是小白,都能滔滔不绝的说上一些,因为它太重要了,是整个Objective-C动态语言的核心。大多数开发者入手runtime是从黑魔法Swizzling开始的。在这里,以一个程序的生命周期为线索记录分享下自己的其他的一些理解。
程序启动时:
一个APP从点击图标开始,到mian函数执行的函数顺序,不了解的同学可以点这里。其中有一点:
dyld动态链接依赖库,并由 runtime 负责加载成 objc 定义的结构,所有初始化工作结束后,dyld 调用真正的 main 函数。
可见从程序还未完全启动,runtime就开始了它的工作,dyld 开始将程序二进制文件初始化交由 ImageLoader 读取 image,其中包含了我们的类、方法等各种符号,由于 runtime 向 dyld 绑定了回调,当 image 加载到内存后,dyld 会通知 runtime 进行处理,runtime 接手后调用 map_images 做解析和处理,接下来 load_images 中调用 call_load_methods 方法,遍历所有加载进来的 Class,按继承层级依次递归调用 Class 的 +load 方法和其 Category 的 +load 方法(这段引用自sunny大神的博客)。
runtime调用+load方法的顺序:
1、先调用原类的+load方法,再调用其Category的+load方法
2、若干个Category中有函数名一样的方法,则随机调用,无固定顺序
调用方法时:
首先要谈到一个知识点:isa指针。
每个实例对象都有一个isa指针,并指向自己所属的类;每一个类对象的isa指针指向对应的元类;元类的isa指向根元类;根元类的isa指针指向本身。绕晕了的同学自备解晕💊。
再看一下class的内部结构:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
};
知道这一点之后就好理解runtime的消息传递机制了,首先引入头文件:<objc/message.h>。我们在调用方法的时候,xcode会转化成objc_msgSend()的方式,通过实例对象的isa指针去所属类去寻找对应的方法名;
查找顺序:
1、实例方法:本类的缓存方法列表->本类的方法列表->父类的缓存方法列表->父类的方法列表->NSObject->消息转发
2、类方法:本类的元类的缓存方法列表->本类的元类的方法列表->元类的父类的缓存方法列表->元类的父类的方法列表->NSObject->消息转发
如果以上都没有找到对应的方法实现,理论上就要抛异常了,但是还有三个机会可以挽回,正式进入runtime消息转发:
消息转发一:
+(BOOL)resolveInstanceMethod:(SEL)sel{}
示例:
+ (BOOL)resolveInstanceMethod:(SEL)sel{
//方法一:可以在本类在转发IMP指针
if (sel == @selector(eat)) {
class_addMethod([self class], sel, (IMP)testMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void testMethodIMP(id self, SEL _cmd)
{
NSLog(@"call resolveInstanceMethod");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
//方法二:也可以直接在方法里do something
if (sel == @selector(eat)) {
class_addMethod([self class], sel, imp_implementationWithBlock(^{
NSLog(@"call resolveInstanceMethod");
}), "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
消息转发二:可以通过SEL名调用其他方法未声明的方法
-(id)forwardingTargetForSelector:(SEL)aSelector{}
示例:
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(eat)) {
// 将可以执行该sel的类返回,该类可不声明方法
return [PersonNew new];
}
return [super forwardingTargetForSelector:aSelector];
}
消息转发三:自动向父类访问,直到NSObject
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{}
-(void)forwardInvocation:(NSInvocation *)anInvocation{}
示例:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
//尝试获取方法的参数和返回值
if (aSelector == @selector(eat)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
if (anInvocation.selector == @selector(eat)) {
[anInvocation invokeWithTarget:[PersonNewChild new]];
return;
}
[super forwardInvocation:anInvocation];
}
这时runtime消息转发结束。如果还是处理不了这个SEL,最后会调用:
-(void)doesNotRecognizeSelector:(SEL)sel {}
"v@:"的解释:(想看所有的Type Encoding点这里)。
"v" :A void;
"@":An object (whether statically typed or typed id)
" : ":A method selector (SEL)
简单记录一下平时所学,希望每位同学每天都在进步中,加油!