第四篇:isa指针底层分析
首先我们来看一段代码,通过这三种模式都可以打印HPWPerson的类对象,打印后我们发现三种方式打印的类对象地址都一样,
void lgTestClassNum(void){
Class class1 = [HPWPerson class];
Class class2 = [HPWPerson alloc].class;
Class class3 = object_getClass([HPWPerson alloc]);
NSLog(@"\n%p-\n%p-\n%p",class1,class2,class3);
}
2022-04-22 18:16:13.838751+0800 002-isa分析[49376:528799]
0x1000082f0-
0x1000082f0-
0x1000082f0
接着我们打开objc的源码,通过搜索struct objc_class发现了typedef struct objc_class *Class,发现类对象其实就是一个结构体,接着我们再去源码搜索下objc_class,会发现什么呢?
WechatIMG1918.jpeg下面这个是objc_class继承objc_object这个,同时我们看到了ISA指针,说明类对象里也有isa指针,类对象的isa指针是指向元类对象的。那我们怎么去证明呢?且元类又是什么东西呢?
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA; // 8
Class superclass;// 8
cache_t cache; // 16 // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags //
带着上面的问题,我们继续去探索,我们打印下对象,然后通过对象地址找到对应的类对象,其中用到了ISA_BIT这个掩码的值,这个掩码我们需要选对,因为我们用的电脑是M1的所以需要选择对应的arm64架构的,所以ISA_BIT对应的掩码值也就是0x007ffffffffffff8ULL.
HPWPerson *p = [HPWPerson alloc];
NSLog(@"%@",p);
2022-04-22 18:36:58.073715+0800 002-isa分析[50359:546896] <HPWPerson: 0x10121d3d0>
(lldb) x/4gx p
0x10121d3d0: 0x01000001000082f1 0x0000000000000000
0x10121d3e0: 0x0000000000000000 0x0000000000000000
(lldb)
# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# define ISA_MASK 0x007ffffffffffff8ULL
# define ISA_MAGIC_MASK 0x0000000000000001ULL
# define ISA_MAGIC_VALUE 0x0000000000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 0
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t weakly_referenced : 1; \
uintptr_t shiftcls_and_sig : 52; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
接着我们把得到掩码和打印的HPWPerson对象的地址进行一个与操作,等到一个内存地址.然后我们再po下这个地址,就会得到一个HPWPerson类对象,接着我们再打印类对象的x/4gx的地址,0x00000001000082c8是类对象的isa指针地址,然后我们把类对象的isa指针与掩码再与一下,接着po下得到的地址就会发现打印的还是HPWPerson。
2022-04-22 18:36:58.073715+0800 002-isa分析[50359:546896] <HPWPerson: 0x10121d3d0>
(lldb) x/4gx p
0x10121d3d0: 0x01000001000082f1 0x0000000000000000
0x10121d3e0: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x01000001000082f1 & 0x007ffffffffffff8ULL
(unsigned long long) $1 = 0x00000001000082f0
(lldb) po 0x00000001000082f0
HPWPerson
(lldb) x/4gx 0x00000001000082f0
0x1000082f0: 0x00000001000082c8 0x00000001e72881c8
0x100008300: 0x0007000100733280 0x0002802900000000
(lldb) p/x 0x00000001000082c8 & 0x007ffffffffffff8ULL
(unsigned long long) $4 = 0x00000001000082c8
(lldb) po 0x00000001000082c8
HPWPerson
通过上面的打印,我们发现HPWPerson的两个地址是不一样的,但是我们之前是知道类对象只有一个地址,那到底哪个是对的呢,我继续去探究下,我们在最开始打印的地址为0x1000082f0,所以我们第一次得到的HPWPerson类的地址是一样的。那0x00000001000082c8对应的HPWPerson是什么呢,其实他是一个元类。那么发现HPWPerson元类和HPWPerson类对象的名字都是一样的。总结是:实例对象的isa-->类对象isa-->元类对象,其实元类对象也有一个isa指针,上面我们得到元类的地址,那我们继续打印,这里得到一个NSObject
(lldb) x/4gx 0x00000001000082c8
0x1000082c8: 0x00000001e72881a0 0x00000001e72881a0
0x1000082d8: 0x0003000100733900 0x0001e03500000000
(lldb) p/x 0x00000001e72881a0 & 0x007ffffffffffff8ULL
(unsigned long long) $6 = 0x00000001e72881a0
(lldb) po 0x00000001e72881a0
NSObject
接着我们来打印下NSObject的类方法,也就是NSObject.class,最后得到的0x00000001e72881a0就是根元类,到了这里我们就知道了:
实例对象的isa-->类对象isa-->元类对象isa-->根元类
根类的NSObject isa -- >根元类(NSObject 元类)
(lldb) p/x NSObject.class
(Class) $8 = 0x00000001e72881c8 NSObject
(lldb) x/4gx 0x00000001e72881c8
0x1e72881c8: 0x00000001e72881a0 0x0000000000000000
0x1e72881d8: 0x000100010070fa60 0x0002801000000000
(lldb) p/x 0x00000001e72881a0 & 0x007ffffffffffff8ULL
(unsigned long long) $9 = 0x00000001e72881a0
通过上面我们有个疑问?根元类的isa指针指向什么呢,我们经过下面的输出得到了内存地址还是相同,都是0x00000001e72881a0,所以我们得出的结论是:
实例对象的isa-->类对象isa-->元类对象isa-->根元类isa -->根元类自己 ,这样就实现了一个指向的闭环。
(lldb) x/4gx 0x00000001e72881a0
0x1e72881a0: 0x00000001e72881a0 0x00000001e72881c8
0x1e72881b0: 0x0007000100733b90 0x0001e03400000000
(lldb) p/x 0x00000001e72881a0 & 0x007ffffffffffff8ULL
(unsigned long long) $10 = 0x00000001e72881a0
(lldb) po 0x00000001e72881a0
接着我们用代码进行分析一下:
// NSObject实例对象
NSObject *object1 = [NSObject alloc];
// NSObject类对象
Class class = object_getClass(object1);
// NSObject元类(根元类)
Class metaClass = object_getClass(class);
NSLog(@"NSObject实例对象:%p",object1);
NSLog(@"NSObject类对象:%p",class);
NSLog(@"NSObject元类(根元类):%p",metaClass);
// HPWPerson -- 元类的父类就是父类的元类
Class pMetaClass = objc_getMetaClass("HPWPerson");
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"%@ - %p",pMetaClass,pMetaClass);
NSLog(@"%@ - %p",psuperClass,psuperClass);
// HPWTeacher继承自HPWPerson
// HPWTeacher元类的父类 就是 HPWPerson(HPWPerson的元类)
Class tMetaClass = objc_getMetaClass("HPWTeacher");
Class tsuperClass = class_getSuperclass(tMetaClass);
NSLog(@"%@ - %p",tsuperClass,tsuperClass);
// NSObject的父类
Class nsuperClass = class_getSuperclass(NSObject.class);
NSLog(@"%@ - %p",nsuperClass,nsuperClass);
// 根元类的父类 -- NSObject
Class rnsuperClass = class_getSuperclass(metaClass);
NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);
运行上面的语句后打印出如下:
2022-04-22 22:39:16.162213+0800 002-isa分析[53384:683254] NSObject实例对象:0x101011f10
2022-04-22 22:39:16.162244+0800 002-isa分析[53384:683254] NSObject类对象:0x1e72881c8
2022-04-22 22:39:16.162263+0800 002-isa分析[53384:683254] NSObject元类(根元类):0x1e72881a0
2022-04-22 22:39:16.162326+0800 002-isa分析[53384:683254] HPWPerson - 0x1000082c8
2022-04-22 22:39:16.162344+0800 002-isa分析[53384:683254] NSObject - 0x1e72881a0
2022-04-22 22:39:16.162356+0800 002-isa分析[53384:683254] HPWPerson - 0x1000082c8
2022-04-22 22:39:16.162369+0800 002-isa分析[53384:683254] (null) - 0x0
2022-04-22 22:39:16.162417+0800 002-isa分析[53384:683254] NSObject - 0x1e72881c8
通过上面我们知道,NSObject的父类是nil,根元类的父类就是NSObject,最后我们用两张图进行总结下,方便大家进行理解。
WechatIMG1923.jpeg WechatIMG1924.jpeg
上面我们知道,NSObject是万类之主,isa指针指向都是可以把子类通过3步找到根元类。
知识点补充:内存平移
通过下面代码打印:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello World!");
HPWPerson *p = [HPWPerson alloc];
}
return 0;
}
WechatIMG1925.jpeg
上面的0x100008198是类对象的首地址
我们再看下源码:
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA; // 8
Class superclass;// 8
cache_t cache; // 16 // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags //
Class getSuperclass() const {
#if __has_feature(ptrauth_calls)
# if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
if (superclass == Nil)
return Nil;
#if SUPERCLASS_SIGNING_TREAT_UNSIGNED_AS_NIL
void *stripped = ptrauth_strip((void *)superclass, ISA_SIGNING_KEY);
if ((void *)superclass == stripped) {
void *resigned = ptrauth_sign_unauthenticated(stripped, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS));
if ((void *)superclass != resigned)
return Nil;
}
#endif
我们来分析下 class_data_bits_t bits ,分析这个我们用到了内存平移的概念。进入到class_data_bits_t里我们看到了有method_t这个方法,我们关系的是 SEL name;和 MethodListIMP imp;,同时还有个isSmall和big这个概念,这个就是大小端的概念,大小端的意思我们用一张图来表示,大小端是指在不同的处理器上不同,我们用的是M1电脑,所以是个小端。
WechatIMG1926.jpeg
struct method_t {
static const uint32_t smallMethodListFlag = 0x80000000;
method_t(const method_t &other) = delete;
// The representation of a "big" method. This is the traditional
// representation of three pointers storing the selector, types
// and implementation.
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
private:
bool isSmall() const {
return ((uintptr_t)this & 1) == 1;
}
// The representation of a "small" method. This stores three
// relative offsets to the name, types, and implementation.
struct small {
// The name field either refers to a selector (in the shared
// cache) or a selref (everywhere else).
RelativePointer<const void *> name;
RelativePointer<const char *> types;
RelativePointer<IMP, /*isNullable*/false> imp;
bool inSharedCache() const {
return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
objc::inSharedCache((uintptr_t)this));
}
};
然后我们通过P命令去打印,会得到这个我们关心的 SEL方法的 name,也就得到了结论(method_t里存放的是HPWPerson里的所有的方法)。类方法不存在method_t里,实例方法存在method_t里。其实类方法是存放在元类里的。属性存放在property_t这个结构体里,打印其属性后其有name显示属性名字,name = ‘age’,还有一个attributes = “jihih”用来进行描述的。