底层原理-05-类的内存优化

241 阅读7分钟

1.WWDC 2020 - 1.类的优化

wwdc地址:developer.apple.com/videos/play…

类的加载过程中结构

苹果对类的结构体进一步进行了优化我们熟知的类的结构体如下:

截屏2021-06-21 下午3.11.08.png

属性协议实例方法 等信息主要在bits类型是class_data_bits_t* 当类第一次从磁盘加载到内存时的结构:

截屏2021-06-21 下午3.11.34.png
其中clss_ro_t readonly的意思,不做修改。
当类第一次被时候的时候的结构(runtime)

截屏2021-06-21 下午4.35.16.png class_rw_t是可读写的类型 比如当category被加载时,它可以向类中添加新的方法 通过runtime API手动向类中添加属性和方法

对于一些需要动态更新的部分提取出来存入class_rw_ext_t,这里就是苹果做出的优化,实际上动态更新的部分只有10%。对class_rw_t不常用的部分进行了剥离。如果需要用到这部分就从扩展记录中分配一个,滑到类中供其使用

截屏2021-06-21 下午4.35.24.png
类的整体结构

截屏2021-06-21 下午4.35.35.png

wwdc提出的2个概念

  • Clean Memory 加载后不会发生改变的内存,比如clss_ro_t clean memory 可以进行移除的,从而节省更多的内存空间,因为如果你有需要clean memory ,系统可以从磁盘中重新加载
  • Dirty Memory 进程运行时会发生改变的内存,比如class_rw_t 类结构一经使用就会变成 dirty memory,因为运行时会向它写入新的数据

2.类方法探索方式-runtime

我们之前通过lldb 打印的方式探索了属性实例变量现在我们通过runtime进行探索

@interface KBPerson :NSObject
{
    int age;//成员变量
    NSString *name;//成员变量
    NSObject *objc;//实例变量
    
}
@property (nonatomic, strong)NSString *nameA;
@property (nonatomic, copy) NSString *nameB;
@property (nonatomic)NSString *nameC;
@property (atomic)NSString *nameD;
@end

@implementation KBPerson
void test_IvasCopy_PropertiesCopy(Class class){
    
    unsigned int  count = 0;
    Ivar *ivars = class_copyIvarList(class, &count);
    for (int i = 0; i<count; i++) {
        Ivar const ivar = ivars[i];
        //获取是例变量名字
        const char *cName = ivar_getName(ivar);
        NSString *ivarName = [NSString stringWithUTF8String:cName];
        KBLog(@"ivar_is:%@",ivarName);
        
    }
    free(ivars);
    
    
    unsigned int pCount = 0;
    objc_property_t *properties = class_copyPropertyList(class, &pCount);
    
    for (unsigned int i =0; i<pCount; i++) {
        
        objc_property_t const  property = properties[i];
//        获取属性名字
        const char *pName = property_getName(property);
        NSString *propertyName = [NSString stringWithUTF8String:pName];
        KBLog(@"property_is:%@",propertyName);
    }

}
    

@end

打印后得到:

ivar_is:age
ivar_is:name
ivar_is:objc
ivar_is:_nameA
ivar_is:_nameB
ivar_is:_nameC
ivar_is:_nameD
property_is:nameA
property_is:nameB
property_is:nameC
property_is:nameD

和我们之前验证的一样,ivaslist 包含类所有的成员变量,属性列表只有属性
我们继续探索方法列表:

void test_methods_copy(Class class)
{
    
    unsigned count = 0;
    Method *methods = class_copyMethodList(class, &count);
    for (unsigned i = 0; i<count; i++) {
        Method const method = methods[i];
        //获取方法的名字
        NSString *methodNmae = NSStringFromSelector(method_getName(method));
        NSLog(@"method is:%@",methodNmae);
        
        
    }
    free(methods);
}

打印结果:

2021-06-21 19:21:16.283418+0800 方法探究[17854:317936] method is:sayNB
2021-06-21 19:21:16.283851+0800 方法探究[17854:317936] method is:nameA
2021-06-21 19:21:16.283916+0800 方法探究[17854:317936] method is:setNameA:
2021-06-21 19:21:16.284003+0800 方法探究[17854:317936] method is:nameB
2021-06-21 19:21:16.284084+0800 方法探究[17854:317936] method is:setNameB:
2021-06-21 19:21:16.284116+0800 方法探究[17854:317936] method is:nameC
2021-06-21 19:21:16.284146+0800 方法探究[17854:317936] method is:setNameC:
2021-06-21 19:21:16.284182+0800 方法探究[17854:317936] method is:nameD
2021-06-21 19:21:16.284332+0800 方法探究[17854:317936] method is:setNameD:
2021-06-21 19:21:16.284365+0800 方法探究[17854:317936] method is:.cxx_destruct//方法原本是为了C++对象析构的,ARC借用了这个方法插入代码实现了自动内存释放的工作

但是没有发现类方法。我们在之前探索中知道实例方法在类中,那么是否可以假设类方法在元类中

 Class metaClass = object_getClass(KBPerson.class);
        test_methods_copy(metaClass);

打印:

2021-06-21 19:34:41.750020+0800 方法探究[17953:325489] method is:sayNB2

验证了我们的猜想。

3.编码

SELIMP关系
SEL是方法名,IMP是方法入口,函数指针地址。相当于SEL是一本书的每一条目录,IMP是具体的页数。
1.我们通过SEL知道这个书的目录是什么内容
2.我们通过IMP知道具体的页码(IMP是一个入口)
3.我们通过具体的页码去找目录下的内容
官方类型编码

截屏2021-06-21 下午7.51.38.png 我们也可以打印

