iOS底层实现分析之对象的本质(未完待续)

和谐学习!不急不躁!!我是你们的老朋友小青龙~

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文件,这个就是我们需要研究和分析的文件:

01.png

双击打开它,我们会看到7000多行的代码,看到这里是不是有点懵?小编你玩我呢,这怎么阅读!别急,我们只需要研究我们想要的部分即可。
针对main.cpp文件,全局搜索“student”,可以看到这样的代码:

02.png

由图可知,对象在底层的实现,本质上就是一个结构体。如果还有小伙伴不确定这个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类里的偏移量,这边给大家画个图:

03.png

拓展:属性的取值是通过地址来访问,而属性的IVAR在程序编译之后就固定了,所以这也就解释了为什么不能在category里添加实例变量。在分类中,我们定义属性,系统不会帮你自动生成set、get方法,需要你手动实现,这种属性我们通常称之为关联属性。而由于这种属性的关联是动态的,所以一旦程序出错,我们不太容易找到原因,尤其是多人开发的时候。

我这边提供了工程,里面带有编译好的main.cpp文件
工程百度网盘地址

提取码: i9b7

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享