1. WWDC 类结构的优化

WWDC 2020-Runtime优化:

  • 类结构的优化
  • 方法列表的优化
  • Tagged Pointer格式的优化

下面我们只针对第一项“类结构的优化”进行详细说明

1.1 Clean Memory & Dirty Memory

1.1.1 Clean Memory

  • 加载后不会再改变的内存
  • class_ro_t是只读的,属于Clean Memory
  • 可移除,从而节省更多内存空间。如果你有需要,系统可以从磁盘中重新加载

1.1.2 Dirty Memory

  • 进程运行时会发生变化的内存
  • 类结构体一旦被使用就是Dirty Memory,因为运行时会写入新的数据,例如:方法缓存
  • Dirty Memory是类数据被分为两部分的原因

Dirty MemoryClean Memory更昂贵,因为在进程运行的整个过程中,都需要被保留。通过分离出那些永远不会改变的数据,将大部分的类数据存储为Clean Memory

1.2 类结构的优化

类第一次从磁盘被加载到内存时的结构
image.png

  • 类对象包含了最常用的信息:元类、父类、以及方法的缓存。它还有一个指针,指向额外信息class_ro_t,其中ro表示只读
  • class_ro_t中包含了类名、方法、协议、实例变量和属性等信息

当一个类首次被使用,运行时会为它分配额外的存储容量,即:class_rw_t
image.png

  • 其中rw表示可读写

通过firstSubclassnextSiblingClass,所有的类都会链接成一个树状结构
image.png

  • 允许运行时遍历当前使用的所有类

为什么方法和属性,在class_ro_tclass_rw_t各存一份?
image.png

  • 可以在运行时进行更改
  • category被加载时,它可以向类中添加新的方法
  • 使用Runtime API动态添加属性和方法
  • 因为class_ro_t是只读的,所以需要在class_rw_t中来跟踪这些东西

class_rw_t结构在设备中,占用很多的内存,那么我们如何去缩小这些结构呢?

在实践中发现,大约只有10%的类,真正的更改了它们的方法

所有,我们可以将不常用的部分进行拆分
image.png

  • class_rw_t的大小可减少一半

对于确实需要额外信息的类,我们可以分配这些扩展记录中的一个,并把它滑到类中供其使用
image.png

如果原来的代码直接访问class_rw_t结构,由于结构内存布局发生了变化,可能产生崩溃。苹果推荐使用Runtime API,这样底层的细节会由系统处理
image.png

2. 成员变量 & 实例变量 & 属性

2.1 成员变量与属性的区别

  • 成员变量:在底层只是变量的声明
  • 属性:系统会自动在底层添加_属性名变量,同时生成settergetter方法

2.2 成员变量与实例变量的区别

  • 实例变量是一种特殊的成员变量
  • 成员变量为基本数据类型
  • 实例变量为对象类型,例如:NSObject类型
  • NSString为常量类型,属于成员变量

2.3 属性的底层实现

打开main.m文件,写入以下代码:

  1. #import <Foundation/Foundation.h>
  2. @interface LGPerson : NSObject{
  3. NSString *desc;
  4. NSObject *obj;
  5. }
  6. @property (strong,nonatomic) NSString *name;
  7. @end
  8. @implementation LGPerson
  9. @end

生成cpp文件

  1. clang -rewrite-objc main.m -o main.cpp

打开main.cpp文件,查看属性的底层实现

  1. extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
  2. struct LGPerson_IMPL {
  3. struct NSObject_IMPL NSObject_IVARS;
  4. NSString *desc;
  5. NSObject *obj;
  6. NSString *_name;
  7. };
  8. // @property (strong,nonatomic) NSString *name;
  9. static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
  10. static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }
  • 在底层代码中,属性被优化掉
  • 生成_开头的成员变量 + getter/setter方法

3. 编码

