iOS 底层探索:isa与类关联的原理

iOS 底层探索: 学习大纲 OC篇

前言

  • 这篇主要内容探索 类与isa是如何关联的。
  • 在之前iOS底层探索 alloc&init这篇文章中,我们知道了_class_createInstanceFromZone方法的关键三步:
    a. 获取实例的内存空间大小: cls->instanceSize()
    b. 根据内存大小,分配内存空间,让实例指向内存开始地址: calloc
    c. 关联isa,实例的isa指向类: obj->initInstanceIsa(cls, hasCxxDtor), 结合位运算、联合体、位域和结构体的内存对齐的知识,我们探索oc对象的本质从关联isa,实例的isa指向类开始。

准备工作

先大概了解一个编译器:clang :

  • clang是一个由Apple主导编写,基于LLVM的C/C++/OC的编译器,主要是用于底层编译,将一些文件``输出成c++文件,例如main.m 输出成main.cpp;
  • 其目的是为了更好的观察底层的一些结构 及 实现的逻辑,方便理解底层原理。

一 、查看类的编译源码

1 .打开终端,cd到文件目录下,利用clang将main.m编译成 main.cpp .

//1、将 main.m 编译成 main.cpp
clang -rewrite-objc main.m -o main.cpp

其他举例:

//1、将 ViewController.m 编译成  ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m

//以下两种方式是通过指定架构模式的命令行,使用xcode工具 xcrun
//2、模拟器文件编译
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 

//3、真机文件编译
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp 

2 .打开 main.cpp, 找到HJPerson,发现HJPerson在底层会被编译成 struct 结构体


点击NSObject_IMPL 跳转可以得到 isa

struct NSObject_IMPL {
    Class isa;
};

先看这里:

struct HJPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *_name;
};

这里的知识点:

  • HJPerson中的第一个属性 NSObject_IVARS 等效于 NSObject中的isa, 每个类中都有默认属性isa

二 、翻开objc源码 可以看到 NSObjectisa 也是class类型

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

现在我们通过clang 看到类被编译成结构体之后,找到isa ,那么isa是如何关联类的信息的呢?
我们可以在 _class_createInstanceFromZone 的核心方法的第三步:关联isa,实例的isa指向类: obj->initInstanceIsa(cls, hasCxxDtor)中找到答案,这里做了一系列操作isa和类信息的操作,我们再去查看initInstanceIsa 内部源码

inline void
objc_object::initProtocolIsa(Class cls)
{
    return initClassIsa(cls);
}

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

//排队
inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
//断言
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
//关键代码: 初始化isa
        isa = isa_t((uintptr_t)cls);
    } else {
//禁用非指针Isa
        ASSERT(!DisableNonpointerIsa);
//实例需要原始Isa
        ASSERT(!cls->instancesRequireRawIsa());
//关键代码: 初始化isa
        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);

        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic是ISA_MAGIC_VALUE的一部分
         // isa.nonpointer是ISA_MAGIC_VALUE的一部分
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        isa = newisa;
    }
}

重要代码:isa_t: 指针的初始化

  • isa = isa_t((uintptr_t)cls);
  • isa_t newisa(0);

关键单词:nonpointer 非指针

三 、继续探索isa_t是怎么做的,我们再次进入源码跳转到isa_t内部如下:


union isa_t { //联合体 isa_t
//两个初始化方法
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
 //一个Class
    Class cls;
//一个 bits 。 uintptr_t :在64位的机器上,intptr_t和uintptr_t分别是long int、unsigned long int的别名
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
// ISA_BITFIELD 这是一个宏 
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

从union isa_t定义可以看出:

  • 提供了两个成员,cls 和 bits,由联合体的定义所知,这两个成员是互斥的,也就意味着,当初始化isa指针时,有两种初始化方式

    1. 通过cls初始化,bits无默认值

    2. 通过bits初始化,cls有默认值

  • 还提供了一个结构体定义的位域,用于存储类信息及其他信息,结构体的成员ISA_BITFIELD,这是一个宏定义,有两个版本 arm64(对应ios 移动端) 和 x86_64(对应macOS),以下是它们的一些宏定义,如下图所示:

    ISA_BITFIELD

注:

  • nonpointer有两个值,表示自定义的类等,占1位
    0:纯isa指针
    1:不只是类对象地址,isa中包含了类信息、对象的引用计数等

  • has_assoc表示关联对象标志位,占1位
    0:没有关联对象
    1:存在关联对象

  • has_cxx_dtor 表示该对象是否有C++/OC的析构器(类似于dealloc),占1位如果有析构函数,则需要做析构逻辑
    如果没有,则可以更快的释放对象, 析构函数 类似于 oc层面的 dealloc;

  • shiftclx表示存储类的指针的值(类的地址), 即类信息arm64中占 33位,开启指针优化的情况下,在arm64架构中有33位用来存储类指针x86_64中占 44位;

  • magic 用于调试器判断当前对象是真的对象 还是 没有初始化的空间,占6位;

  • weakly_refrenced是 指对象是否被指向 或者 曾经指向一个ARC的弱变量, 没有弱引用的对象可以更快释放deallocating 标志对象是是否正在释放内存;

  • has_sidetable_rc表示 当对象引用计数大于10时,则需要借用该变量存储进位;

  • extra_rc(额外的引用计数) --- 表示该对象的引用计数值,实际上是引用计数值减1, 如果对象的引用计数为10,那么extra_rc为9;

针对两种不同平台,其isa的存储情况如图所示


结构体的成员ISA_BITFIELD排列情况

我们lldb 调试 可以看到经过一系列赋值 将HJPerson类信息存进了shiftcls中


注:
为什么在shiftcls赋值时(newisa.shiftcls = (uintptr_t)cls >> 3)需要类型强转?

因为内存的存储不能存储字符串,机器码只能识别 0 、1这两种数字,所以需要将其转换为uintptr_t数据类型,这样shiftcls中存储的类信息才能被机器码理解, 其中uintptr_t是long。

至此,我们得出结论:在isa初始化obj->initInstanceIsa(cls, hasCxxDtor)的时候,通过isa_t联合体,在位域运算中,将类信息cls存进了存储类的指针的值shiftclx , 最后isa = newisa;isa中既有HJPerson的指针,又有HJPerson的信息。就这样isa与类关联到一起了。

四 、拓展 验证 isa 与 类 的关联

简单点 我们在x86_64中通过isa & ISA_MSAK验证

image.png

流程:

  • 1.在_class_createInstanceFromZone方法,此时cls 与isa已经关联完成,执行po objc

  • 2.执行x/4gx obj,得到isa指针的地址0x001d8001000021fd

  • 3.将isa指针地址 & ISA_MASK(处于macOS,使用x86_64中的宏定义),即 po 0x001d8001000021fd & 0x00007ffffffffff8,得出HJPerson.x86_64中,ISA_MASK 宏定义的值为0x00007ffffffffff8ULL

image.png

good~~~加油!

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