iOS-isa指针;objc实例对象类元类根类根元类

Posted MinggeQingchun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS-isa指针;objc实例对象类元类根类根元类相关的知识,希望对你有一定的参考价值。

一、Object(objc实例对象),Class(类),Metaclass(元类),Rootclass(根类),Rootclass‘s metaclass(根元类)

要理解ios中的isa指针,我们就离不开Objective-C中类的几种数据结构;在Objective-C的类型结构中,Object(实例),Class(类),Metaclass(元类),Rootclass(根类),Rootclass‘s metaclass(根元类),且这些都是对象。

看一下Objective-C对象模型图帮助理解:

Objective-C---->C/C++----->汇编语言---->机器语言;我们知道Objective-C是面向对象语言,其所有的对象都是由其对应的类实例化而来,其实类本身也是一种对象;

在Objective-C中,我们用到的几乎所有类都是NSObject类的子类 

我们打开Xcode中#import <objc/runtime.h>头文件中的NSObject.h文件或者苹果公开源码

(苹果官方公开源码objc4源码在线浏览objc4源码下载,本文使用的objc4-818.2版本)

NSObject类定义格式如下(忽略其方法声明):

@interface NSObject <NSObject> {
    Class isa;
}

打开objc.h文件会发现对类(CLass)实例对象(objc,或者instance)定义如下:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class; //class类对象

/// 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; //objc实例对象

Class是一个指向objc_class(类)结构体的指针,而id是一个指向objc_object(实例对象)结构体的指针。

objc_object(实例对象)中isa指针指向的类结构称为objc_class(该对象的类),其中存放着普通成员变量与对象方法 (“-”开头的方法)。

objc_class(类)中isa指针指向的类结构称为metaclass(该类的元类),其中存放着static类型的成员变量与static类型的方法 (“+”开头的方法)。

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

objc_object(实例对象)结构体中只有isa一个成员属性,指向objc_class(该对象的类)。

runtime.h文件中

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;   //isa指针,指向metaclass(该类的元类)

#if !__OBJC2__

    Class super_class   //指向objc_class(该类)的super_class(父类)

    const char *name    //objc_class(该类)的类名

    long version        //objc_class(该类)的版本信息,初始化为0,可以通过runtime函数class_setVersion和class_getVersion进行修改和读取

    long info           //一些标识信息,如CLS_CLASS表示objc_class(该类)为普通类。ClS_CLASS表示objc_class(该类)为metaclass(元类)

    long instance_size  //objc_class(该类)的实例变量的大小

    struct objc_ivar_list *ivars    //用于存储每个成员变量的地址

    struct objc_method_list **methodLists   //方法列表,与info标识关联

    struct objc_cache *cache        //指向最近使用的方法的指针,用于提升效率

    struct objc_protocol_list *protocols    //存储objc_class(该类)的一些协议
#endif

} OBJC2_UNAVAILABLE;

objc_class(类)objc_object(实例对象)的结构体中多了很多成员,注释中介绍了各个成员的作用。

因此,我们会发现objc实例对象,类对象,元类,根类,根元类中

1、实例对象(Object): 我们创建的一个对象或实例objc其实就是一个struct objc_object结构体,这个结构体只有一个成员变量,这是一个Class类型的变量isa,也是一个结构体指针,这个objc对象的isa指针指向他的类对象(即平时我们所说的类)

objc对象在内存中的排布结构体,如下图:

Objective-C 对象的结构图
ISA指针
根类的实例变量
倒数第二层父类的实例变量
...
父类的实例变量
类的实例变量

类(CLass):存储Object实例的相关数据,如:实例方法列表、成员变量列表、属性列表。

元类(Metaclass):存储Class相关的数据,如:类方法列表、类的信息等。

二、isa指针

我们再看NSObject.mm文件源码,查看一下实例方法 - (CLass)class

// 类方法,返回自身
+ (Class)class {
    return self;
}
 
// 实例方法,查找isa(类)
- (Class)class {
    return object_getClass(self);
}

我们创建实例对象objc,调用 - (CLass)class,实际就是调用object_getClass方法

我们再点击object_getClass方法看源码定义,在objc-class.mm文件中

/***********************************************************************
* object_getClass.
* Locking: None. If you add locking, tell gdb (rdar://7516456).
**********************************************************************/
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

继续点击getIsa()方法,在objc-object.h文件中

inline Class
objc_object::getIsa() 
{
    if (fastpath(!isTaggedPointer())) return ISA();

    extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
    uintptr_t slot, ptr = (uintptr_t)this;
    Class cls;

    slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
    cls = objc_tag_classes[slot];
    if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
        slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        cls = objc_tag_ext_classes[slot];
    }
    return cls;
}

可以将其简化成

inline Class
objc_object::getIsa() 
{
    if (!isTaggedPointer()) return ISA();
    return cls;
}

 继续点击ISA()方法,objc-object.h文件中

在苹果官方公开源码objc4-787.1版本中 

#if SUPPORT_NONPOINTER_ISA