底层的方法列表中,生成的编码含义是什么?

  1. static struct /*_method_list_t*/ {
  2. unsigned int entsize; // sizeof(struct _objc_method)
  3. unsigned int method_count;
  4. struct _objc_method method_list[4];
  5. } _OBJC_$_INSTANCE_METHODS_LGPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
  6. sizeof(_objc_method),
  7. 4,
  8. {{(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGPerson_name},
  9. {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_},
  10. {(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGPerson_name},
  11. {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_}}
  12. };

3.1 Code对照表

编码中Code与数据类型的对照表
image.png

3.2 编码的含义

编码中,除了Code之外,还有数字,它们组合在一起的含义是什么?

案例1

name属性的getter方法

  1. NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd)
  2. -------------------------
  3. @16@0:8
  • @16@表示返回值类型为id类型,16表示方法所占用的内存为16字节
  • @0@表示第一个参数类型为id类型,0表示参数1存储的起始位置,占8字节
  • :8:表示第二个参数类型为SEL类型,8表示参数2存储的起始位置,占8字节
  • 参数分别为objc_msgSend的两个隐含参数,id类型的selfSEL类型的_cmd

案例2

name属性的setter方法

  1. void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name)
  2. -------------------------
  3. v24@0:8@16
  • v24v表示返回值类型为void类型,24表示方法所占用的内存为24字节
  • @0@表示第一个参数类型为id类型,0表示参数1存储的起始位置,占8字节
  • :8:表示第二个参数类型为SEL类型,8表示参数2存储的起始位置,占8字节
  • @16@表示第三个参数类型为id类型,16表示参数3存储的起始位置,占8字节

3.3 使用代码查看类型编码

  1. void lgTypes(void){
  2. NSLog(@"char --> %s",@encode(char));
  3. NSLog(@"int --> %s",@encode(int));
  4. NSLog(@"short --> %s",@encode(short));
  5. NSLog(@"long --> %s",@encode(long));
  6. NSLog(@"long long --> %s",@encode(long long));
  7. NSLog(@"unsigned char --> %s",@encode(unsigned char));
  8. NSLog(@"unsigned int --> %s",@encode(unsigned int));
  9. NSLog(@"unsigned short --> %s",@encode(unsigned short));
  10. NSLog(@"unsigned long --> %s",@encode(unsigned long long));
  11. NSLog(@"float --> %s",@encode(float));
  12. NSLog(@"bool --> %s",@encode(bool));
  13. NSLog(@"void --> %s",@encode(void));
  14. NSLog(@"char * --> %s",@encode(char *));
  15. NSLog(@"id --> %s",@encode(id));
  16. NSLog(@"Class --> %s",@encode(Class));
  17. NSLog(@"SEL --> %s",@encode(SEL));
  18. int array[] = {1,2,3};
  19. NSLog(@"int[] --> %s",@encode(typeof(array)));
  20. typedef struct person{
  21. char *name;
  22. int age;
  23. }Person;
  24. NSLog(@"struct --> %s",@encode(Person));
  25. typedef union union_type{
  26. char *name;
  27. int a;
  28. }Union;
  29. NSLog(@"union --> %s",@encode(Union));
  30. int a = 2;
  31. int *b = {&a};
  32. NSLog(@"int[] --> %s",@encode(typeof(b)));
  33. }
  34. -------------------------
  35. //输出结果:
  36. char --> c
  37. int --> i
  38. short --> s
  39. long --> q
  40. long long --> q
  41. unsigned char --> C
  42. unsigned int --> I
  43. unsigned short --> S
  44. unsigned long --> Q
  45. float --> f
  46. bool --> B
  47. void --> v
  48. char * --> *
  49. id --> @
  50. Class --> #
  51. SEL --> :
  52. int[] --> [3i]
  53. struct --> {person=*i}
  54. union --> (union_type=*i)
  55. int[] --> ^i
  • @encode:获取指定类型的编码字符串

扩展内容

为什么底层生成属性的getter/setter方法有同样的两份?
image.png

使用MachOView查看MachO文件
image.png

  • setName:为例:在符号表中,同样的方法,因符号的功能及种类不同,出现了两份

使用objdump命令,按符号功能查看

  1. objdump --macho --syms KCObjcBuild | grep setName:
  2. -------------------------
  3. 0000000100003d20 l F __TEXT,__text -[LGPerson setName:]
  4. 0000000100003d20 l d *UND* -[LGPerson setName:]
  • F:表示Function
  • d:表示Debug

