[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fgQeQsmOnfEbuIUeKd2I3tCcZXTo2DnZIWK3qeUunxgo":3},{"code":4,"message":5,"data":6},200,"成功",{"id":7,"createdAt":8,"title":9,"content":10,"summary":11,"image":12,"uid":13,"user":14,"categoryId":21,"category":22,"subCategoryId":24,"subCategory":25,"comments":27,"status":17,"reason":28,"notice":28,"visitCount":29,"commentCount":30,"keywords":31},106,"2024-04-02T16:49:01.483Z","Objective-c对象&类&元类&内存对齐等的知识点","## 一、概览\n### 1.1 类的本质\n是结构体。\n\nobjc2.0源码之前，使用的是objc_class结构体：\n![老objc_class.png](https://image.xinwei.ltd/image1712075563167.png)\nobjc2.0源码新使用的objc_class结构体:\n![新objc_class.png](https://image.xinwei.ltd/image1712075678950.png)\n\n\n- clang\n将代码编译成c++的代码使用clang：\n```clang\nclang -rewrite-obj main.m -o main.cpp\n```\n\n\n- xcrun\n使用xcrun指定sdk进行输出c++代码\n```xcrun\nxcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx源文件 -o 输出的c++文件名\n```\n\n\n### 1.2 获取对象的内存大小\n在runtime的`\u003Cobjc/runtime.h>`中，定一个了一个`class_getInstanceSize`的函数，可以获取类的实例的大小：\n![class_getInstanceSize.jpg](https://image.xinwei.ltd/11712074108958.jpg)\n\n![class_getInstanceSize使用示例.jpg](https://image.xinwei.ltd/21712074148927.jpg)\n\n\n从图中可以看出，系统给对象分配了***16***个字节，但是对象只占了***8***个字节。\n- `class_getInstanceSize`获取的是内存对齐后的成员变量相加后所占用的大小\n- `malloc_size`获取的是系统给对象开辟内存的大小\n\n\n> oc的runtime源码中要求所有的对象开辟空间至少是16字节。\n> \n![oc->instanceSize.jpg](https://image.xinwei.ltd/31712074260364.jpg)\n\n\n### 1.3 实例对象探究\n结构体的成员在内存中是连续存储的。\n![对象内存结构.jpg](https://image.xinwei.ltd/41712074471516.jpg)\n如图所示，示例对象中存储了isa指针和成员变量的值。\n\n### 1.4 内存对齐\noc中规定对象最小开辟空间是16个字节。\n\n即使没有这个约束，那么还有一个内存对齐。\n\n**内存对齐**：结构体的大小必须是最大成员大小的倍数。\n![内存对齐.jpg](https://image.xinwei.ltd/51712074608060.jpg)\n\n针对stu，其实是这样理解：isa是8个字节，int占4个字节，加起来是12，但是由于内存对齐规则，所以需要是8的倍数，那么接近12的8的倍数，就是16。\n\niOS系统的要求是，malloc分配出来的内存空间，一定是16（buket size的最大值是256）的倍数。\n### 1.5 对象实例中的存储\n实例中存储isa指针和成员变量的值，没有方法。\n\n### 1.6 sizeof\n会在编译器确定传入的值是什么类型，比如，如果是指针类型，直接替换成8，如果是int类型，那么替换成4.\n\n## 二、示例解读\n### 2.1 如下示例\n![内存大小探究.jpg](https://image.xinwei.ltd/61712074692488.jpg)\n\n这个示例中，结构体的class_getInstanceSize大小是24，但是malloc_size是32。\n![内存大小探究2.jpg](https://image.xinwei.ltd/71712074740173.jpg)\n\n解释：iOS系统的要求是，malloc分配出来的内存空间，一定是16（buket size的最大值是256）的倍数。instanceSize只需要24个字节就可以存储，但是分配内存时又要是16的倍数。\n\n## 三、类对象\n### 3.1 获取类对象\n```oc.m\nNSObject *obj = [[NSOject alloc] init];\n\nClass objCls = [obj class];\n\nClass objCls1 = [NSObject class];\n\nClass objCls2 = object_getClass(obj);\n```\n\n每个类在内存中有且只有一个class对象。\n\n### 3.2 类对象中存储的信息\n- isa指针\n- superclass指针\n- 类的属性信息、类的对象方法信息\n- 类的协议信息、类的成员变量信息\n\n\n## 四、元类对象\n### 4.1 获取元类对象\n```oc.m\nClass metaCls = object_getClass([NSObject class]);\n\nClass metaCls1 = object_getClass([obj class]);\n```\n\n每个类在内存中有且只有一个meta-class对象。\n### 4.2 元类中存储的数据\n- isa指针\n- superclass指针\n- 类的类方法信息\n\n### 4.3 判断是否是元类对象\n```oc.m\nclass_isMetaClass([NSObject class]);\n```\n\n\n### 4.4 Objective-c中为什么设计元类\n主要是为了复用消息机制。参考下面五中的指向图。\n\n\n\n## 五、isa & superclass\n实例对象、类对象、元类对象都有isa指针，有一个指向图如下：\n![isa指向图.jpg](https://image.xinwei.ltd/81712074886786.jpg)\n\nisa和superclass的指向都如图所示。\n\n### 5.1 经典案例\n```oc.m\n// 新增一个NSObject+Test分类\n@interface NSObject (Test)\n\n+ (void)test;\n\n@end\n\n@implementation NSObject (Test)\n\n+ (void)test {\n    NSLog(@\"NSObject中的+test\", self);\n}\n\n- (void)test {\n    NSLog(@\"NSObject中的-test\", self);\n}\n\n@end\n\n\n// 一个oc类Person\n@interface Person:  NSObject\n\n+ (void)test;\n\n@end\n\n@implementation Person\n\n+ (void)test {\n    NSLog(@\"Person中的+test\", self);\n}\n\n@end\n\n\n// main.m中的main函数\nint main(int argc, const char *argv[]) {\n    NSLog(@\"Person中的类adr: %p\", [Person class]);\n    NSLog(@\"NSObject中的类adr: %p\", [NSObject class]);\n    \n    [Person test];\n    [NSObject test];\n}\n```\n\n- 第一种情况：NSObject分类Test中声明了类方法test以及实现，Person类也声明和实现了类方法。\n\n会各自调用自己的类方法，打印各自类方法中的NSLog；\n\n打印的self，也是各自的类对象地址。各自的self都是各自类对象地址。\n\n- 第二种情况：NSObject分类Test中声明了类方法test以及实现，Person类只声明，但不实现类方法。\n\nNSObject分类会调用自己的类方法，打印自几类方法中的NSLog；\n\nPerson调用NSObject中的类方法test。\n\n输出的self也是各自的类对象地址。\n\n- 第三种情况：NSObject分类Test中声明了类方法+test，但只实现实例方法-test，Person类只声明+test，但不实现类方法。\n\n输出的self也是各自的类对象地址。因为调用+test方法的接收者都是各自的类对象。\n\nNSObject分类和Person都会调用分类的实例方法。\n\n> 因为当NSObject元类中没有类+test方法时，会从superclass找到父类NSObject，父类NSObject中有方法名test【是不会辨别类方法和实例方法的，只关注方法名】，所以尽管main函数中是调用了类方法，但是最终会走到NSObject的相同方法名的实例方法中去。\n\n### 5.2 示例[self class]和[super class]\n```oc.m\n@interface Person: NSObject\n@end\n\n@implementation Person\n\n- (instancetype)init {\n    self = [super init];\n    if (self) {\n        NSLog(@\"self class: %@；super class: %@\", [self class], [super class])\n    }\n    return self;\n}\n\n@end\n```\n\n以上代码均打印Person对象地址。\n\n解释：\n[self class]，对象的class，直接就是对象的isa指向，所以是Person类。\n\n[super class]，表示从当前类的父类进行查找方法，所以以上代码需要从Person的父类NSObject中去找，NSObject实现了class方法，那么读取方法接收者的isa指针，这里的方法接收者还是self，所以self的isa指针指向的还是Person类。\n\n\n使用clang将oc转为c++：\n（下面这张图是偷懒从网上找的）\n![oc转c++.jpg](https://image.xinwei.ltd/91712075033158.jpg)\n\n按照苹果的文档，super是一个指向objc_super结构体的指针。\n![objc_super.jpg](https://image.xinwei.ltd/101712075065185.jpg)\n\n[super class]的消息接收者即receiver是self。所以当最后找isa指针时，还是找的self的isa指向的类Person。\n\n### 5.3 isa & isa_mask才能是类的地址值\n![isa_mask逻辑.jgp](https://image.xinwei.ltd/111712075115672.jpg)\n\nisa是一个共同体，如下：\n![isa结构体.png](https://image.xinwei.ltd/image1712076334468.png)\n\n\n示例（控制台中通过lldb调试，验证class的isa是需要&isa_mask的值，就是元类的地址）：\n![isa_mask的lldb调试.jpg](https://image.xinwei.ltd/121712075142935.jpg)\n\n上图中，因为objc2.0，将isa设置为private，所以在oc中使用lldb调试时，使用personClass->isa是拿不到isa的，所以上图中的mj_objc_class这个结构体模拟的就是objc2.0中object_class这个结构体，目的是为了给oc暴露isa这个指针来查看isa指向的地址值\n![objc_object中的private私有isa.png](https://image.xinwei.ltd/image1712076197032.png)\n\nsuperclass没有使用mask，所以是直接指向的父类的地址。\n![superclass的lldb调试.jpg](https://image.xinwei.ltd/131712075197419.jpg)\n\n\n## 六、objc_class的探索\n新的objc源码中是使用了objc_class，继承自objc_object【c++语法】。\n![objc_class源码.jpg](https://image.xinwei.ltd/141712075246203.jpg)\n\n\n### 6.1 objc_class的数据结构\n![objc_class结构.jpg](https://image.xinwei.ltd/151712075306105.jpg)\n\n\n\n### 6.2 小结\n![ios面试题.jpg](https://image.xinwei.ltd/161712075365343.jpg)\n\n\n## 七、分类\n### 7.1 分类的方法和类方法\n- 分类的方法是和主类的方法是存在一起的，都在元类的method_list_t\n- 分类的方法不会覆盖主类的方法\n- 分类如果有和类同名的方法，分类的方法排在主类的前面\n- 分类的加载在主类之后\n如果要确保只使用主类中的方法：\n![响应本类方法.jpg](https://image.xinwei.ltd/171712075418800.jpg)\n\n\n文章太长，其他部分下篇再附上。\n\n\n","这篇文章描述了oc中的对象、类、元类、内存对齐和isa指针等内容。这篇文章是多年前在学习iOS的过程中做的笔记，如今整理了一下发到这里，供大家参阅交流，后面也会陆陆续续将之前的文章分享出来。","https://image.xinwei.ltd/image1712075563167.png",499668042977349,{"phone":15,"userId":13,"nickName":16,"vipType":17,"avatar":18,"sign":19,"createdAt":20},"13121171998","全栈老韩",1,"https://image.xinwei.ltd/images/IMG_5430.JPG","全栈工程师，擅长iOS App开发、前端（vue、react、nuxt、小程序&Taro）开发、Flutter、React Native、后端（midwayjs、golang、express、koa）开发、docker容器、seo优化等。","2024-01-01T16:14:30.305Z",2,{"id":21,"name":23},"IT技术",3,{"id":24,"name":26,"parentId":21},"iOS",[],"",1012,0,"oc,oc class,oc meta class,ios memory,ios对象,ios类,ios元类,ios内存"]