inline Class 
objc_object::ISA() 
{
    ASSERT(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

在苹果官方公开源码objc4-818.2版本中 

//objc-object.h文件
inline Class
objc_object::ISA(bool authenticated)
{
    ASSERT(!isTaggedPointer());
    return isa.getDecodedClass(authenticated);
}


//objc-object.h文件
inline Class
isa_t::getDecodedClass(bool authenticated) {
#if SUPPORT_INDEXED_ISA
    if (nonpointer) {
        return classForIndex(indexcls);
    }
    return (Class)cls;
#else
    return getClass(authenticated);
#endif
}


//objc-object.h文件
inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
    return cls;
#else

    uintptr_t clsbits = bits;

#   if __has_feature(ptrauth_calls)
#       if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
    // Most callers aren't security critical, so skip the
    // authentication unless they ask for it. Message sending and
    // cache filling are protected by the auth code in msgSend.
    if (authenticated) {
        // Mask off all bits besides the class pointer and signature.
        clsbits &= ISA_MASK;
        if (clsbits == 0)
            return Nil;
        clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR));
    } else {
        // If not authenticating, strip using the precomputed class mask.
        clsbits &= objc_debug_isa_class_mask;
    }
#       else
    // If not authenticating, strip using the precomputed class mask.
    clsbits &= objc_debug_isa_class_mask;
#       endif

#   else
    clsbits &= ISA_MASK;
#   endif

    return (Class)clsbits;
#endif
}

我们都可以将其简化成

inline Class 
objc_object::ISA() 
{
    return (Class)(isa.bits & ISA_MASK);
}

追根溯源,我们看到实例方法 - (CLass)class最终获取的即是:结构体objc_objectisa.bits & ISA_MASK

接下来解释一下

1、inline关键字作用
2、在方法objc_object::ISA() 中双冒号::的作用
3、objc_object中的isa
4、isa.bits & ISA_MASK

1、inline关键字

用来定义一个类的内联函数,引入它的主要原因是用它替代C中表达式形式的宏定义。

inline关键字修饰的是内联函数,内联函数用于替代宏定义。取代宏定义的原因是:

(1)C中使用define这种形式宏定义的原因是因为,C语言是一个效率很高的语言,这种宏定义在形式及使用上像一个函数,但它使用预处理器实现,没有了参数压栈,代码生成等一系列的操作,因此,效率很高,这是它在C中被使用的一个主要原因。
(2)这种宏定义在形式上类似于一个函数,但在使用它时,仅仅只是做预处理器符号表中的简单替换,因此它不能进行参数有效性的检测,也就不能享受C++编译器严格类型检查的好处,另外它的返回值也不能被强制转换为可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。
(3)在C++中引入了类及类的访问控制,这样,如果一个操作或者说一个表达式涉及到类的保护成员或私有成员,你就不可能使用这种宏定义来实现(因为无法将this指针放在合适的位置)。
(4)inline 推出的目的,也正是为了取代这种表达式形式的宏定义,它消除了宏定义的缺点,同时又很好地继承了宏定义的优点。

2、方法objc_object::ISA() 中双冒号::

双冒号::用于表示“域操作符”,例:声明了一个类A,类A里声明了一个成员函数void f(),但没有在类的声明里给出f的定义,那么在类外定义f时,就要写成void A::f(),表示这个f()函数是类A的成员函数。

3、objc_object中的isa指针

根据objc_object的定义,可以知道,isa其实是一个isa_t的对象,继续看isa_t的实现:

union isa_t 
{
//这里省略很多变量
}

isa_t是个联合体,也就是说:objc_object 中的isa其实是个结构体 

4、isa.bits & ISA_MASK

isa是个联合体,其内部的属性bits

union isa_t 
{
   //省略部分方法和属性...
    uintptr_t bits;
}

然后看uintptr_t实现:

typedef unsigned long       uintptr_t;

发现其是个unsigned long类型,ISA_MASK的定义:

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# else
#   error unknown architecture for packed isa
# endif

其实ISA_MASK是个数值类型。也就是说判断两个对象是否是同一个class其实是通过比对objc_object中的数值计算后得出的结果是否相等得出的

三、isa指针和Object(objc实例对象),Class(类),Metaclass(元类),Rootclass(根类),Rootclass‘s metaclass(根元类)之间联系

1、子类,父类实例对象测试代码:

    //继承关系: Man : People : NSObject(Man继承自People继承自NSObject)
    NSObject *object_instance = [[NSObject alloc]init];
    People *people = [[People alloc]init];
    Man *man = [[Man alloc]init];
    
    NSLog(@"object_instance----     %p" , object_instance);
    NSLog(@"people----              %p" , people);
    NSLog(@"man----                 %p" , man);

三个实例对象的首地址,输出结果:

打印3个实例对象内存内容

根据objc_objectstruct结构得知,对应的内存内容应该为Class 内容,是一个isa,但是这里的isa是经过初始化后的isa(即包含指向Cls的信息和一些其余信息)。我们可以通过& 0x0000000ffffffff8ULL取出对应的isa指向的Class信息如下

 对应是的$0 $1 $2就是isa指向的内容,po一下

 继续查看类对象的内存分布,对应命令和结果如下