使用nm命名,按符号种类查看

  1. nm -pa KCObjcBuild | grep setName:
  2. -------------------------
  3. 0000000100003d20 t -[LGPerson setName:]
  4. 0000000100003d20 - 01 0000 FUN -[LGPerson setName:]
  • t:表示符号存储在代码段(__TEXT.__text),小写表示符号为本地符号(local
  • -:表示调试符号

4. setter方法的底层实现

4.1 准备工作

打开main.m文件,写入以下代码:

  1. #import <Foundation/Foundation.h>
  2. @interface LGPerson : NSObject
  3. @property (strong,nonatomic) NSString *name;
  4. @property (copy,nonatomic) NSString *nick;
  5. @end
  6. @implementation LGPerson
  7. @end

生成cpp文件

  1. clang -rewrite-objc main.m -o main.cpp

4.2 底层实现的差异

OC中,使用不同修饰符定义属性,例如:strongcopy。会导致setter方法在底层实现的差异

name属性的setter方法

  1. static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }
  • 使用内存平移方式

nick属性的setter方法

  1. extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
  2. static void _I_LGPerson_setNick_(LGPerson * self, SEL _cmd, NSString *nick) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nick), (id)nick, 0, 1); }
  • 调用objc_setProperty函数,相当于所有属性setter方法的抽取封装,因为setter方法的本质都是修改内存中的数据

找到setter方法和objc_setProperty在底层的关联时机:

方法在编译阶段已经确定函数地址
image.png

所以setter方法和objc_setProperty的关联,在编译时期由llvm完成

4.3 探索llvm源码

打开llvm-project源码

搜索objc_setProperty关键字

打开CGObjCMac.cpp文件

4.3.1 getSetPropertyFn函数

image.png

  • 找到objc_setProperty函数的创建
  • 由下至上反推,找出函数的创建时机

搜索getSetPropertyFn关键字

4.3.2 GetPropertySetFunction函数

image.png

  • 找到中间层的调用,继续向上层反推

搜索GetPropertySetFunction关键字

打开CGObjC.cpp文件

4.3.3 generateObjCSetterBody函数

image.png

  • 当策略为GetSetPropertySetPropertyAndExpressionGet时,有可能触发GetPropertySetFunction函数

判断strategy.getKind()的策略值
image.png


搜索GetSetProperty关键字

4.3.4 PropertyImplStrategy函数

image.png

  • 使用copy修饰,Kind设置GetSetProperty

4.4 探索objc源码

找到objc_setProperty的函数实现

打开objc-accessors.mm文件

4.4.1 objc_setProperty函数

  1. void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
  2. {
  3. bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
  4. bool mutableCopy = (shouldCopy == MUTABLE_COPY);
  5. reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
  6. }
  • 调用reallySetProperty函数

4.4.2 reallySetProperty函数

  1. static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
  2. {
  3. if (offset == 0) {
  4. object_setClass(self, newValue);
  5. return;
  6. }
  7. id oldValue;
  8. id *slot = (id*) ((char*)self + offset);
  9. if (copy) {
  10. newValue = [newValue copyWithZone:nil];
  11. } else if (mutableCopy) {
  12. newValue = [newValue mutableCopyWithZone:nil];
  13. } else {
  14. if (*slot == newValue) return;
  15. newValue = objc_retain(newValue);
  16. }
  17. if (!atomic) {
  18. oldValue = *slot;
  19. *slot = newValue;
  20. } else {
  21. spinlock_t& slotlock = PropertyLocks[slot];
  22. slotlock.lock();
  23. oldValue = *slot;
  24. *slot = newValue;
  25. slotlock.unlock();
  26. }
  27. objc_release(oldValue);
  28. }
  • 底层触发内存拷贝的逻辑,和strong使用内存平移赋值是有很大差异的

总结:

  • objc_setProperty的函数实现,在objc4-818.2源码中

5. Runtime API

5.1 class_copyMethodList

class_copyMethodList:获取类的实例方法列表

5.1.1 获取方法列表

封装objc_copyMethodList方法

  1. void objc_copyMethodList(Class pClass){
  2. unsigned int count = 0;
  3. Method *methods = class_copyMethodList(pClass, &count);
  4. for (unsigned int i=0; i < count; i++) {
  5. Method const method = methods[i];
  6. NSString *key = NSStringFromSelector(method_getName(method));
  7. NSLog(@"Method:%@", key);
  8. }
  9. free(methods);
  10. }

打印类的方法列表

  1. objc_copyMethodList(LGPerson.class);
  2. -------------------------
  3. MethodsayNB
  4. Methodnick
  5. MethodsetNick:
  6. Methodinit
  7. Methodname
  8. Method:.cxx_destruct
  9. MethodsetName:

