iOS runtime整理
runtime简介
Runtime 又叫运行时,是一套底层的 C 语言 API,是 iOS 系统的核心之一。开发者在编码过程中,可以给任意一个对象发送消息,在编译阶段只是确定了要向接收者发送这条消息,而接受者将要如何响应和处理这条消息,那就要看运行时来决定了。 C语言中,在编译期,函数的调用就会决定调用哪个函数。 而OC的函数,属于动态调用过程,在编译期并不能决定真正调用哪个函数,只有在真正运行时才会根据函数的名称找到对应的函数来调用。
typedef struct objc_class *Class;
typedef struct objc_object *id;
@interface Object {
Class isa;
}
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
struct objc_object {
private:
isa_t isa;
}
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
}
- Objective-C 对象都是 C 语言结构体实现的,所有的对象都会包含一个isa_t类型的结构体
- objc_class继承于objc_object。所以在objc_class中也会包含isa_t类型的结构体isa。至此,可以得出结论:Objective-C 中类也是一个对象。
- 我们可以认为id中的isa指针指向的是一个类对象,并且在Class结构体中的isa指针指向元类
对象的实例方法调用时,通过对象的 isa 在类中获取方法的实现。
类对象的类方法调用时,通过类的 isa 在元类中获取方法的实现。
对象,类,元类之间的关系
SEL
typedef struct objc_selector *SEL;
objc_selector是一个映射到方法的C字符串。SEL是系统在编译过程中,会根据方法的名字以及参数序列生成一个用来区分这个方法的唯一ID编号,这个 ID 就是 SEL 类型的。我们需要注意的是,不同类中相同名字的方法所对应的方法选择器是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器。由于这点特性,也导致了OC不支持函数重载。
获取SEL的几种方法:
SEL aSel = @selector(didReceiveMemoryWarning);
SEL a_sel = NSSelectorFromString(@"didReceiveMemoryWarning");
SEL a_Sel = sel_registerName("didReceiveMemoryWarning");
NSLog(@"%p___%p___%p",aSel,a_sel,a_Sel);
Method
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; //方法名
char *method_types OBJC2_UNAVAILABLE; //参数类型以及返回值类型编码
IMP method_imp OBJC2_UNAVAILABLE; //方法实现指针
}
获取方法
// 获取实例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 获取类方法
Method class_getClassMethod ( Class cls, SEL name );
// 获取所有方法的数组
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
IMP
IMP即Implementation,是一个函数指针,指向的是函数的具体实现。在runtime中消息传递和转发的目的就是为了找到IMP,并执行函数。
获取IMP的方法:
//通过Method获取IMP
IMP method_getImplementation(Method m);
// 返回方法的具体实现
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
runtime消息发送与转发
消息发送和转发- NilTest是用来检测是否为nil的,如果检测方法的接受者是nil,那么系统会自动clean并且return。
- Cache的作用主要是为了优化方法调用的性能。当对象receiver调用方法message时,首先根据对象receiver的isa指针查找到它对应的类,然后在类的methodLists中搜索方法,如果没有找到,就使用super_class指针到父类中的methodLists查找,一旦找到就调用方法。如果没有找到,有可能消息转发,也可能忽略它。但这样查找方式效率太低,因为往往一个类大概只有20%的方法经常被调用,占总调用次数的80%。所以使用Cache来缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找。
- MethodTableLookup 可以算是个接口层宏,主要用于保存环境与准备参数,来调用 __class_lookupMethodAndLoadCache3函数
- __class_lookupMethodAndLoadCache3函数也是个接口层(C编写),此函数提供相应参数配置,实际功能在lookUpImpOrForward函数中。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
if (!cls->isRealized()) {
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
}
retry:
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Stop searching, but don't cache yet; call method resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
_class_resolveMethod(cls, sel, inst);
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help. Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
return imp;
}
- 调用realizeClass方法是申请class_rw_t的可读写空间。
- _class_initialize是类初始化的过程。
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
- 这个函数首先判断是否是meta-class类,如果不是元类,就执行_class_resolveInstanceMethod,如果是元类,执行_class_resolveClassMethod。
- 为什么查找类方法的实现过程中会查看resolveInstanceMethod的实现。
其根本原因为 NSObject 为root meta class类的根类,而root meta class 为所有其他meta class的 ISA指向的类。在类方法的巡查过程中,通过meta class的继承关系会最终找到NSObject类,所以在NSObject 的resolveInstanceMethod添加方法决议实现代码,并且在类方法(+号方法)的决议过程中检测该实现是合理的。
消息转发
动态方法解析
+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法);我们有机会为该未知消息新增一个”处理方法””,不过使用该方法的前提是我们已经实现了该”处理方法”
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"methodO:"]) {
Method addM = class_getInstanceMethod([self class], sel_registerName("functionMethodAddO:"));
class_addMethod([self class], sel, method_getImplementation(addM), method_getTypeEncoding(addM));
}
return [super resolveInstanceMethod:sel];
}
备用接收者
如果一个对象实现了这个方法,并返回一个非nil的结果,则这个对象会作为消息的新接收者,且消息会被分发到这个对象
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSString *selectorString = NSStringFromSelector(aSelector);
Class cls = NSClassFromString(@"MrHelp");
// 将消息转发给MrHelp类来处理
if ([selectorString isEqualToString:@"methodTw"]) {
return [cls new];
}
return [super forwardingTargetForSelector:aSelector];
}
完整消息转发
运行时系统会在这一步给消息接收者最后一次机会将消息转发给其它对象。对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。在这个方法中我们可以实现一些更复杂的功能,我们可以对消息的内容进行修改,比如追回一个参数等,然后再去触发消息
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
Class cls = NSClassFromString(@"MrHelp");
NSMethodSignature *singature= [super methodSignatureForSelector:aSelector];
if (!singature) {
singature = [cls instanceMethodSignatureForSelector:sel_registerName("universalMethod:AndClass:")];
}
return singature;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
NSString *selectorString = NSStringFromSelector(anInvocation.selector);
Class cls = NSClassFromString(@"MrHelp");
id methodStr =selectorString;
id className = [anInvocation.target class];
[anInvocation setArgument:&methodStr atIndex:2];//第一个参数
[anInvocation setArgument:&className atIndex:3];//第而个参数
[anInvocation setSelector:sel_registerName("universalMethod:AndClass:")];//universalMethod:AndClass:AndArg:
[anInvocation invokeWithTarget:[cls new]];
}