一、概要
先要区分两个概念:
1、类:一种结构体,所有对象公用一个类结构。
2、对象:通过类创建出来,每个对象有独立的内存空间。
Student *obj = [[Student alloc] init];
obj就是对象
Student就是类
二、对象结构
如上图,Student对象实例stu结构为Student_IMPL,定义的所有成员变量,属性都会包含在对象结构体中。这样就能确保所有对象的成员变量和属性都是独立的,相互不影响。
大家会发现,对象结构体里没有方法?
方法比较特殊,不像成员变量,方法可以所有对象公用一个方法,只需要在方法参数传入对象的本身的指针self,就可以区分是哪个对象在使用方法了。
那么,方法存到哪里呢?这里就引入了类结构。
三、类结构
objc_class就是类的结构,通过结构里面的bits可以找到另一个结构class_rw_t和class_ro_t。
1、先看看class_rw_t和class_ro_t主要的内容:
class_ro_t是类初始时候的东西,比如方法是放到baseMethodList
如果增加了分类,就会把分类方法放到class_rw_t的methods里面,并且把baseMethodList拷贝到methods的后面。这就解释了添加分类方法后,原同名的方法不是被覆盖的,而是被放到了分类的后面,导致每次都是只找到分类的方法。
2、method_t结构:
这里重点看看types:
oc的函数,默认会传入两个值,id和sel类型
i ->返回值
@ ->传入的第一个参数,id类型
: ->传入的第二个参数,sel类型
i ->传入的第三个参数,int类型
f ->传入的第三个参数,float类型
返回值i24代表,一共有24个字节。
其他的每个标识后面跟的数字代表从第几个字节开始。如 @0,代表 id参数从第0个字节开始。
更多类型参考:
3、objc_class
3.1、isa指针
下面,我们回来看看基本类结构objc_class。
isa指针,对象的isa指向类,类的指向元类。
arm64以前的,isa占8个字节,直接指向类或元类
arm64以后,isa使用共同体的方式,其中指向类或元类的地址指针为isa&mask得到,一个33位+后面3个0=36位。所以所有对象的地址后三位都是0。
3.2、superclass
比较容易理解,指向父类。下图是isa与superclass的关系:
对象调用方法的流程:
类调用方法的流程:
3.3、cache_t
通过散列表来缓存已经访问过的方法,以提高访问效率:
存:调用一个方法SEL(test)后,会直接通过一个哈希函数,把SEl(test)转换为对应buckets的idx。这里是直接SEL(test)&mask,任何&mask的值小于等于mask,如果冲突,就idx-1&mask的值。找到idx,就把此方法封装为bucket_t,如果存到buckets列表对应索引idx的位置。
找:查找SEL(test)方法,与上面类似,直接通过哈希函数得到函数对应的idx,然后直接就能找到对应的bucket了,然后再在bucket中找到对应函数的实现imp。
四、一些使用工具
1、将oc代码转为c/c++代码:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
2、查看工具:
下面看个题目:
@interface Student : NSObject
{
@public
int _anum;
int _bnum;
}
@property(nonatomic) int score;
- (void)showName;
@end
Student *obj = [[Student alloc] init];
obj->_anum = 10;
obj->_bnum = 11;
NSLog(@"instandsize=%zi mallocsize=%zi",class_getInstanceSize(obj.class),malloc_size((__bridge void *)obj));
class_getInstanceSize-->24
malloc_size--->32
class_getInstanceSize代表对象占用的内存,已最大字节对齐,因为isa指针为8个字节,所以是8的倍速。
malloc_size代表实际分配了多少的内存来存储这个对象。以16字节对齐。