iOS-底层原理 05 类的数据结构优化 & 属性和成员变量的区别

249

WWDC 2020 - 运行时的优化

主要对运行时做了三个地方的优化:

  • 数据结构改变。OC在运行时会使用他们进行追踪类。
  • OC方法列表的变化
  • tagged pointer格式的变化 官方地址

数据结构改变

Clean memoryDirty memory的区别

Clean memory

  • 加载后不会改变的内存(如:class_ro_t就在这里,因为他是只读)
  • 这里可以删除,从而节约更多的空间,需要时在磁盘中重新创建。 Dirty memory
  • 在运行时就发生改变的内存(如:类结构一经过使用就会变成dirty memory,因为他在运行时会写入数据,比如创建一个新的方法缓存,并从类中指向他) 所以,在运行时内存消耗就会很多,优化更多的运行时内存就变得很重要。最新版本,就对运行时的数据结构进行了改造,把动态添加的方法协议属性等添加到class_rw_ext_t中.

当类第一次从磁盘加载时的结构

myclassDirty Memory内存区,class_ro_tClean Memory内存区 运行后 2.png

当类第一次使用时的结构

第一次使用时会为他分配额外的存储容量,class_rw_t用于读写数据,在这里存储了只有在运行时才会生成的数据(如:所有的类都会链接成一个树状结构,是通过first subclassnext sibling class来实现,可以遍历所有当前使用的类)。同时存在运行时更改的方法,属性,协议(当分类加载时,会向类中添加新的方法)。这样会造成内存太大,为了减少内存,又把不常用的方法属性协议 放在了class_rw_ext_t中,提高内存使用效率。 运行后.png

将需要动态更新的部分提取出来,存⼊class_rw_ext_t

运行后.png

当类使用到了扩展部分的方法

class_rw_t优化,其实就是对class_rw_t不常用的部分进行了剥离。如果需要用到这部分就从扩展记录中分配一个,滑到类中供其使用。

未命名文件3.png

方法列表改变

每个类都有一个方法列表,每个方法都有三个参数:名称参数和返回值指向方法实现的指针。这些参数都是以指针进行存储,当使用时去查询该指针指向的区域即可。以前使用了8字节(64位)存储这些指针,但是因为指针和指向的内存基本上都在一个二进制下,其实并不需要64位,所以把64位 偏移量查询方法实现改成了32位偏移查询。这样可以节约一半的内存。

Tagged pointer格式的改变

是对64位的存储信息的优化,增加读取的效率。因为一般情况下,64位的信息是不会存储满的,在高位和低位都是0,那么在这些位置可以增加一些其他信息,比如存储的类型 是否是指针等信息,方便读取使用。增加运行时的效率。

类的成员变量和属性

  • 成员变量是指类声明大括号中的变量。包含基本类型变量(intNSString等类型),实例变量(对象)。
  • 成员变量用于类内部,无需与外界接触的变量
  • 成员变量:在底层没有其他操作只是变量的声明
  • 属性:系统会自动在底层添加了_属性名变量,同时生成setter和getter方法

需要研究的代码

{
    NSString *hobby;
}
@property (atomic, copy) NSString *acnickName;
@property (nonatomic) NSString *nnickName;

可以使用clang把类编译成.cpp文件。参考这里

编译后结果如下

struct LGPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *hobby;
	NSString *_nickName;
	NSString *_acnickName;
};
...
static NSString * _I_LGPerson_acnickName(LGPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), 1); }
static void _I_LGPerson_setAcnickName_(LGPerson * self, SEL _cmd, NSString *acnickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), (id)acnickName, 1, 1); }

static NSString * _I_LGPerson_nnickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nnickName)); }
static void _I_LGPerson_setNnickName_(LGPerson * self, SEL _cmd, NSString *nnickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nnickName)) = nnickName; }
...
  • 成员变量仅仅只是变量声明
  • 属性不仅有变量声明,还有getset方法
  • 属性不同的修饰符,都有对应的实现方法。
  • copy修饰的属性采用了objc_getPropertyobjc_setProperty方法。
  • 其他修饰符修饰的属性使用的是地址+偏移量进行赋值取值

查看objc的源码,分析setget实现原理。获取oc源码

  • 全局搜索objc_setProperty,找到如下几个类型的方法

截屏2021-06-24 上午2.05.37.png

  • 搜索reallySetProperty,可以看到其实现方法

截屏2021-06-24 上午2.11.10.png

  • 搜索objc_getProperty

截屏2021-06-24 上午2.19.13.png

也可以使用LLVM源码,进行分析底层的实现(仅参考)

根据clang编译后的文件中的方法objc_setPropertyobjc_getProperty,去下载好的LLVM文件中 LLVM-project反过来推导其方法过程。

  • 搜索objc_setProperty

截屏2021-06-24 上午2.32.34.png

  • 搜索getSetPropertyFn,图片省略
  • 搜索GetPropertySetFunction,图片省略
  • 搜索PropertyImplStrategy,图片省略

截屏2021-06-24 上午2.45.45.png

补充

isKindOfClassisMemberOfClass的区别

isKindOfClass 底层实现

// 元类 -> 根元类 -> NSObject -> nil
//类的元类与cls比较
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}
//对象的类与cls比较
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

isKindOfClass 底层实现

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls; //类的元类和cls比较
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;//类 = 类
}

实例

    BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
    BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
    BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       //
    BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     //
    NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

    BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
    BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
    BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];       //
    BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     //
    NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

输出

 re1 :1    
 re2 :0
 re3 :0
 re4 :0
 
 re5 :1
 re6 :1
 re7 :1
 re8 :1

对执行结果进行分析

  • re1 : 1 ,是 NSObject的元类与 NSObject 的对比,使用 +isKindOfClass
NSObject的元类是根元类,与NSObject比较,不相等
获取根元类的父类即NSObject,与NSObject相等
  • re2 : 0 ,是 NSObject的元类与 NSObject 的对比,使用 +isMemberOfClass
NSObject的元类是根元类,与NSObject比较,不相等
  • re3 : 0 ,是 LGPerson的元类与 LGPerson 的对比,使用 +isKindOfClass
LGPerson的元类,与LGPerson比较,不相等
元类的父类是根元类,与LGPerson比较,不相等
根元类的父类是NSObject,与LGPerson比较,不相等
  • re4 : 0 ,是 LGPerson的元类与 NSObject 的对比,使用 +isMemberOfClass
LGPerson的元类,与LGPerson比较,不相等
  • re5 : 1 ,是 NSObjectNSObject 的对比,使用 -isKindOfClass
NSObjectNSObject比较,相等
  • re6 : 1 ,是 NSObjectNSObject 的对比,使用 -isMemberOfClass
NSObjectNSObject比较,相等
  • re7 : 1 ,是 LGPersonLGPerson 的对比,使用 -isKindOfClass
LGPerson与LGPerson比较,相等
  • re8 : 1 ,是 LGPersonNSObject 的对比,使用 -isMemberOfClass
LGPerson与LGPerson比较,相等