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_object
的isa.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_object
struct结构得知,对应的内存内容应该为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-isa指针;objc实例对象类元类根类根元类的主要内容,如果未能解决你的问题,请参考以下文章
iOS经典面试题之深入解析objc对象的内存空间数据结构以及isa指针的理解