OC底层原理05 - isa与类关联的原理

在探讨这个问题前,我们首先要弄清楚对象的本质什么
编译器clang
clang是一个由Apple主导编写,基于LLVMC/C++/OC的编译器
操作指令

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

//2、将 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
//3、模拟器文件编译
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 

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

探索对象本质是什么

  1. main中自定义一个HLPerson类,有一个属性name
@interface HLPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation HLPerson
@end
  1. 打开终端,cdmain.m的文件夹,输入clang指令:clang -rewrite-objc main.m -o main.cppmain.m编译成 main.cpp
  2. 打开编译好的main.cpp,搜索HLPerson,找到HLPerson的定义
    image.png
    我们发现HLPerson在底层被编译成struct结构体,属性name还生成了相应的get方法_I_HLPerson_name以及set方法_I_HLPerson_setName_
    在结构体中,我们看到第一个属性为struct NSObject_IMPL NSObject_IVARS,其实它是继承于NSObject,这种方式属于伪继承,伪继承是直接将结构体定义为HLPerson中的第一个属性,意味着HLPerson拥有该结构体中的所有成员变量
    然后我们搜索NSObject_IMPL
struct NSObject_IMPL {
    Class isa;
};

发现NSObject_IMPL中的第一个属性其实就是isa

总结

  • OC对象的本质就是结构体
  • 每个对象都有一个isa,继承于NSObject

objc_setProperty 源码探索

在上面我们看到除了HLPerson底层定义外,还有其属性对应的getset方法,其中set方法其实是依赖于runtimeobjc_setProperty所实现的
接下来我们来看看objc_setProperty的底层原理

  • objc4-781中全局搜索objc_setProperty,找到objc_setProperty的源码实现
    image.png
  • 跳转至reallySetProperty,其方法的原理就是新值retain,旧值release
    image.png

总结

所有外层属性的set方法。都会来到objc_setProperty方法,调用了reallySetProperty实现set功能。

image.png

这是一种适配器设计模式(即将底层接口适配为客户端需要的接口),对外提供一个接口,供上层的set方法使用,对内调用底层的set方法,使其相互不受影响,即无论上层怎么变,下层都是不变的,或者下层的变化也无法影响上层,主要是达到上下层接口隔离的目的

构造数据类型

构造数据类型的方式有以下两种:

  • 结构体struct
  • 联合体union,也称为共用体
    结构体 struct
    结构体是指把不同的数据组合成一个整体,其变量是共存的,变量不管是否使用,都会分配内存。
  • 缺点:所有属性都分配内存,比较浪费内存,假设有4个int成员,一共分配了16字节的内存,但是在使用时,你只使用了4字节,剩余的12字节依旧会分配,这就属于内存的浪费
  • 优点:存储容量较大包容性强,且成员之间不会相互影响(占用不同内存)
    联合体 union
    联合体也是由不同的数据类型组成,但其变量是互斥的,所有的成员共占一段内存。而且共用体采用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会将原来成员的值覆盖掉
  • 缺点:每个变量是互斥的,且包容性差
  • 优点:所有成员共用一段内存,使内存的使用更为精细灵活,同时也节省了内存空间

两者的区别

  • 内存占用情况
    • 结构体的各个成员会占用不同的内存,互相之间没有影响
    • 联合体的所有成员占用同一段内存,修改一个成员会影响其余所有成员
  • 内存分配大小
    • 结构体内存 >= 所有成员占用的内存总和(成员之间可能会有缝隙)
    • 共用体占用的内存等于 最大的成员占用的内存

isa的类型 isa_t

查看objc4-781源码,看到以下isa指针的类型isa_t的定义,从定义中可以看出是通过联合体(union)定义的。