打印元类的方法列表

  1. objc_copyMethodList(objc_getMetaClass(class_getName(LGPerson.class)));
  2. -------------------------
  3. Methodgood

5.2 class_getInstanceMethod

class_getInstanceMethod:获取指定类中的实例方法

5.2.1 验证实例方法

封装instanceMethod_classToMetaclass函数

  1. void instanceMethod_classToMetaclass(Class pClass){
  2. const char *className = class_getName(pClass);
  3. Class metaClass = objc_getMetaClass(className);
  4. Method method1 = class_getInstanceMethod(pClass, @selector(sayNB));
  5. Method method2 = class_getInstanceMethod(metaClass, @selector(sayNB));
  6. NSLog(@"类的sayNB实例方法:%p", method1);
  7. NSLog(@"元类的sayNB实例方法:%p", method2);
  8. Method method3 = class_getInstanceMethod(pClass, @selector(good));
  9. Method method4 = class_getInstanceMethod(metaClass, @selector(good));
  10. NSLog(@"类的good实例方法:%p", method3);
  11. NSLog(@"元类的good实例方法:%p", method4);
  12. }

传入LGPerson类对象

  1. instanceMethod_classToMetaclass(LGPerson.class);
  2. -------------------------
  3. 类的sayNB实例方法:0x1000081d0
  4. 元类的sayNB实例方法:0x0
  5. 类的good实例方法:0x0
  6. 元类的good实例方法:0x100008168
  • 类中存储了sayNB实例方法
  • 元类中存储了good实例方法

总结:

  • OC底层,所有方法都是实例方法
  • 类方法,元类中存储的实例方法

5.3 class_getClassMethod

class_getClassMethod:获取指定类中的类方法

5.3.1 验证类方法

封装classMethod_classToMetaclass函数

  1. void classMethod_classToMetaclass(Class pClass){
  2. const char *className = class_getName(pClass);
  3. Class metaClass = objc_getMetaClass(className);
  4. Method method1 = class_getClassMethod(pClass, @selector(sayNB));
  5. Method method2 = class_getClassMethod(metaClass, @selector(sayNB));
  6. NSLog(@"类的sayNB类方法:%p", method1);
  7. NSLog(@"元类的sayNB类方法:%p", method2);
  8. Method method3 = class_getClassMethod(pClass, @selector(good));
  9. Method method4 = class_getClassMethod(metaClass, @selector(good));
  10. NSLog(@"类的good类方法:%p", method3);
  11. NSLog(@"元类的good类方法:%p", method4);
  12. }

传入LGPerson类对象

  1. classMethod_classToMetaclass(LGPerson.class);
  2. -------------------------
  3. 类的sayNB类方法:0x0
  4. 元类的sayNB类方法:0x0
  5. 类的good类方法:0x100008158
  6. 元类的good类方法:0x100008158
  • 类中存储了good类方法
  • 元类中存储了good类方法

疑问:

  • 为什么元类中存储了good类方法?

5.3.2 探索objc源码

找到class_getClassMethod函数

  1. Method class_getClassMethod(Class cls, SEL sel)
  2. {
  3. if (!cls || !sel) return nil;
  4. return class_getInstanceMethod(cls->getMeta(), sel);
  5. }
  • 调用class_getInstanceMethod函数
  • 本质:查找当前类对象所属元类中的实例方法

找到getMeta函数

  1. Class getMeta() {
  2. if (isMetaClassMaybeUnrealized()) return (Class)this;
  3. else return this->ISA();
  4. }
  • 传入的类对象是元类,返回自己

所以,元类中存储good类方法,其实找到的还是元类中的good实例方法

5.4 class_getMethodImplementation

class_getMethodImplementation:获取指定方法的函数实现地址

5.4.1 SEL & IMP

SELIMP的关系
image.png

  • SEL:书的⽬录名称(⽅法编号)
  • IMP : ⽬录的⻚码(函数指针地址)

