第四篇:isa指针底层分析

2022-04-22  本文已影响0人  坚持才会看到希望

首先我们来看一段代码,通过这三种模式都可以打印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”用来进行描述的。

上一篇下一篇

猜你喜欢

热点阅读