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

news/2024/11/25 0:50:46/

一、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的位置以及它的作用_第1张图片

二、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_ISAinline Class 
objc_object::ISA() 
{ASSERT(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISAif (isa.nonpointer) {uintptr_t slot = isa.indexcls;return classForIndex((unsigned)slot);}return (Class)isa.bits;
#elsereturn (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_ISAif (nonpointer) {return classForIndex(indexcls);}return (Class)cls;
#elsereturn getClass(authenticated);
#endif
}//objc-object.h文件
inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISAreturn cls;
#elseuintptr_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#   elseclsbits &= ISA_MASK;
#   endifreturn (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);//使用结构体转化,拿到isaClass 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的位置以及它的作用


http://www.ppmy.cn/news/188743.html

相关文章

java如何驱动z4mplus打印二维码_斑马 Zebra 打印机驱动下载

斑马Zebra打印机驱动下载 环境支持&#xff1a; Windows Vista、Windows 7、Windows 8 和 Windows 10的所有版本。 Windows Server 2008、2008 R2、2012, 2012 R2 和 2016。 32 位和 64 位 (x64) 版本。 支持打印机型号&#xff1a; Zebra 105 Zebra 105S (152 dpi) Zebra 105S…

关于 Kubernetes中集群统一日志管理方案(Elasticsearch+Filebeat+Kibana+Metricbeat)搭建的一些笔记

写在前面 学习K8s&#xff0c;所以整理分享给小伙伴这里要说明的是&#xff1a;这一套方案太吃硬件了&#xff0c;需要高配的本才能跑起来我的16G运存,集群用三个虚机部署的&#xff0c;工作节点都是3核5G的配置折腾了两天没有跑起来&#xff0c;后来放弃了&#xff0c;查了下&…

【STM32F429】第8章 ThreadX GUIX移植到STM32F429(MDK AC6)

最新教程下载&#xff1a;http://www.armbbs.cn/forum.php?modviewthread&tid98429 第8章 ThreadX GUIX移植到STM32F429&#xff08;MDK AC6&#xff09; 本章节将为大家介绍ThreadX GUIX的MDK AC6方式移植和设计框架&#xff0c;理论上不建议初学者直接学习&#xff…

introduction(html)(1-26)

去样式 a, abbr, acronym, address, applet, article, aside, audio, b, big, blockquote, body, canvas, caption, center, cite, code, dd, del, details, dfn, div, dl, dt, em, embed, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, h5, h6, header, hgro…

IDL常用命令总结

1.数组的创建 创建索引数组:findgen(num),dindgen(num) 创建一个特定纬度的数组并赋值&#xff1a;replicate(2.0,4,2),创建4列2行值为2.0的数组 2.数组的运算 标量乘&#xff1b; #数组乘,将一个数组的列乘以另一个数组的行&#xff0c;和数学运算列行乘法不同 如&#xff…

10GX:结构化布线的里程碑(转)

10GX&#xff1a;结构化布线的里程碑(转)[more] 10GX&#xff1a;结构化布线的里程碑 Belden IBDN 10GX系统与通常10G布线系统的区别&#xff0c;在于10GX系统不是一个改进或拔高的6类系统&#xff0c;而是围绕着四项激发技术的一个革命性创新设计。10GX系统不仅适用于当今高速…

【目标检测】基于yolov5的红细胞检测和计数(附代码和数据集,Ubuntu系统)

写在前面:“路虽远,行则将至;事虽难,做则必成。只要有愚公移山的志气、滴水穿石的毅力,脚踏实地,埋头苦干,积跬步以至千里,就一定能够把宏伟目标变为美好现实。” 首先感谢兄弟们的订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以…

【目标检测】基于yolov5的钢筋检测和计数(附代码和数据集)

写在前面: 首先感谢兄弟们的订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 文末获取代码和数据集,请看检测效果: 一. 介绍 基于Yolov5的钢筋检测和计数项目是一种使用深度学习算法来自动检测和…