查找流程:

  • ⾸先明⽩我们要找到书本的什么内容(SEL⽬录⾥⾯的名称)
  • 通过名称找到对应的⻚码(通过SELIMP
  • 通过⻚码去定位具体的内容(通过IMP找到函数实现)

5.4.2 验证方法IMP

封装imp_classToMetaclass函数

  1. void imp_classToMetaclass(Class pClass){
  2. const char *className = class_getName(pClass);
  3. Class metaClass = objc_getMetaClass(className);
  4. IMP imp1 = class_getMethodImplementation(pClass, @selector(sayNB));
  5. IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayNB));
  6. NSLog(@"类的sayNB方法的IMP:%p", imp1);
  7. NSLog(@"元类的sayNB方法的IMP:%p", imp2);
  8. IMP imp3 = class_getMethodImplementation(pClass, @selector(good));
  9. IMP imp4 = class_getMethodImplementation(metaClass, @selector(good));
  10. NSLog(@"类的good方法的IMP:%p", imp3);
  11. NSLog(@"元类的good方法的IMP:%p", imp4);
  12. }

传入LGPerson类对象

  1. imp_classToMetaclass(pClass);
  2. -------------------------
  3. 类的sayNB方法的IMP0x100003aa0
  4. 元类的sayNB方法的IMP0x7fff202405c0
  5. 类的good方法的IMP0x7fff202405c0
  6. 元类的good方法的IMP0x100003ab0

疑问:

  • 元类中没有sayNB实例方法,为什么存在方法IMP
  • 类中没有good类方法,为什么存在方法IMP
  • 元类中sayNB和类中good,两个方法IMP为什么相同?

5.4.3 探索objc源码

找到class_getMethodImplementation函数

  1. __attribute__((flatten))
  2. IMP class_getMethodImplementation(Class cls, SEL sel)
  3. {
  4. IMP imp;
  5. if (!cls || !sel) return nil;
  6. lockdebug_assert_no_locks_locked_except({ &loadMethodLock });
  7. imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
  8. // Translate forwarding function to C-callable external version
  9. if (!imp) {
  10. return _objc_msgForward;
  11. }
  12. return imp;
  13. }
  • 如果找不到方法IMP,返回_objc_msgForward函数地址

找到_objc_msgForward的定义

  1. OBJC_EXPORT void
  2. _objc_msgForward(void /* id receiver, SEL sel, ... */ )
  3. OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
  • Runtime的消息转发机制

所以,当方法IMP不存在时,触发Runtime的消息转发机制,返回_objc_msgForward函数地址,所以IMP地址相同

6. isKindOfClass

6.1 方法的作用

打开objc4-818.2源码

查看汇编代码
image.png

  • 无论调用类对象还是实例对象的isKindOfClass方法,入口函数统一为objc_opt_isKindOfClass

找到objc_opt_isKindOfClass函数

  1. BOOL
  2. objc_opt_isKindOfClass(id obj, Class otherClass)
  3. {
  4. #if __OBJC2__
  5. if (slowpath(!obj)) return NO;
  6. Class cls = obj->getIsa();
  7. if (fastpath(!cls->hasCustomCore())) {
  8. for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
  9. if (tcls == otherClass) return YES;
  10. }
  11. return NO;
  12. }
  13. #endif
  14. return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
  15. }
  • 获取对象isa指向的类,和传入的Class对比
  • 遍历对象isa指向类的父类,和传入的Class对比

isKindOfClass方法的作用:

  • 类对象

◦ 元类 vs Class
◦ 遍历:元类的父类 vs Class

  • 实例对象

◦ 类 vs Class
◦ 遍历:类的父类 vs Class

6.2 验证类对象与实例对象

封装isKindOfClass函数

  1. void isKindOfClassDemo(){
  2. BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
  3. BOOL re2 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];
  4. NSLog(@"NSObject类对象:%hhd", re1);
  5. NSLog(@"LGPerson类对象:%hhd", re2);
  6. BOOL re3 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
  7. BOOL re4 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
  8. NSLog(@"NSObject实例对象:%hhd", re3);
  9. NSLog(@"LGPerson实例对象:%hhd", re4);
  10. }

调用isKindOfClassDemo函数

  1. isKindOfClassDemo();
  2. -------------------------
  3. NSObject类对象:1
  4. LGPerson类对象:0
  5. NSObject实例对象:1
  6. LGPerson实例对象:1

7. isMemberOfClass

7.1 方法的作用

打开objc4-818.2源码

查看汇编代码
image.png

  • 调用指定对象的objc_msgSend函数

