前言
上一篇文章写完以后发现我们在.m文件转成 .cpp以后好像里面的细节内容没有讲清楚,为了学习探索并且让上一篇文章能够更清晰一些。我决定在更深层次探索一下。
前置条件
上一篇我们创建 Animal 的文件,同时创建他的一个分类Eat。我们对其进行一些内容添加。具体内容如下所示。
cd 到目标文件下,终端利用
clang输出cpp文件
clang -rewrite-objc Animal.m -o AnimalCate.cpp
内容讲解
属性
因为代码内容太多我们先全局搜索一下关键字Animal,我们发现了下面的代码
struct Animal_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int type;
NSString *name;
int _age;
CGFloat _size;
};
通过上面的代码,我们发现,这个结构体包含了我们所有定义的变量。这个结构体里只有属性变量,通过以前的知识我们可以联想到,定义一个结构体以后,他在内存所占用的内存大小,就是他定义的属性变量大小。(我们在面试中,总会被问这个问题) NSObject_IMPL其实就是我们所谓的isa。
通过全局搜索我们得到
struct NSObject_IMPL { Class isa; };
方法
我们接着往下看 就会看到方法的定义。
//
static void _C_Animal_live(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_7r_d8xxgvyj49q6l2m7nng6gsgh0000gn_T_Animal_882bb6_mi_0);
}
static void _I_Animal_run(Animal * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_7r_d8xxgvyj49q6l2m7nng6gsgh0000gn_T_Animal_882bb6_mi_1);
}
// get set方法
static CGFloat _I_Animal_size(Animal * self, SEL _cmd) { return (*(CGFloat *)((char *)self + OBJC_IVAR_$_Animal$_size)); }
static void _I_Animal_setSize_(Animal * self, SEL _cmd, CGFloat size) { (*(CGFloat *)((char *)self + OBJC_IVAR_$_Animal$_size)) = size; }
// get set方法
static int _I_Animal_age(Animal * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_Animal$_age)); }
static void _I_Animal_setAge_(Animal * self, SEL _cmd, int age) { (*(int *)((char *)self + OBJC_IVAR_$_Animal$_age)) = age; }
通过属性和方法 我们发现无论是类方法还是实例方法都没有是实现在结构体内部,而是直接定义在外部,结构体里面是可以定义方法的,为什么不在结构体里面定义实现方法呢?
我们查看方法参数可以看出,每个方法 都有一个Animal * self的变量。我们的思路开始清晰了一点。
如果每实现一个对象,都要给他的方法分配内存,当实例对象增多的时候,内存占用也多了,但是他们的方法其实都是一样的没区别的。重复的申请内存是没有意义的,我们是不是可以通过参数将实例对象传到方法里面就可以不用重复实现了呢? 很明显我们找到了原因。
我们继续看 我们发现在get set看到了好奇怪的内容
extern "C" unsigned long int OBJC_IVAR_$_Animal$_size __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct Animal, _size);
(*(CGFloat *)((char *)self + OBJC_IVAR_$_Animal$_size)) = size;
我们来解释一下
(char *)self把
self转换成char *类型的指针;
((char *)self + OBJC_IVAR_$_Animal$_size)指针移动,由于先前已经转换成
char*类型,所以指针移动的步长是一个字节,总共移动OBJC_IVAR_$_Animal$_size长度的字节,OBJC_IVAR_$_Animal$_size就是_size属性在Animal_IMPL结构体中的内存偏移量
(CGFloat *)((char *)self + OBJC_IVAR_$_Animal$_size)这一步是指针移动完成后,把指针转成
CGFloat *类型的;这是因为_size属性是CGFloat类型的;
(*(CGFloat *)((char *)self + OBJC_IVAR_$_Animal$_size))由于式子是在等号
=左边, 所以加*号是赋值的意思
类
为了便于结合代码理解和分析我们通过代码加注释的方法进行解析
// 共有变量
struct _prop_t {
const char *name;
const char *attributes;
};
struct _protocol_t;
/*
方法的描述
_cmd:方法的名字
method_type:方法的类型,像这样"v16@0:8",描述这个方法的返回值类型,参数类型等
_imp:函数指针,可以理解为方法的实现的地址,通过这个指针就能拿到这个方法;从而调用这个方法;
runtime里面的黑魔法,方法交换;其实就是修改_imp指针,让它指针另一个函数的地址;
*/
struct _objc_method {
struct objc_selector * _cmd;
const char *method_type;
void *_imp;
};
// 目前不涉及 先不展开讲
struct _protocol_t {
void * isa; // NULL
const char *protocol_name;
const struct _protocol_list_t * protocol_list; // super protocols
const struct method_list_t *instance_methods;
const struct method_list_t *class_methods;
const struct method_list_t *optionalInstanceMethods;
const struct method_list_t *optionalClassMethods;
const struct _prop_list_t * properties;
const unsigned int size; // sizeof(struct _protocol_t)
const unsigned int flags; // = 0
const char ** extendedMethodTypes;
};
/*
变量描述,变量的名字,类型,占用的内存字节数,内存偏移量
offset:保存这个变量在结构体内存中的内存偏移量;方便找到这个变量的位置;类似_class_ro_t结构体中的instanceStart属性
name:变量名字
type:变量的类型
alignment:字节对齐
size:这个变量占的内存长度;类似_class_ro_t结构体中的instanceSize属性
*/
struct _ivar_t {
unsigned long int *offset; // pointer to ivar offset location
const char *name;
const char *type;
unsigned int alignment;
unsigned int size;
};
/*
保存类的具体内
instanceStart: 对象在内存地址中开始的位置,如果我们要获取某个对象,就需要先拿到这个属性,通过指针移动,获取到这个对象内存的起始位置
instanceSize :这个就是对象的内存长度,也就是这个对象在内存中占用的字节数
name:类名
baseMethods:保存方法位置
baseProtocols:保存协议位置
ivars:保存对象变量位置
weakIvarLayout 可能是弱应用??
properties:保存属性的位置
*/
struct _class_ro_t {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
unsigned int reserved;
const unsigned char *ivarLayout;
const char *name;
const struct _method_list_t *baseMethods;
const struct _objc_protocol_list *baseProtocols;
const struct _ivar_list_t *ivars;
const unsigned char *weakIvarLayout;
const struct _prop_list_t *properties;
};
/*
isa指针指向的就是_class_t结构体对象;
可以把p->isa转成:
struct _class_t *c = (struct _class_t *)(p->isa);
对Animal类来说,全局只有一个Animal类对象,
这个类对象就是_class_t对象,我们创建对象的时候,会把类对象(也就是_class_t对象)的指针地址保存一份到对象的isa上;
也就是我们每创建一个p对象,p对象都会在isa上保存一份类对象的指针地址
*/
struct _class_t {
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};
//分类
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
#pragma warning(disable:4273)
extern "C" unsigned long int OBJC_IVAR_$_Animal$type __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct Animal, type);
extern "C" unsigned long int OBJC_IVAR_$_Animal$name __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct Animal, name);
extern "C" unsigned long int OBJC_IVAR_$_Animal$_age __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct Animal, _age);
extern "C" unsigned long int OBJC_IVAR_$_Animal$_size __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct Animal, _size);
/**
属性列表
长度是4 包含所有属性
"type", "i", 2, 4
分别对应了 _ivar_t里的
name,type, alignment,size;
这里说一下 alignment
对齐值 2 表示变量应该对齐到 2^(2-1) = 2 字节边界。
对齐值 3 表示变量应该对齐到 2^(3-1) = 4 字节边界。
所以代码中:
type 和 _age 的对齐要求是 2 字节。
name 和 _size 的对齐要求是 4 字节。
*/
static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[4];
} _OBJC_$_INSTANCE_VARIABLES_Animal __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
4,
{{(unsigned long int *)&OBJC_IVAR_$_Animal$type, "type", "i", 2, 4},
{(unsigned long int *)&OBJC_IVAR_$_Animal$name, "name", "@\"NSString\"", 3, 8},
{(unsigned long int *)&OBJC_IVAR_$_Animal$_age, "_age", "i", 2, 4},
{(unsigned long int *)&OBJC_IVAR_$_Animal$_size, "_size", "d", 3, 8}}
};
/**
实例方法列表
entsize:保存的是_objc_method结构体的占内存的大小;
method_count:保存的是方法数量
method_list:所有的方法都保存在这个数组中;
一个方法的具体内容保存在_objc_method结构体中;
*/
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[9];
} _OBJC_$_INSTANCE_METHODS_Animal __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
9,
{{(struct objc_selector *)"run", "v16@0:8", (void *)_I_Animal_run},
{(struct objc_selector *)"size", "d16@0:8", (void *)_I_Animal_size},
{(struct objc_selector *)"setSize:", "v24@0:8d16", (void *)_I_Animal_setSize_},
{(struct objc_selector *)"age", "i16@0:8", (void *)_I_Animal_age},
{(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_Animal_setAge_},
{(struct objc_selector *)"size", "d16@0:8", (void *)_I_Animal_size},
{(struct objc_selector *)"setSize:", "v24@0:8d16", (void *)_I_Animal_setSize_},
{(struct objc_selector *)"age", "i16@0:8", (void *)_I_Animal_age},
{(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_Animal_setAge_}}
};
/**
共有变量列表
*/
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Animal __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"size","Td,N,V_size"}}
};
/**
元类 ro 目前我们只是实现了一个类方法 ,目前这个类方法存储在 _OBJC_$_CLASS_METHODS_Animal 里
*/
static struct _class_ro_t _OBJC_METACLASS_RO_$_Animal __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1, sizeof(struct _class_t), sizeof(struct _class_t),
(unsigned int)0,
0,
"Animal",
(const struct _method_list_t *)&_OBJC_$_CLASS_METHODS_Animal,
0,
0,
0,
0,
};
/**
类对象里的ro
将上面 实现的相关属性存储在类对象ro中
*/
static struct _class_ro_t _OBJC_CLASS_RO_$_Animal __attribute__ ((used, section ("__DATA,__objc_const"))) = {
0, __OFFSETOFIVAR__(struct Animal, type), sizeof(struct Animal_IMPL),
(unsigned int)0,
0,
"Animal",
(const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_Animal,
0,
(const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_Animal,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Animal,
};
实现以上所有内容以后就要对类进行申明实现
//父类
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSObject;
//类
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_Animal __attribute__ ((used, section ("__DATA,__objc_data"))) = {
0, // &OBJC_METACLASS_$_Animal,
0, // &OBJC_CLASS_$_NSObject,
0, // (void *)&_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_CLASS_RO_$_Animal,
};
//类的赋值
static void OBJC_CLASS_SETUP_$_Animal(void ) {
OBJC_CLASS_$_Animal.isa = &OBJC_METACLASS_$_Animal;
OBJC_CLASS_$_Animal.superclass = &OBJC_CLASS_$_NSObject;
OBJC_CLASS_$_Animal.cache = &_objc_empty_cache;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) **static** **void** *OBJC_CLASS_SETUP[] = {
(**void** *)&OBJC_CLASS_SETUP_$_Animal,
};
元类
/**
类方法列表
entsize:保存的是_objc_method结构体的占内存的大小;
method_count:保存的是方法数量
method_list:所有的方法都保存在这个数组中;
一个方法的具体内容保存在_objc_method结构体中;
*/
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CLASS_METHODS_Animal __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"live", "v16@0:8", (void *)_C_Animal_live}}
};
//根类 (元类的父类)
extern "C" __declspec(dllimport) struct _class_t OBJC_METACLASS_$_NSObject;
// 元类
extern "C" __declspec(dllexport) struct _class_t OBJC_METACLASS_$_Animal __attribute__ ((used, section ("__DATA,__objc_data"))) = {
0, // &OBJC_METACLASS_$_NSObject,
0, // &OBJC_METACLASS_$_NSObject,
0, // (void *)&_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_METACLASS_RO_$_Animal,
};
//元类 的赋值
static void OBJC_CLASS_SETUP_$_Animal(void ) {
OBJC_METACLASS_$_Animal.isa = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_Animal.superclass = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_Animal.cache = &_objc_empty_cache;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) **static** **void** *OBJC_CLASS_SETUP[] = {
(**void** *)&OBJC_CLASS_SETUP_$_Animal,
};
注意元类和类的 OBJC_CLASS_SETUP_$_Animal内容其实是合并在一起的,并没有因为类和元类不同分开。