第3课-OC对象原理下
[TOC]
1.对象的本质
在之前的课程中我们讲过了编译器,在探索对象的本质之前,我们需要将源文件通过编译器编译成指定文件
1.1 clang编译器的常见用法
1、将目标文件编译成指定文件 例如下面我们将main.m文件编译成c++文件,首先我们在终端进入main.m所在的目录,然后执行clang -rewrite-objc main.m -o main.cpp命令
此时我们会报如下错误: 解决办法:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
执行完成后,会生成一个main.cpp的c++文件
除了通过clang生成目标文件,我们还可以通过xcode自带的xcrun
生成目标文件。
xcode
安装的时候顺带安装了xcrun
命令,xcrun
命令在clang
的基础上进⾏了⼀些封装,要更好⽤⼀些 xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (模拟器) xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc XXXX.m -o XXX.cpp (⼿机)
例如将ViewController.m
文件编译成vc.cpp
文件
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc ViewController.m -o vc.cpp
通过查看编译之后的cpp文件,我们就能够查看源代码经过编译器编译之后的底层代码
1.2 探索对象的本质
先上结论: 对象在底层的本质就是结构体
1.2.1 简单对象
假设我们有main.m函数代码如下:
// 对象在底层的本质就是结构体
@interface ZBPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@end
@implementation ZBPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
通过上面clang编译指令生成main.cpp文件,打开main.cpp文件我们搜索ZBPerson,可以得到如下关键内容。
struct NSObject_IMPL {
Class isa;
};
#ifndef _REWRITER_typedef_ZBPerson
#define _REWRITER_typedef_ZBPerson
typedef struct objc_object ZBPerson;
typedef struct {} _objc_exc_ZBPerson;
#endif
struct ZBPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
NSString *_nickName;
};
分析上面的代码我们可以发现:
typedef struct objc_object ZBPerson:ZBPerson被编译成了结构体struct objc_object
首先struct objc_object是一个结构体,里面有一个Class类型的变量isa,而Class的本质就是一个结构体指针struct objc_class *
typedef struct objc_class *Class; struct objc_object { Class _Nonnull isa __attribute__((deprecated)); };
接下来我们再看一下struct objc_class是个什么东西,通过查询objc源码我们可以得到struct objc_class是继承自objc_object的结构体,也就是说它本质上是struct objc_object的子结构体
// 通过查询objc源码我们可以得到struct objc_class是继承自objc_object的结构体 struct objc_class : objc_object { // Class ISA; Class superclass; cache_t cache; .... }
ZBPerson类的成员变量编译之后变成了结构体ZBPerson_IMPL里面的变量,该结构体有三个成员变量
struct NSObject_IMPL { Class isa; }; struct ZBPerson_IMPL { struct NSObject_IMPL NSObject_IVARS; NSString *_name; NSString *_nickName; };
- 变量_name,也就是对应ZBPerson类name属性自动生成的_name成员变量
- 变量_nickName,也就是对应ZBPerson类nickName属性自动生成的_nickName成员变量
- 变量NSObject_IVARS是一个结构体,此结构体内部有一个Class类型的变量isa
所以我们可以总结出ZBPerson对象的底层实现图:
再来查看一下内存分配情况 一共占用24字节,isa,_name,_nickName各自占用8字节。 内存布局大概是这样,person 指针指向结构体地址,结构体地址即为首个成员变量的地址。
1.2.2 继承对象
实例1: 如果是 ZBPerson 继承自 NSObject ,ZBStudent 继承自 ZBPerson 类,ZBPerson包含name和nickName两个属性, Student包含age属性,则底层实现结构如下图。
@interface ZBPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@end
@implementation ZBPerson
@end
@interface ZBStudent : ZBPerson
@property (nonatomic, assign) int age;
@end
@implementation ZBStudent
@end
编译之后的对应关系如下
再来看内存分配情况:
首先ZBPerson一共24字节:
- isa指针占据8字节;
- _name变量,字符串指针类型占据8字节
- _nickName成员变量,字符串类型占据8字节
ZBStudent一共8字节:
- _age成员变量,int类型,占据4字节
所以一共分配24+4=28字节。字节对齐后32字节
实例2: 我们将上面的Person对象修改一下
// 对象在底层的本质就是结构体
@interface ZBPerson : NSObject
@property (nonatomic, assign) int height;
@end
@implementation ZBPerson
@end
@interface ZBStudent : ZBPerson
@property (nonatomic, assign) int age;
@end
@implementation ZBStudent
@end
编译之后的对应关系如下:
分析结构体ZBStudent_IMPL字节对齐之后,占用24字节。那么ZBPerson对象占用多少字节呢?
ZBStudent *student = [[ZBStudent alloc] init];
student.height = 180;
student.age = 18;
NSLog(@"student = %p, sizeof = %lu, mallocSize = %lu", student, class_getInstanceSize([student class]), malloc_size((__bridge const void *)(student)));// student = 0x10100f490, sizeof = 16, mallocSize = 16
这里有个疑问?为什么sizeof = 16, mallocSize = 16
,不应该是sizeof = 24, mallocSize = 32
吗
我们查看一下class_getInstanceSize方法的源码:
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
由源码可以得知,class_getInstanceSize底层就是走的成员变量的8字节对齐算法。注意这里是先计算所有成员变量的大小,然后再执行字节对齐算法。
- ZBStudent对象一共包含isa, int height,int age 3个成员变量,总计8+4+4=16字节
- 经过验证data()->ro()->instanceSize确是=16
1.2.3 id对象的本质
typedef struct objc_object *id;
id类型的本质就是结构体指针类型 struct objc_object *
也就是为什么我们OC代码使用id声明变量的时候,不需要添加*
id person = [ZBPerson alloc] ====> struct objc_object *person = [ZBPerson alloc]
1.2.4 对象的setter和getter方法
上面是name和nickName对应的setter和getter方法,里面有很多参数,我们在OC代码中是看不到的,这里就是隐藏参数。
注意这里的getter方法中,计算返回值是通过(char *)self + OBJC_IVAR_$_ZBPerson$_name
这里实际上通过self首地址+成员变量在对象内存中的偏移量OBJC_IVAR_$_ZBPerson$_name来计算它的内存地址