Objective-c对象&类&元类&内存对齐等的知识点
一、概览
1.1 类的本质
是结构体。
objc2.0源码之前,使用的是objc_class结构体:

objc2.0源码新使用的objc_class结构体:

- clang
将代码编译成c++的代码使用clang:
clang
clang -rewrite-obj main.m -o main.cpp
- xcrun
使用xcrun指定sdk进行输出c++代码
xcrun
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx源文件 -o 输出的c++文件名
1.2 获取对象的内存大小
在runtime的<objc/runtime.h>中,定一个了一个class_getInstanceSize的函数,可以获取类的实例的大小:


从图中可以看出,系统给对象分配了16个字节,但是对象只占了8个字节。
class_getInstanceSize获取的是内存对齐后的成员变量相加后所占用的大小malloc_size获取的是系统给对象开辟内存的大小
oc的runtime源码中要求所有的对象开辟空间至少是16字节。

1.3 实例对象探究
结构体的成员在内存中是连续存储的。

如图所示,示例对象中存储了isa指针和成员变量的值。
1.4 内存对齐
oc中规定对象最小开辟空间是16个字节。
即使没有这个约束,那么还有一个内存对齐。
内存对齐:结构体的大小必须是最大成员大小的倍数。

针对stu,其实是这样理解:isa是8个字节,int占4个字节,加起来是12,但是由于内存对齐规则,所以需要是8的倍数,那么接近12的8的倍数,就是16。
iOS系统的要求是,malloc分配出来的内存空间,一定是16(buket size的最大值是256)的倍数。
1.5 对象实例中的存储
实例中存储isa指针和成员变量的值,没有方法。
1.6 sizeof
会在编译器确定传入的值是什么类型,比如,如果是指针类型,直接替换成8,如果是int类型,那么替换成4.
二、示例解读
2.1 如下示例

这个示例中,结构体的class_getInstanceSize大小是24,但是malloc_size是32。

解释:iOS系统的要求是,malloc分配出来的内存空间,一定是16(buket size的最大值是256)的倍数。instanceSize只需要24个字节就可以存储,但是分配内存时又要是16的倍数。
三、类对象
3.1 获取类对象
oc.m
NSObject *obj = [[NSOject alloc] init];
Class objCls = [obj class];
Class objCls1 = [NSObject class];
Class objCls2 = object_getClass(obj);
每个类在内存中有且只有一个class对象。
3.2 类对象中存储的信息
- isa指针
- superclass指针
- 类的属性信息、类的对象方法信息
- 类的协议信息、类的成员变量信息
四、元类对象
4.1 获取元类对象
oc.m
Class metaCls = object_getClass([NSObject class]);
Class metaCls1 = object_getClass([obj class]);
每个类在内存中有且只有一个meta-class对象。
4.2 元类中存储的数据
- isa指针
- superclass指针
- 类的类方法信息
4.3 判断是否是元类对象
oc.m
class_isMetaClass([NSObject class]);
4.4 Objective-c中为什么设计元类
主要是为了复用消息机制。参考下面五中的指向图。
五、isa & superclass
实例对象、类对象、元类对象都有isa指针,有一个指向图如下:

isa和superclass的指向都如图所示。
5.1 经典案例
oc.m
// 新增一个NSObject+Test分类
@interface NSObject (Test)
+ (void)test;
@end
@implementation NSObject (Test)
+ (void)test {
NSLog(@"NSObject中的+test", self);
}
- (void)test {
NSLog(@"NSObject中的-test", self);
}
@end
// 一个oc类Person
@interface Person: NSObject
+ (void)test;
@end
@implementation Person
+ (void)test {
NSLog(@"Person中的+test", self);
}
@end
// main.m中的main函数
int main(int argc, const char *argv[]) {
NSLog(@"Person中的类adr: %p", [Person class]);
NSLog(@"NSObject中的类adr: %p", [NSObject class]);
[Person test];
[NSObject test];
}
- 第一种情况:NSObject分类Test中声明了类方法test以及实现,Person类也声明和实现了类方法。
会各自调用自己的类方法,打印各自类方法中的NSLog;
打印的self,也是各自的类对象地址。各自的self都是各自类对象地址。
- 第二种情况:NSObject分类Test中声明了类方法test以及实现,Person类只声明,但不实现类方法。
NSObject分类会调用自己的类方法,打印自几类方法中的NSLog;
Person调用NSObject中的类方法test。
输出的self也是各自的类对象地址。
- 第三种情况:NSObject分类Test中声明了类方法+test,但只实现实例方法-test,Person类只声明+test,但不实现类方法。
输出的self也是各自的类对象地址。因为调用+test方法的接收者都是各自的类对象。
NSObject分类和Person都会调用分类的实例方法。
因为当NSObject元类中没有类+test方法时,会从superclass找到父类NSObject,父类NSObject中有方法名test【是不会辨别类方法和实例方法的,只关注方法名】,所以尽管main函数中是调用了类方法,但是最终会走到NSObject的相同方法名的实例方法中去。
5.2 示例[self class]和[super class]
oc.m
@interface Person: NSObject
@end
@implementation Person
- (instancetype)init {
self = [super init];
if (self) {
NSLog(@"self class: %@;super class: %@", [self class], [super class])
}
return self;
}
@end
以上代码均打印Person对象地址。
解释:
[self class],对象的class,直接就是对象的isa指向,所以是Person类。
[super class],表示从当前类的父类进行查找方法,所以以上代码需要从Person的父类NSObject中去找,NSObject实现了class方法,那么读取方法接收者的isa指针,这里的方法接收者还是self,所以self的isa指针指向的还是Person类。
使用clang将oc转为c++:
(下面这张图是偷懒从网上找的)

按照苹果的文档,super是一个指向objc_super结构体的指针。

[super class]的消息接收者即receiver是self。所以当最后找isa指针时,还是找的self的isa指向的类Person。
5.3 isa & isa_mask才能是类的地址值

isa是一个共同体,如下:

示例(控制台中通过lldb调试,验证class的isa是需要&isa_mask的值,就是元类的地址):

上图中,因为objc2.0,将isa设置为private,所以在oc中使用lldb调试时,使用personClass->isa是拿不到isa的,所以上图中的mj_objc_class这个结构体模拟的就是objc2.0中object_class这个结构体,目的是为了给oc暴露isa这个指针来查看isa指向的地址值

superclass没有使用mask,所以是直接指向的父类的地址。

六、objc_class的探索
新的objc源码中是使用了objc_class,继承自objc_object【c++语法】。

6.1 objc_class的数据结构

6.2 小结

七、分类
7.1 分类的方法和类方法
- 分类的方法是和主类的方法是存在一起的,都在元类的method_list_t
- 分类的方法不会覆盖主类的方法
- 分类如果有和类同名的方法,分类的方法排在主类的前面
- 分类的加载在主类之后
如果要确保只使用主类中的方法:

文章太长,其他部分下篇再附上。
暂无评论,快来发表第一条评论吧