和谐学习!不急不躁!!我是你们的老朋友小青龙~
iOS之alloc底层实现分析
iOS之alloc底层实现分析02(未完待续)
到目前为止,前两篇文章都在说对象,那么对象到底是一个什么东西呢?我们又该如何去了解它,去跟它做更深一步
的探讨呢?
接下来我给大家推荐一个由苹果主导编写的轻量级编译器:Clang
。
我们都知道,OC是C和C++的一个超集。而Clang
可以帮我们还原OC底层的真正实现
,让我们了解到它在底层是什么样的一个情况。
下面,我们开始切入正题:
- 新建工程,在main函数里实现一个Student类,写完大概是这个样子:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
@interface Student : NSObject
@property (nonatomic,strong) NSString *name;
@property (nonatomic,assign) NSInteger age;
@end
@implementation Student
@end
int main(int argc, char * argv[]) {
...
}
复制代码
-
然后打开我们的命令工具,cd到main.m所在目录下(这个命令相信大家都会吧)
-
然后执行命令$
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
-
这时候我们可以看到目录下多了一个main.cpp文件,这个就是我们需要研究和分析的文件:
双击打开它,我们会看到7000多行的代码,看到这里是不是有点懵?小编你玩我呢,这怎么阅读!别急,我们只需要研究我们想要的部分即可。
针对main.cpp文件,全局搜索“student
”,可以看到这样的代码:
由图可知,对象在底层的实现,本质上就是一个结构体
。如果还有小伙伴不确定这个Student_IMPL结构体就是我们的student,可以自己再定义一个属性,执行上面的xcrun命令,再次找到main.cpp这块区域的代码进行查看对比。
我们还看到,Student_IMPL里有一个属性NSObject_IVARS
,它的类型是NSObject_IMPL,全局搜索”NSObject_IMPL {
“找到这样一串代码:
struct NSObject_IMPL {
Class isa;
};
复制代码
说明NSObject_IVARS本质上就是一个isa
指针,然后我们看到isa的类型是Class,全局搜索”Class
“,这样搜的结果有点多不好找,我们改为搜”objc_class
“,找到这样一串代码:
typedef struct objc_class *Class;
复制代码
说明Class
的底层实现也是一个结构体指针
,它是objc_class结构体指针的一个别名。
另外,我们在typedef struct objc_class *Class
下面还看到这样一行代码:
typedef struct objc_object *id;
复制代码
说明我们平时常用的通用类型id
,在本质上也是一个objc_object的指针类型
,这也就解释了我们平时定义通用类型参数的时候,id后面不用跟*
了,因为它本身就是一个指针。
图片上还有一句代码:
typedef struct objc_object Student;
复制代码
意思是Student这个类它是一个结构体,Student继承自NSObject,而NSObject在底层的实现是objc_object。
了解了对象的本质是结构体之后,我们再来看看对象的属性,我们知道Student有个属性是name,在main.cpp
全局搜索“_name
”,可以看到这样一串代码:
static NSString * _I_Student_name(Student * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Student$_name)); }
static void _I_Student_setName_(Student * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_Student$_name)) = name; }
复制代码
额。。似乎不是那么方便阅读,那我们给它格式化一下代码:
static NSString * _I_Student_name(Student * self, SEL _cmd) {
return (*(NSString **)((char *)self + OBJC_IVAR_$_Student$_name));
}
static void _I_Student_setName_(Student * self, SEL _cmd, NSString *name) {
(*(NSString **)((char *)self + OBJC_IVAR_$_Student$_name)) = name;
}
复制代码
由此可见,这两个方法是name属性的set
方法和get
方法的底层实现,它包含了两个隐形参数self
和_cmd
。我们再来看它的get
方法,return返回的是一大串似乎很复杂的东西,噢买糕的。。这都是啥玩意儿,奇奇怪怪的。。。
还记得上一篇文章,提到的x/8gx打印内容吗?里面的属性值打印,是通过地址去访问的。所以由此可以分析出来,上面的get方法renturn的也是一个地址,它等于self首地址+name对应OBJC_IVAR在Student类里的偏移量,这边给大家画个图:
拓展:属性的取值是通过地址来访问,而属性的IVAR在程序编译之后就固定了
,所以这也就解释了为什么不能在category里添加实例变量。在分类中,我们定义属性,系统不会帮你自动生成set、get方法,需要你手动实现,这种属性我们通常称之为关联属性。而由于这种属性的关联是动态的,所以一旦程序出错,我们不太容易找到原因,尤其是多人开发的时候。
我这边提供了工程,里面带有编译好的main.cpp文件:
工程百度网盘地址
提取码: i9b7