image.png
isa_t的定义中可以看出:

  • 提供了两个成员,clsbits,由联合体的定义所知,这两个成员是互斥
  • 提供了一个结构体定义的位域,用于存储类信息及其他信息,结构体的成员ISA_BITFIELD,这是一个宏定义,有两个版本__arm64__(对应iOS移动端)和__x86_64__(对应macOS),以下是它们的一些宏定义,如下图所示
    image.png
  • nonpointer:表示是否对isa指针开启指针优化0:纯isa指针;1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等
  • has_assoc:关联对象标志位。0:没有;1:存在
  • has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象
  • shiftcls:存储类指针的值。开启指针优化的情况下,在arm64架构中有33位⽤来存储类指针;在x86_64架构中有44位⽤来存储类指针
  • magic:⽤于调试器判断当前对象是真的对象还是没有初始化的空间
  • weakly_referenced:志对象是否被指向或者曾经指向⼀个 ARC 的弱变量,没有弱引⽤的对象可以更快释放。
  • deallocating:标志对象是否正在释放内存
  • has_sidetable_rc:当对象引⽤技术⼤于 10 时,则需要借⽤该变量存储进位
  • extra_rc:当表示该对象的引⽤计数值,实际上是引⽤计数值减 1。例如,如果对象的引⽤计数为 10,那么 extra_rc 为 9。如果引⽤计数⼤于 10,则需要使⽤到下⾯的has_sidetable_rc

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

image.png

原理探索

  • 通过alloc-->_objc_rootAlloc-->callAlloc-->_objc_rootAllocWithZone-->_class_createInstanceFromZone方法路径,查找到initInstanceIsa
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}
  • 跳转至initIsa的源码,即isa指针的初始化
    image.png

验证isa指针位域

  • 首先通过main中的HLPerson断点 --> initInstanceIsa --> initIsa (由于项目是macOS环境下,所以使用的是x86_64),查看如果当前clsHLPerson,往下运行代码至准备赋值bits
    image.png
  • 执行lldb命令:p newisa,打印newisa的详细信息
    image.png
  • 继续往下执行,走完newisa.bits = ISA_MAGIC_VALUE;这一行,表示为isabits成员赋值完成,重新执行lldb命令p newisa,打应结果如下
    image.png

    通过赋值前后对比,我们发现newisa的值产生了变化。nonpointer变为了1magic变为了5959转换为2进制111011。根据规则,ISA_MAGIC_VALUE的第0位应为152-47位应为111011,其它位应为0。查看ISA_MAGIC_VALUE定义,该值等于0x001d800000000001ULL,将其转换为2进制,如图
    image.png
    完美印证~

isa 与 类 的关联

clsisa关联原理就是isa指针中的shiftcls位域中存储了类信息,其中initInstanceIsa的过程是将calloc指针和当前的类cls关联起来,可以通过以下几种方式来验证:

  • 【方式一】通过initIsa方法中的newisa.shiftcls = (uintptr_t)cls >> 3;验证
  • 【方式二】通过isa指针地址和ISA_MSAK的值进行&运算来验证
  • 【方式三】通过runtime的方法object_getClass验证
  • 【方式四】通过位运算验证

方式一:initIsa

  • 运行至newisa.shiftcls = (uintptr_t)cls >> 3;,其中shiftcls用于存储当前类的值信息
  • 执行lldb命令p (uintptr_t)cls,结果为(uintptr_t) $10 = 4295000648,再将结果右移三位,有以下两种方式(任选其一),将得到536875081存储到newisashiftcls
    • p 4295000648 >> 3
    • 通过上一步的结果$10,执行lldb命令p $10 >> 3
      image.png
  • 继续执行代码,将newisa.shiftcls赋值完成,让后打印newisa
    image.png
  • 查看结果得知,cls由默认值变成了HLPersonshiftcls也由0变成了536875081,此时isacls关联已经完成

方式二:isa & ISA_MSAK

  • initInstanceIsa进行完毕,继续执行,回到_class_createInstanceFromZone方法,此时clsisa已经关联完成,执行po obj
    image.png
  • 执行x/4gx obj,得到isa指针的地址0x001d80010000824d
  • isa指针地址 & ISA_MASK(处于macOS环境,使用x86_64中的宏定义),即po 0x001d80010000824d & 0x00007ffffffffff8ULL,得出HLPerson
    image.png

方式三:object_getClass

  • main中导入#import <objc/runtime.h>
  • 通过runtimeapi,即object_getClass函数获取类信息
object_getClass(<#id  _Nullable obj#>)
  • 查看object_getClass函数源码的实现
    image.png
  • 跳转至object_getClass源码
    image.png
  • 跳转至getIsa源码
    image.png
  • 可以看到,如果不是一个纯isa指针返回的是ISA()的返回值,跳转至ISA源码
    image.png
  • else流程中,拿到isabits,再& ISA_MASK,这与方式二中的原理是一致的
  • 至此,也可证明isacls关联完成

方式四:位运算

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

推荐阅读更多精彩内容