OC源码剖析对象的本质
1. 类的底层实现
先写一个 Person
类:
1 2 3 4 5 6 7 8 9 10 11 12 | @interface Person : NSObject @property (nonatomic, copy) NSString *p_name; @property (nonatomic, assign) int p_age; - ( void )p_instanceMethod1; @end @implementation Person - ( void )p_instanceMethod1{ NSLog(@ "%s" ,__func__); } @end |
使用 clang
编译器, clang -rewrite-objc Person.m -o Person.cpp
将 Person.m
编译成 Person.cpp
文件,部分代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | /// 1: Person 类型的底层结构 struct NSObject_IMPL { Class isa; }; struct Person_IMPL { struct NSObject_IMPL NSObject_IVARS; int _p_age; NSString * _Nonnull _p_name; }; /// 2: p_name 属性的底层结构 // get static NSString * _Nonnull _I_Person_p_name(Person * self, SEL _cmd) { return (*(NSString * _Nonnull *)(( char *)self + OBJC_IVAR_$_Person$_p_name)); } extern "C" __declspec ( dllimport ) void objc_setProperty (id, SEL, long , id, bool , bool ); // set static void _I_Person_setP_name_(Person * self, SEL _cmd, NSString * _Nonnull p_name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__( struct Person, _p_name), (id)p_name, 0, 1); } /// 3: p_age 类型的底层结构 // get static int _I_Person_p_age(Person * self, SEL _cmd) { return (*( int *)(( char *)self + OBJC_IVAR_$_Person$_p_age)); } // set static void _I_Person_setP_age_(Person * self, SEL _cmd, int p_age) { (*( int *)(( char *)self + OBJC_IVAR_$_Person$_p_age)) = p_age; } /// 4: p_instanceMethod1 方法的底层结构 static void _I_Person_p_instanceMethod1(Person * self, SEL _cmd) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_0y_j3bzj65z6vw6hy1chw_4m0000gp_T_Person_f010c0_mi_0,__func__); } |
- NSObject 类被编译成了 NSObject_IMPL 的结构体。
- Person 类被编译成了 Person_IMPL 的结构体。
- Person 类的内部还增加了一个 NSObject_IMPL 的结构体
- 我们知道 Person 继承于 NSObject, 所以它的底层实现中是第一个成员是父类的结构体,就是底层继承的实现方式。用这样的方式拥有父类所有的成员变量。
- NSObject_IMPL 是 NSObject 类的编译后的结构体,它的内部只有一个 Class 类型的 isa 成员变量。我们知道 isa 是 isa_t 类型的,那为什么在这里定义成 Class 类型呢?这是为了更加直观的提现出它代表的是类的信息,所以在获取isa 的方法中,将它强制转换成了Class 类型, 代码如下:
1 2 3 4 5 6 | inline Class objc_object::ISA() { ... return (Class)(isa.bits & ISA_MASK) } |
总结:
1.类的底层实现是结构体。
2.继承是通过把父类的结构体声明为本类结构体的第一个成员变量实现的。
2. isa_t 的类型
联合体: 所有成员可以是不同的类型,但是公用一块内存区域,设置了一个成员变量就会覆盖另一个成员变量的数据。优点是节省空间。
1 2 3 4 5 6 7 8 9 10 11 12 | union isa_t { //联合体 isa_t() { } isa_t( uintptr_t value) : bits(value) { } //提供了cls 和 bits ,两者是互斥关系 Class cls; uintptr_t bits; #if defined(ISA_BITFIELD) struct { ISA_BITFIELD; // defined in isa.h }; #endif }; |
isa 指针占用8字节,64位。64位中不同的位代表不同的含义:
对象与类的 isa 的指向关系
对象.isa -> 类.super -> 父类.super -> 根类.super -> nil
类.isa -> 元类.super -> 父元类.super -> 根元类.super -> 根类.super -> nil
元类.isa = 父元类.isa = 根元类.isa = 根元类
应用:判断对象类型
下面的打印结果是什么:
1 2 3 4 5 6 7 8 | // [NSObject class] = NSObject // object_getClass((id)[NSObject class]) = NSObject meta class // 沿着 NSObject 的继承者链去找根元类 -> 根类 == NSObject meta class 或者 NSObject meta class 的父类的实例 BOOL res1 = [(id)[NSObject class ] isKindOfClass:[NSObject class ]]; BOOL res2 = [(id)[NSObject class ] isMemberOfClass:[NSObject class ]]; BOOL res3 = [(id)[TestObject class ] isKindOfClass:[TestObject class ]]; BOOL res4 = [(id)[TestObject class ] isMemberOfClass:[TestObject class ]]; |
只有第一个是YES, 剩下的都是NO。
isKindOfClass: 判断自己的isa 指向的类是否等于传入的类,不等于的话,找自己的继承连中的父类看有没有等于传入的类,有则YES,没有则NO
isMemberOfClass 判断自己的isa 指向的类是否等于传入的类,等于则YES,不等于则NO
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // 类对象,是否是指定的元类的实例 + ( BOOL )isMemberOfClass:(Class)cls { return object_getClass((id)self) == cls; } // 实例对象,是否是指定的类的实例 - ( BOOL )isMemberOfClass:(Class)cls { return [self class ] == cls; } // 类对象,是否是指定的元类cls的实例,或者是cls继承者链中子类的实例 + ( BOOL )isKindOfClass:(Class)cls { for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->super_class) { if (tcls == cls) return YES; } return NO; } // 实例对象,是否是指定的类的实例,或者是cls继承者链中子类的实例 -( BOOL )isKindOfClass:(Class)cls { for (Class tcls = [self class ]; tcls; tcls = tcls->super_class) { if (tcls == cls) return YES; } return NO; } |
青山不改,绿水长流,感谢每位佳人支持!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了