iOS底层探索之对象原理(三)
前言
iOS底层探索之对象原理(二)我们了解到 isa
是一个联合体位域,ISA_BITFIELD
存储了类的一些信息,本文我们将继续探索isa
是如何关联对象与类的呢?以及isa
的走位分析
alloc流程
iOS底层探索之对象原理(一)我们探索了《alloc底层原理》知道给一个对象发送alloc
消息,在底层源码会来到_objc_rootAlloc
,然后进入callAlloc
。但其实这里漏掉了一个过程,就是objc_alloc
dyld
在加载Mach-O
二进制文件的时候,会进行符号绑定的操作,也就是说sel_alloc
绑定到了 objc_alloc
上面去了。 这一步其实没有真正开源。
不过我们在 libobjc
源码中全局搜索 objc_alloc
即可发现如下代码:
static void
fixupMessageRef(message_ref_t *msg)
{
msg->sel = sel_registerName((const char *)msg->sel);
if (msg->imp == &objc_msgSend_fixup) {
if (msg->sel == SEL_alloc) {
msg->imp = (IMP)&objc_alloc;
}
/* 省略下面的代码 */
}
}
而 fixupMessageRef
的调用则是在 _read_images
也就是 dyld
读取我们的镜像文件的时候,什么意思呢,这里我们再看 _read_images
的源码:
#if SUPPORT_FIXUP
// Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
if (PrintVtables) {
_objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
"call sites in %s", count, hi->fname());
}
for (i = 0; i < count; i++) {
fixupMessageRef(refs+i);
}
}
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
从代码不难看出,我们在读取镜像文件的时候,判断如果需要 fixup
,如果需要的话,我们就调用 fixupMessageRef
,然后在 fixupMessageRef
内部,我们判断当前消息的 SEL
是否是 SEL_alloc
,如果是的话就替换其 IMP
为 objc_alloc
。这一流程只会走一次,也就是说 objc_alloc
只会走一次。
isa的走位分析
我们都知道对象可以创建多个,但是类是否可以创建多个呢?
答案很简单,一个。那么如果来验证呢?
接着我们开始如下验证:
//MARK: - 分析类对象内存存在个数
void lgTestClassNum(){
Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
Class class4 = [LGPerson alloc].class;
NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}
// 打印输出如下:
0x100002108-
0x100002108-
0x100002108-
0x100002108
所以我们就知道了类在内存中只会存在一份。
(lldb) x/4gx LGTeacher.class
0x100001420: 0x001d8001000013f9 0x0000000100b38140
0x100001430: 0x00000001003db270 0x0000000000000000
(lldb) po 0x001d8001000013f9
17082823967917874
(lldb) p 0x001d8001000013f9
(long) $2 = 8303516107936761
(lldb) po 0x100001420
LGTeacher
我们通过上面的打印,就发现 类的内存结构里面的第一个结构打印出来还是 LGTeacher,那么是不是就意味着 对象->类->类 这样的死循环呢?这里的第二个类其实是 元类。是由系统帮我们创建的。这个元类也无法被我们实例化。
也就是下面的这种关系: 对象 ——> 类对象 ——> 元类
那么好奇心来了,元类的 isa
又是什么呢,在Xcode测试有以下结果:
isa走位 & 继承关系结论
- isa:对象 ——> 类对象 ——> 元类 ——> 根元类 ——> 根元类自己
- superclass:NSObject 父类是nil 、根元类的父类是 NSObject
对象的本质
在我们认知里面,OC 对象的本质就是一个结构体,这个结论在 libobjc
源码的 objc-private.h 源文件中可以得到证实。
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
/* 省略其他的内容 */
}
而对于对象所属的类来说,我们也可以在 objc-runtime-new.h
源文件中找到
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
/* 省略其他的内容 */
}
也就是说 objc_class
内存中第一个位置是 isa
,第二个位置是 superclass
,我们可以通过 LLDB 打印内存地址来验证:
很简单,我们只需要使用 clang 的一个命令来编译我们的 OC 源文件即可。
// 编译底层源码
clang -rewrite-objc main.m -o main.cpp
// 存在UIkit系统其他动态库引用问题 则使用:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m
// xcrun xcode 命令
xcrun -sdk iphonesimulator clang -rewrite-objc ViewController.m
xcrun -sdk iphoneos clang -rewrite-objc ViewController.m
clang -rewrite-objc main.m -o main.cpp
这行命令会把我们的 main.m 文件编译成 C++ 格式,输出为 main.cpp。
通过观察,我们可以看到有一个
NSObject_IVARS
这个其实是 NSObject
里面的成员变量。
- 成员变量 (不会生成 getter 和 setter)
- 实例变量是一种特殊的成员变量,是由类声明而来
- 属性 (LLVM 会帮我们自动生成 getter 和 setter )
附上isa初始化图