void lgTypes(void){
    NSLog(@"char --> %s",@encode(char));
    NSLog(@"int --> %s",@encode(int));
    NSLog(@"short --> %s",@encode(short));
    NSLog(@"long --> %s",@encode(long));
    NSLog(@"long long --> %s",@encode(long long));
    NSLog(@"unsigned char --> %s",@encode(unsigned char));
    NSLog(@"unsigned int --> %s",@encode(unsigned int));
    NSLog(@"unsigned short --> %s",@encode(unsigned short));
    NSLog(@"unsigned long --> %s",@encode(unsigned long long));
    NSLog(@"float --> %s",@encode(float));
    NSLog(@"bool --> %s",@encode(bool));
    NSLog(@"void --> %s",@encode(void));
    NSLog(@"char * --> %s",@encode(char *));
    NSLog(@"id --> %s",@encode(id));
    NSLog(@"Class --> %s",@encode(Class));
    NSLog(@"SEL --> %s",@encode(SEL));
    int array[] = {1,2,3};
    NSLog(@"int[] --> %s",@encode(typeof(array)));
    typedef struct person{
        char *name;
        int age;
    }Person;
    NSLog(@"struct --> %s",@encode(Person));
    
    typedef union union_type{
        char *name;
        int a;
    }Union;
    NSLog(@"union --> %s",@encode(Union));

    int a = 2;
    int *b = {&a};
    NSLog(@"int[] --> %s",@encode(typeof(b)));
}

打印结果:

2021-06-21 19:54:40.680563+0800 001-类的属性与变量[18144:335155] char --> c
2021-06-21 19:54:40.680978+0800 001-类的属性与变量[18144:335155] int --> i
2021-06-21 19:54:40.681020+0800 001-类的属性与变量[18144:335155] short --> s
2021-06-21 19:54:40.681045+0800 001-类的属性与变量[18144:335155] long --> q
2021-06-21 19:54:40.681068+0800 001-类的属性与变量[18144:335155] long long --> q
2021-06-21 19:54:40.681090+0800 001-类的属性与变量[18144:335155] unsigned char --> C
2021-06-21 19:54:40.681112+0800 001-类的属性与变量[18144:335155] unsigned int --> I
2021-06-21 19:54:40.681133+0800 001-类的属性与变量[18144:335155] unsigned short --> S
2021-06-21 19:54:40.681167+0800 001-类的属性与变量[18144:335155] unsigned long --> Q
2021-06-21 19:54:40.681189+0800 001-类的属性与变量[18144:335155] float --> f
2021-06-21 19:54:40.681211+0800 001-类的属性与变量[18144:335155] bool --> B
2021-06-21 19:54:40.681585+0800 001-类的属性与变量[18144:335155] void --> v
2021-06-21 19:54:40.681630+0800 001-类的属性与变量[18144:335155] char * --> *
2021-06-21 19:54:40.708258+0800 001-类的属性与变量[18144:335155] id --> @
2021-06-21 19:54:40.708305+0800 001-类的属性与变量[18144:335155] Class --> #
2021-06-21 19:54:40.708336+0800 001-类的属性与变量[18144:335155] SEL --> :
2021-06-21 19:54:40.708379+0800 001-类的属性与变量[18144:335155] int[] --> [3i]
2021-06-21 19:54:40.708402+0800 001-类的属性与变量[18144:335155] struct --> {person=*i}
2021-06-21 19:54:40.708426+0800 001-类的属性与变量[18144:335155] union --> (union_type=*i)
2021-06-21 19:54:40.708466+0800 001-类的属性与变量[18144:335155] int[] --> ^i

4.面试题

分别打印下面的值并说出为什么

    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);

我们通过源码可知isKindOfClass 的类方法和实例方法:

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;//for循环查找自己类的ISA 即元类,以及元类的父类是否和cls类相等,相等为yes
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;//for循环查找自己,和自己类的父类,相当于在类的继承关系上查找是否和cls有相等
    }
    return NO;
}


isMemberOfClass的源码:

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

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;自己的类和cls比较
}


那么结果是:

re1:1 [NSObject class]为类,走类方法,进行for循环查找NSObject的元类继承关系,根元类的父类是NSObject 所以最后和[NSObject class]相等为true;
re2:0 [NSObject class]为类 走类方法,查找自己的元类 ,元类和类不相同,false
re3:0 [LGPerson class]为类 走类方法,for循环查找LGPerson的元类和元类的父类是否和LGPerson相等,没有想等的为false
re4:0 [LGPerson class]为类 走类方法,LGPerson的元类和LGPerson不相等为false
re5:1 [NSObject alloc]为实例 走实例方法,for循环查找NSObject的类和父类 ,是否有和 NSObject相等的,NSObject = NSObjecttrue
re6:1 [NSObject alloc]为实例 走实例方法,[NSObject alloc]的isa 为类 和NSObject想的所以为true
re7:1 [LGPerson alloc]为实例 走实例方法 for循环查找实例的isa 和他的父类,LGPerson alloc]的isa 是LGPerson 和 [LGPerson class]相等所以true
re8:1 [LGPerson alloc]为实例 走实例方法 [LGPerson alloc]->ISALGPerson 和[LGPerson class]相等所以true
  • 总结:
    isKindOfClass:是一个循环方法,类方法就是循环找这个自己这个类的元类和元类的父类和想要比较的类进行比较。实例就是找自己的类以及父类和想要比较的类进行比较。
    isMemberOfClass:查找一次,实例的话找所指的类,类的话找所指的元类和想比较的类进行比较。
    都是找传入的ISA进行比较,isKindOfClass要找父类,isMemberOfClass不要。