我们会发现

(lldb) x/2gx 0x00007fff800d0660 //NSObject元类地址
0x7fff800d0660: 0x00007fff800d0638 0x0000000000000000 //根元类地址 + 父类地址(=NSObject类对象的地址)
(lldb) x/2gx 0x0000000100728798 //People元类地址
0x100728798: 0x0000000100728770 0x00007fff800d0660 //根元类地址 + 父类地址(=People父类的元类地址)
(lldb) x/2gx 0x0000000100728748 //Man元类地址
0x100728748: 0x0000000100728720 0x0000000100728798 //根元类地址 + 父类地址(=Man父类的元类地址)

所有元类的isa都指向NSObject 对应的元类我们称其为根元类
根元类的superclass指针指向NSObject的类对象,其他子元类的superclass指针指向对应父类的元类。

2、实例对象,类对象,元类对象测试代码:

//自定义
struct objc_classA{
  Class isa;
};

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //isa指针,实例对象,类,元类,根类,根元类
    [self testISAForObjc];
}

//isa指针,实例对象,类,元类,根类,根元类
- (void)testISAForObjc {
    Person *p =  [[Person alloc]init];//Person的实例对象
          
    Class pClass = object_getClass(p);//Person的类对象
      
    struct  objc_classA *pClassA = (__bridge struct objc_classA *)(pClass);//使用结构体转化,拿到isa
      
    Class metaPClass = object_getClass(pClass);//Person的元类对象
    
    NSLog(@"p----           %p" , p);
    NSLog(@"pClass----      %p" , pClass);
    NSLog(@"pClassA----     %p" , pClassA);
    NSLog(@"metaPClass----  %p" , metaPClass);
}

输出如下:
 

1、实例对象的isa的指针的首地址就是实例对象的首地址

2、类对象的isa的指针的首地址就是类对象的首地址

3、元类对象的isa的指针的首地址就是元类对象的首地址(同样的方式可看)

4、实例对象的isa指向的是类对象

5、类对象的isa指向的是元类对象

由此我们可看出其实实例、类、元类最主要的链接通道就是他们的isa指针

(一)isa指针的指向(即isa与实例,类,元类之间联系)

一个objc实例对象的isa指针指向他的类对象(即平时我们所说的类),类对象的isa指针指向他的元类,元类的isa指针指向根元类,所有的元类isa都指向同一个根元类,根元类的isa指针指向根元类本身。根元类super class父类指向NSObject类对象。

objc/instance的isa指向class:当调用对象方法的时候,通过实例对象的isa找到类对象,最后找到类对象里面的对象方法,然后执行相应的对象方法
class的isa指向meta-class:当调用类方法的时候,通过类对象的isa找到元类对象,最后找到元类对象里面的类方法,然后执行相应的类方法

总结一下实例对象,类对象以及元类对象之间的isa指向和继承关系的规则为:

1、实例对象的isa指向该类,类的isa指向元类(metaClass)
2、类的superClass指向其父类,如果该类为根类则值为nil
3、元类的isa指向根元类,如果该元类是根元类则指向自身
4、元类的superClass指向父元类,若根元类则指向该根类

(二)isa指针作用

1、调用某个对象的对象方法

它会首先在自身isa指针指向的objc_class(类)的methodLists中查找该方法,如果找不到则会通过objc_class(类)的super_class指针找到其父类,然后从其methodLists中查找该方法,如果仍然找不到,则继续通过 super_class向上一级父类结构体中查找,直至根class

2、调用某个类方法

它会首先通过自己的isa指针找到metaclass(元类),并从其methodLists中查找该类方法,如果找不到则会通过metaclass(元类)的super_class指针找到父类的metaclass(元类)结构体,然后从methodLists中查找该方法,如果仍然找不到,则继续通过super_class向上一级父类结构体中查 找,直至根metaclass(元类);

注意细节:

运行的时候编译器会将代码转化为objc_msgSend(obj, @selector (makeText)),在objc_msgSend函数中首先通过obj(对象)的isa指针找到obj(对象)对应的class(类)。在class(类)中先去cache中通过SEL(方法的编号)查找对应method(方法),若cache中未找到,再去methodLists中查找,若methodists中未找到,则去superClass中查找,若能找到,则将method(方法)加入到cache中,以方便下次查找,并通过method(方法)中的函数指针跳转到对应的函数中去执行。

GitHub示例代码Demo

参考文章:
iOS开发之runtime(2):浅析NSObject对象的Class

OC-底层实现isa指针

isa的位置以及它的作用

以上是关于iOS-isa指针;objc实例对象类元类根类根元类的主要内容,如果未能解决你的问题,请参考以下文章

Java集合类根接口:Collection 和 Map

iOS开发底层之类的底层探究-05

iOS经典面试题之深入解析objc对象的内存空间数据结构以及isa指针的理解

iOS底层原理之类,元类,根元类探索(上)

猿创征文|iOS经典面试题之深入解析objc对象的内存空间数据结构以及isa指针的理解

类和元类