美文网首页
id、isa、objc_msgSend

id、isa、objc_msgSend

作者: 厨子 | 来源:发表于2018-03-14 15:12 被阅读21次
  • 类、类实例、id 类型的数据结构是怎样的?
  • isa 指针作用?
  • 调用实例方法与类方法时,runtime 做了什么?

一、类的数据结构

runtime.h 源码中可以找到类的定义,如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
 
#if !__OBJC2__
    Class super_class                       OBJC2_UNAVAILABLE;  // 父类
    const char *name                        OBJC2_UNAVAILABLE;  // 类名
    long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0
    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;  // 协议链表
#endif
 
} OBJC2_UNAVAILABLE;
  • Class 的定义是 typedef struct objc_class *Class
  • isa: 在Objective-C中,类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类)
  • super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_classNULL
  • cache:用于缓存最近使用的方法,增加命中率。runtime会优先去cache中查找需要调用的方法,如果cache中没有,才去methodLists中查找方法。

二、类实例的数据结构

runtime/objc.h 源码中可以找到objc_object的数据结构定义,如下:

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
  • isa :在类实例中,isa 指向该对象所属的类,比如 NSString 类,NSArray
  • id:是一个objc_object结构类型的指针,可以指向任意类型的对象。id 类型也体现出了 OC 语言的动态性。根据 id 指向的对象的 isa 可以判断出该对象所属的类

三、isa 指针作用

类的 isa 和 类实例的 isa 是有区别的。类的 isa 指向的是 metaClass,因此在查找类方法时,会去该类的 metaClass 中查找;类实例的 isa 指向该类,查找实例方法时,会去该类的方法列表中查找。 objc_msgSend 的汇编代码中,会在返回 isa 的时候,对这两种情况做区分:GetIsaFast NORMAL // r10 = self->isa

怎么验证这种区分呢?在 main.m 文件中,我们改写调用类方法和实例方法的方式:

