第四篇: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”用来进行描述的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容