OC 对象的本质
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface LGPerson : NSObject
@property (nonatomic, strong) NSString *KCName;
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
这里我们在 main.m
文件里面定义一个 LGPerson
类,然后打开终端,cd
到 main.m
所在文件目录,然后输入 clang -rewrite-objc main.m -o main.cpp
命令,最后会看到生成了一个 main.cpp
文件。


打开
main.cpp
文件我们可以看到,只有几行代码的 main.m
文件生成 c++ 文件后,会有很多行代码,这里还只是刚拖动了一旦就已经一万多行代码了。我们直接全局搜索找到 LGPerson

这里可以看到 LGPerson
类,及 KCName
属性的 getter
方法跟 setter
方法。这里可以看到其实对象本质就是结构体。


这里除了
KCName
属性之外还有一个 NSObject_IVARS
属性,这个其实是 isa
。继承于结构体 NSObject_IMPL
,这里是伪继承。全局搜索 NSObject_IMPL
,可以看到确实是 isa
。

我们都知道在 OC
层面,对象最终都是继承于 NSObjec
,这里却是 objc_object
,这是因为在真正的下层,objc
的实现就是 objc_object
。

这里还可以看到
Class
本质是 objc_class *
类型,是一个结构体指针,Class
只是一个别名。id
是 objc_object *
类型,这也是我们平时用 id
声明对象不用加 *
号的原因。
static NSString * _I_LGPerson_KCName(LGPerson * self, SEL _cmd) {
return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_KCName));
}
static void _I_LGPerson_setKCName_(LGPerson * self, SEL _cmd, NSString *KCName) {
(*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_KCName)) = KCName;
}
我们再看一下 getter
方法,会发现有 LGPerson * self, SEL _cmd
这两个参数,但是我们平时写 getter
方法的时候是没有的,这其实是 oc
方法的隐藏参数。这里为什么能通过 return
返回 KCName
的值呢,其实就是拿到 person
的首地址加上IVAR的地址偏移量才能获取到 KCName 的地址,通过地址来获取 KCName
的值。setter
方法也是同样的方式赋值。
补充:Clang是一个C语言、C++、Objective-C语言的轻量级编译器。源代码发布于BSD协议下。 Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。
Clang是一个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器
2013年4月,Clang已经全面支持C++11标准,并开始实现C++1y特性(也就是C++14,这是 C++的下一个小更新版本)。Clang将支持其普通lambda表达式、返回类型的简化处理以及更 好的处理constexpr关键字。 [2]
Clang是一个C++编写、基于LLVM、发布于LLVM BSD许可证下的C/C++/Objective-C/ Objective-C++编译器。它与GNU C语言规范几乎完全兼容(当然,也有部分不兼容的内容, 包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,比如C函数重载 (通过attribute((overloadable))来修饰函数),其目标(之一)就是超越GCC。
联合体位域
-
案例 1
LGCar1
这里定义一个代表汽车方向的结构体 LGCar1
,分别有 4 个方向,总共需要 4 个字节,也就是 32 位。但是其实每个方向用 1 跟 0 就可以表示,所以只需要 4 位就行,但是一个字节是 8 位,所以需要一个字节的空间。这里会有 3 个字节的浪费。那么怎么优化呢,这里有一种位域的方式。
- 案例 2
LGCar2
这里可以看到,采用位域的方式只占用了一个 1 字节(其实只用到了 4 位 0000 1111)。大大的优化了内存。
-
案例 3
联合体
通过打印我们可以发现相比于 teacher1
, teacher2
每次只能有一个属性有值,这是因为联合体的互斥特性。
union,中文名“联合体、共用体”,在某种程度上类似结构体struct的一种数据结构,共用体(union)和结构体(struct)同样可以包含很多种数据类型和变量。
不过区别也挺明显:
结构体(struct)中所有变量是“共存”的——优点是“有容乃大”,全面;缺点是struct内存空间的分配是粗放的,不管用不用,全分配。
而联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”;但优点是内存使用更为精细灵活,也节省了内存空间。
isa

我们在iOS 对象探究一中讲过,当走到这里的时候就会把堆内存申请的结构体指针跟 Class
类进行绑定。那么 isa
是怎么实现的呢,这里我们进到代码看一下。

在 objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
方法中我们可以看到 isa_t
, 这里我们再来看一下 isa_t
。

这里可以看到 isa_t
本质是一个联合体。这里可以看到 isa_t
的构造方法,跟属性 bits
, cls
。
我们都知道指针类型是 8 字节,64 位系统下就是 8 * 8 一共 64 个字节。如果 64 位都只是存储一个指针,就会存在空间的浪费,而且基本上每个类基本上都有 isa
,那么这里是否可以优化呢?所以苹果会把跟类息息相关的存放在 64 位里面,例如引用计数, 是否正在释放, weak, 关联对象, 析构函数等信息。所以这里有了一个概念 nonPointerIsa
。具体是不是这么存的呢,我们进到 ISA_BITFIELD
里面来看一下。

- nonpointer 表示是否对 isa 指针开启指针优化 0:纯isa 指针,1:不止是类对象地址,isa 中包含了类信息, 对象的引用计数等
- has_assoc 关联对象标志位,0:没有,1:存在
- has_cxx_dtor 该对象是否有 C++或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。
- shiftcls 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存类指针。
- magic:用于调试器判断当前对象是真的对象还是没有初始化的空间。
- weakly_referenced 表示对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。
- deallocating:标志对象是否正在释放内存
- unused 是否使用散列表
- has_sidetable_rc:当对象引用计数大于 2 的 19 次方(2^19) 时,则需要借用该变量存储进位
- extra_rc: 表示对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 2 的 19 次方, 则需要使用到下面的 has_sidetable_rc。
这里我们可以看到 x86 64
位下 联合体的存储信息,及相关位域下存储的值代表的含义,下面也附上了注释。
网友评论