int main(int argc, char * argv[]) {
    @autoreleasepool {
       
        // 实例方法调用
        IMP objectM = class_getMethodImplementation([CKTestClass class], @selector(objectMethod));
        // 执行
        objectM();
        // 类方法调用
        IMP classM = class_getMethodImplementation(objc_getMetaClass(class_getName([CKTestClass class])), @selector(classMethod));
        // 执行
        classM();
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
  • 在调用类方法时,传递的 self 对象 是 通过 objc_getMetaClass(name) 获取到的metaClass
  • 如果传进去的是 [CKTestClass class] ,那么返回的 IMP = _objc_msgForward,执行会报错:EXC_BAD_ACCESS (code=1, address=0x2d)
再看 metaClass
  • 每一个 metaClassisa 指针都指向基类的metaClass(图中的NSObject的 metaClass
  • 基类的 metaClassisa 指针指向自己,形成一个回路
  • 每一个 metaClasssuper_class 指针指向它原本 Class 的 Super Class 的 metaClass。但是基类的 metaClass 的 Super Class指向 NSObject Class 本身
  • 最上层的类 NSObjectsuper class指向 nil

实例、isametaClass的关系图:

上图虚线是 isa 指针的指向;实线是 superclass 指针的指向。

四、调用实例方法和类方法时,runtime 做了什么?

首先,我们在 main.m 中,定义一个类如下:

@interface CKTestClass : NSObject
@property (nonatomic, assign) NSInteger numbers;
- (void)objectMethod;
+ (void)classMethod;
@end
@implementation CKTestClass
- (void)objectMethod {
    NSLog(@"objectMethod");
}
+ (void)classMethod {
    NSLog(@"classMethod");
}
@end

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        // alloc :类方法,init :实例方法
        CKTestClass *aObject = [[CKTestClass alloc] init];
        // 实例方法调用
        [aObject objectMethod];
        // 类方法调用
        [CKTestClass classMethod];
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

然后,把这段代码转换为编译后的 c++ 伪代码。切换到 main.m 目录,执行命令:

/*系统的版本号不固定,主要看你模拟器中有哪些系统版本*/
xcrun -sdk iphonesimulator11.1 clang -rewrite-objc main.m

会在该目录下生成一个 main.cpp 文件,在该文件中找到 mian 函数:

// @property (nonatomic, assign) NSInteger numbers;
// - (void)objectMethod;
// + (void)classMethod;
/* @end */

// @implementation CKTestClass

// 实例方法 objectMethod 的定义
static void _I_CKTestClass_objectMethod(CKTestClass * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_c9_53fyy4bn4t700cbzpv02l9yw0000gn_T_main_8eb4b7_mi_0);
}

// 类方法 classMethod 的定义
static void _C_CKTestClass_classMethod(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_c9_53fyy4bn4t700cbzpv02l9yw0000gn_T_main_8eb4b7_mi_1);
}
// 生成属性 numbers 的 get 、set 方法
static NSInteger _I_CKTestClass_numbers(CKTestClass * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_CKTestClass$_numbers)); }
static void _I_CKTestClass_setNumbers_(CKTestClass * self, SEL _cmd, NSInteger numbers) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_CKTestClass$_numbers)) = numbers; }
// @end

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        
       // 生成 CKTestClass 的对象 aObject
        CKTestClass *aObject = ((CKTestClass *(*)(id, SEL))(void *)objc_msgSend)((id)((CKTestClass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CKTestClass"), sel_registerName("alloc")), sel_registerName("init"));

       // 调用 实例方法
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)aObject, sel_registerName("objectMethod"));

       // 调用 类方法
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CKTestClass"), sel_registerName("classMethod"));
        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

在 main 函数中,可以发现,实例方法和类方法的调用,都指向了 id objc_msgSend ( id self, SEL op, ... )

objc_msgSend

看一下它的前两个参数

id self

前面介绍过 idobjc_object 类型。objc_object 结构体包含 isa 指针,它可以找到参数 self 所属的类

SEL op

类型 SEL 可以理解为是一个方法名,可以通过方法选择器 @selector() 生成。有了这两个参数,objc_msgSend 可以通过 isa 找到self 所属的类,然后在该类的方法列表中找 SEL

objc_msgSend 用汇编实现的,至于原因,可以访问 为什么objc_msgSend必须要用汇编实现。大概是因为方法 objc_msgSend会返回不同类型的值,比如:

/*
receiver 是 array,selector 是 @selector(count)和 @selector(objectAtIndex:); 还有个参数 6
*/
NSUInteger n = [array count];
id obj = [array objectAtIndex:6];

// clang 后大概是
NSUInteger n = (NSUInteger (*)(id, SEL))objc_msgSend(array,  @selector(count));
id obj = (id (*)(id, SEL, NSUInteger))objc_msgSend(array, @selector(objectAtIndex:), 6);

好了,问题来了, 对于上面两个方法的调用,objc_msgSend 应该返回什么类型?NSUIntegerid ?使用汇编实现,就是为了解决这个问题。

在文件 runtime/Messengers.subproj/objc-msg-x86_64.s 中可以找到 objc_msgSend 的汇编实现:

ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    MESSENGER_START
   
    // 检测(id self)是否是 nil,如果 !self, return nil
    NilTest NORMAL
    // 快速找到 isa
    GetIsaFast NORMAL       // r10 = self->isa
    // 拿到 isa后,检查缓存中是否已经缓存了方法,如果缓存了就调用并返回
    CacheLookup NORMAL, CALL    // calls IMP on success

    NilTestReturnZero NORMAL

    GetIsaSupport NORMAL

// cache miss: go search the method lists
// 如果缓存里没有,那就跳到 LCacheMiss 标签
LCacheMiss:
    // isa still in r10
    MESSENGER_END_SLOW
    jmp __objc_msgSend_uncached

    END_ENTRY _objc_msgSend

objc_msgSend 传递消息的流程梳理如下:

  • 检测(id self)是否是nil,如果 !self, return nil
  • 快速找到 isa
  • 拿到 isa后,检查缓存中是否已经缓存了方法,如果缓存了就调用并返回
  • 如果缓存里没有,就跳到 LCacheMiss ,这里面会执行 __objc_msgSend_uncached
    • 在该类的方法列表中查找,找到了就加入到缓存并返回 IMP
    • 否则向父类的 Class 查找,重复前面的操作
    • 如果一直查找到根类仍旧没有实现。则会进入__objc_msgForward或者__objc_msgForward_stret ; 分别对应的返回是:__objc_forward_handler__objc_forward_stret_handler。下面是 _objc_forward_handler 的默认实现,直接返回错误信息:
__attribute__((noreturn)) void 
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

Reference :

相关文章

网友评论

      本文标题:id、isa、objc_msgSend

      本文链接:https://www.haomeiwen.com/subject/okkyfftx.html