isa指针

作者: 高思阳 | 来源:发表于2018-10-18 14:16 被阅读22次

    OC是一门面向对象的语言,每一个对象都是类的一个实例。在objective-c语言的内部,每一个对象都有一个isa指针,指向该指针的类。每一个类描述了一系例他的实例的特点,包括成员变量的列表,成员函数的列表。每一个对象都可以接收消息,而对象接收消息列表保存在他所对应的类中。

    当我们初始化一个对象的时候,是怎么发送消息的

    NSObject *obj=[[NSObject alloc] init];
    

    调用方法,其实是给对象发送消息,在编译时这句话会翻译成一个C的函数调用,即:

    objc_msgSend(objc_msgSend([NSObject class],@selector(alloc)),@selector(init));
    

    使用这个函数的需要引入头文件:

    #import <objc/message.h>
    

    那不是把 OC代码转换成C。C语言函数在调用编译的时候就会决定调用哪个函数,而OC是一种动态语言,他会尽可能把代码的从编译链接是推迟到运行时,这就是OC运行时多态。 (给一个对象发送消息,并不会立即执行,而是在运行的时候在去寻找他对应的实现)

    在Xcode中打开,NSObject.h和objc.h,我们可以看到,NSObject就是一个包含isa指针的结构体,按照面向对象的设计原则,所有的事物都应该是对象,所以严格的说OC并不是完全面向对象的(因为含有int double 类型的变量)。在OC语言中,每一个类实际上也是一个对象。每一个类也有一个isa指针。每一个类也可以接收消息,例如代码[NSObject alloc],就是向NSObject这个类发送名为 “alloc” 的消息。

    在oc中,因为类也是一个对象,所以也必须是另外一个类的实例,这个类就是元类(metaclass)。

    • 类对象保存了对象方法的列表。当一个对象方法被调用的时候,对象首先通过isa指针查找到对应的类,类对象会首先查找他本身是否有该方法的实现,如果没有,则元类对象会向他的父类查找方法,这样就可以一直找到继承链的头
    • 元类对象保存了类方法的列表。当一个类方法被调用的时候,类对象首先通过isa指针查找到对应的元类对象,元类对象会首先查找他本身是否有该方法的实现,如果没有,则元类对象会向他的父类查找方法,这样就可以一直找到继承链的头

    一个 Objective-C 方法会被编译成 objc_msgSend,这个函数有两个默认参数,id 类型的 self, SEL 类型的 op。我们先看看 id 的定义:

    typedef struct objc_object *id;
    
    struct objc_object {
      Class _Nonnull isa OBJC_ISA_AVAILABILITY;
    };
    

    我们可以看到,在 objc_object 结构体中,只有一个指向 Class 类型的 isa 指针。

    我们再看看 Class 的定义:

    struct objc_class {
      Class _Nonnull isa OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
    
      Class _Nullable super_class OBJC2_UNAVAILABLE;
    
      const char * _Nonnull name OBJC2_UNAVAILABLE;
    
      long version OBJC2_UNAVAILABLE;
    
      long info OBJC2_UNAVAILABLE;
    
      long instance_size OBJC2_UNAVAILABLE;
    
      struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
    
      struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
    
      struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
    
      struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
    
    #endif
    } OBJC2_UNAVAILABLE;
    

    里面有很多参数,很显眼的能看到这一行:

    struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
    

    看名字也容易理解,这个 methodLists 就是用来存放方法列表的。我们再看看 objc_method_list 这个结构体:

    struct objc_method_list {
    
      struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
    
      int method_count OBJC2_UNAVAILABLE;
    
    #ifdef __LP64__
    
      int space OBJC2_UNAVAILABLE;
    
    #endif
    
      /* variable length structure */
    
      struct objc_method method_list[1] OBJC2_UNAVAILABLE;
    
    }
    

    里面的 objc_method ,也就是我们熟悉的 Method:

    struct objc_method {
    
      SEL _Nonnull method_name OBJC2_UNAVAILABLE;
    
      char * _Nullable method_types OBJC2_UNAVAILABLE;
    
      IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
    }
    

    Method 里面保存了三个参数:

    • 方法的名称
    • 方法的类型
    • 方法的具体实现,由 IMP 指针指向

    经过层层挖掘,我们能明白实例对象调用方法的大致逻辑:

    MyClass *myClass = [[MyClass alloc] init];
    [myClass printLog];
    
    • 先被编译成 ((void (*)(id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));
    • 沿着入参 myClass 的 isa 指针,找到 myClass 的类对象(Class),也就是 MyClass
    • 接着在 MyClass 的方法列表 methodLists 中,找到对应的 Method
    • 最后找到 Method 中的 IMP 指针,执行具体实现

    类对象的类方法又是怎么找到并执行的?

    由上文,我们已经知道,实例对象是通过 isa 指针,找到其类对象(Class)中保存的方法列表中的具体实现的。

    比如:

    MyClass *myClass = [[MyClass alloc] init];
    [myClass printLog];
    

    可以理解为:printLog 方法就是保存在 MyClass 中的。

    那么如果是个类方法,又是保存在什么地方的呢?

    我们回顾下 Class 的定义:

    struct objc_class {
    
      Class _Nonnull isa OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
    
      Class _Nullable super_class OBJC2_UNAVAILABLE;
    
      const char * _Nonnull name OBJC2_UNAVAILABLE;
    
      long version OBJC2_UNAVAILABLE;
    
      long info OBJC2_UNAVAILABLE;
    
      long instance_size OBJC2_UNAVAILABLE;
    
      struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
    
      struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
    
      struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
    
      struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
    
    #endif
    
    } OBJC2_UNAVAILABLE;
    

    可以发现到这一行:

    Class _Nonnull isa OBJC_ISA_AVAILABILITY;
    

    这里的 isa 同样是指向一个 Class 的指针。上文中,我们也知道了类对象的 isa 指针是指向元类对象的。那么不难得出:

    类对象的类方法,是保存在元类对象中的!

    类对象和元类对象都是 Class 类型,仅仅服务的对象不同罢了。找到了元类对象,自然就找到了元类对象中的 methodLists,接下来就和实例对象的方法寻找调用一样的流程了。

    关于对象、类、元类关系:


    对象、类、元类关系
    对象、类、元类关系

    从上图可以看出:

    对象的isa指针指向类对象,类对象的isa指针指向元类,元类的isa指针指向根元类,根元类的isa指针指向自身。

    NSObject的元类的父类是NSObject , NSObject的isa指针又指向NSObject的元类,所以:

    • NSObject类对象 里面的所有的实例对象方法,NSObject元类 也都拥有用(类对象里面的是实例对象的方法,元类对象里面的是类对象的方法)
    • NSObject类对象 可以调用任意 NSObject对象 里的实例方法(因此,所有继承自NSObject的类对象,都可以调用NSObject的实例方法)

    注意:在Objective-C中,几乎所有的类都是继承与NSObject,这里的几乎所有是因为官网关于Cocoa框架有介绍:Cocoa supplies two root classes: NSObject and NSProxy.不继承NSObject的都继承NSProxy,因为NSProxy的应用比较特殊,在Cocoa程序中比较少见,具体的例子可以参考官方API说明文档中关于NSProxy类的介绍,里面讲到了一些例子,比如NSDistantObject

    定义Person类


    image.png

    使用object_getClass()可以获取某个对象的isa指针指向的对象:

    Person *p = [[Person alloc] init];
    
    id class = object_getClass(p1);
    
    id metaClass = object_getClass(class);
    
    id rootMetaClass = object_getClass(metaClass);
    
    [self printMethods:class];
    
    [self printMethods:metaClass];
    
    [self printMethods:rootMetaClass];
    
    //下面方法可以打印出类对象的名称和里面的方法
    - (void) printMethods:(Class)cls
    {
      unsigned int count ;
    
      Method *methods = class_copyMethodList(cls, &count);
    
      NSMutableString *methodNames = [NSMutableString string];
    
      [methodNames appendFormat:@"%@ - ", cls];
    
      for (int i = 0 ; i < count; i++) {
    
        Method method = methods[i];
    
        NSString *methodName = NSStringFromSelector(method_getName(method));
    
        [methodNames appendString: methodName];
    
        [methodNames appendString:@" "];
    
      }
    
      NSLog(@"%@",methodNames);
    
      free(methods);
    
    }
    

    打印结果:


    打印结果

    对象的isa指针指向它的类对象,类对象的isa指针指向它的元类对象,元类的isa指针指向它的根元类对象(这里是NSObject)

    从打印结果可以看出,类对象打印出来的是对象的方法,元类对象打印出来的是类方法。

    链接:https://blog.csdn.net/qq_22854687/article/details/51245568

    相关文章

      网友评论

        本文标题:isa指针

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