找到isMemberOfClass方法

  1. + (BOOL)isMemberOfClass:(Class)cls {
  2. return self->ISA() == cls;
  3. }
  4. - (BOOL)isMemberOfClass:(Class)cls {
  5. return [self class] == cls;
  6. }
  • 类方法:获取类的元类,和传入的cls对比
  • 实例方法:获取对象所属的类,和传入的cls对比

7.2 验证类对象与实例对象

封装isMemberOfClassDemo方法

  1. void isMemberOfClassDemo(){
  2. BOOL re1 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
  3. BOOL re2 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];
  4. NSLog(@"NSObject类对象:%hhd", re1);
  5. NSLog(@"LGPerson类对象:%hhd", re2);
  6. BOOL re3 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
  7. BOOL re4 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
  8. NSLog(@"NSObject实例对象:%hhd", re3);
  9. NSLog(@"LGPerson实例对象:%hhd", re4);
  10. }

调用isMemberOfClassDemo函数

  1. isMemberOfClassDemo();
  2. -------------------------
  3. NSObject类对象:0
  4. LGPerson类对象:0
  5. NSObject实例对象:1
  6. LGPerson实例对象:1

总结

WWDC 2020-Runtime优化

  • 类结构的优化
  • 方法列表的优化
  • Tagged Pointer格式的优化

WWDC-类结构的优化

  • Clean Memory

◦ 加载后不会再改变的内存
class_ro_t是只读的,它就属于Clean Memory
◦ 可移除,从而节省更多内存空间。如果你有需要,系统可以从磁盘中重新加载

  • Dirty Memory

◦ 进程运行时会发生变化的内存
◦ 类结构体一旦被使用就是Dirty Memory,因为运行时会写入新的数据,例如:方法缓存
Dirty Memory是类数据被分为两部分的原因

  • 方法和属性在class_ro_tclass_rw_t各存一份

◦ 可以在运行时进行更改
◦ 当category被加载时,它可以向类中添加新的方法
◦ 用Runtime API动态添加属性和方法
◦ 因为class_ro_t是只读的,所以需要在class_rw_t中来跟踪这些东西

  • 优化class_rw_t空间

◦ 拆分class_rw_ext_t,存储方法、属性、协议等不常用的部分

成员变量与属性的区别

  • 成员变量:在底层只是变量的声明
  • 属性:系统会自动在底层添加_属性名变量,同时生成settergetter方法

成员变量与实例变量的区别

  • 实例变量是一种特殊的成员变量
  • 成员变量为基本数据类型
  • 实例变量为对象类型,例如:NSObject类型
  • NSString为常量类型,属于成员变量

属性的底层实现

  • 在底层代码中,属性被优化掉
  • 生成_开头的成员变量 + getter/setter方法

编码

  • Code对照表:Type Encodings
  • @encode:获取指定类型的编码字符串

setter方法的底层实现

  • OC中,使用不同修饰符定义属性,例如:strongcopy。会导致setter方法在底层的实现产生差异

strong:内存平移
copy:调用objc_setProperty函数

  • objc_setProperty

setter方法和objc_setProperty的关联,在编译时期由llvm完成
objc_setProperty的函数实现,在objc4-818.2源码中
◦ 底层触发内存拷贝的逻辑,和strong使用内存平移赋值是有很大差异的

SELIMP的关系

  • SEL:书的⽬录名称(⽅法编号)
  • IMP : ⽬录的⻚码(函数指针地址)

Runtime API

  • class_copyMethodList:获取类的实例方法列表
  • class_getInstanceMethod:获取指定类中的实例方法

◦ 在OC底层,所有方法都是实例方法
◦ 类方法,元类中存储的实例方法

  • class_getClassMethod:获取指定类中的类方法

◦ 本质:查找当前类对象所属元类中的实例方法
◦ 传入getMetah函数的类对象是元类,返回自己

  • class_getMethodImplementation:获取指定方法的函数实现地址

◦ 当方法IMP不存在时,触发Runtime的消息转发机制,返回_objc_msgForward函数地址

isKindOfClass

  • 类对象

◦ 元类 vs Class
◦ 遍历:元类的父类 vs Class

  • 实例对象

◦ 类 vs Class
◦ 遍历:类的父类 vs Class

isMemberOfClass

  • 类方法:获取类的元类,和传入的cls对比
  • 实例方法:获取对象所属的类,和传入的cls对比