runtimeiOS Runtime

Runtime(一)

2020-02-29  本文已影响0人  二猪哥

一、Runtime 是什么

首先我们都知道,将源代码转换为可执行的程序,通常要经过三个步骤:编译、链接、运行。不同的编译语言,在这三个步骤中所进行的操作又有些不同。
比如:

二、Runtime底层原理解析(使用源码:objc4-750

(一)、isa详解

要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针;在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址;从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息。

查看源码共用体结构:全局搜索:objc_object { ——>objc-private.h(选择这个文件)——>isa_t isa——> isa_t——>union isa_t {}。

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

/*ISA_BITFIELD表示如下*/
 uintptr_t nonpointer        : 1;   //位域   nonpointer:代表二进制的1位。
 uintptr_t has_assoc         : 1;
 uintptr_t has_cxx_dtor      : 1;
 uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
 uintptr_t magic             : 6;
 uintptr_t weakly_referenced : 1;
 uintptr_t deallocating      : 1;
 uintptr_t has_sidetable_rc  : 1;
 uintptr_t extra_rc          : 19;


//最终:
union isa_t {
    Class cls;
    uintptr_t bits;
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
    };
};
(1)、位域

前面提到了在arm64之后isa优化成了共用体,那么要理解这个共用体就不得不先了解位域,因为在这个共用体中,是使用位域去存储信息的。

有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用0和1表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种数据结构,叫做“位域”或“位段”

位域是操控位的一种方法(操控位的另一种方法是使用按位运算符,按位运算符将在之后的笔记中做介绍)。

位域通过一个结构声明来建立:该结构声明为每个字段提供标签,并确定该字段的宽度。例如,下面的声明:

struct {
    uintptr_t nonpointer        : 1;
    uintptr_t has_assoc         : 1;
    uintptr_t has_cxx_dtor      : 1;
    uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
    uintptr_t magic             : 6;
    uintptr_t weakly_referenced : 1;
    uintptr_t deallocating      : 1;
    uintptr_t has_sidetable_rc  : 1;
    uintptr_t extra_rc          : 8;
};

深入了解位域的可看这里。;理解位域之前,我们先来看一下位运算,位运算是操控位的另一种方法。

MHYPerson.h    声明tall、rich、handsome三个属性的set、get方法。
#import <Foundation/Foundation.h>

@interface MHYPerson : NSObject

- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;

- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
@end

MHYPerson.m 通过位运算实现三个属性的set、get方法
#import "MHYPerson.h"

// 掩码,一般用来按位与(&)运算的
//#define MHYTallMask 1
//#define MHYRichMask 2
//#define MHYHandsomeMask 4

//#define MHYTallMask 0b00000001
//#define MHYRichMask 0b00000010
//#define MHYHandsomeMask 0b00000100


#define MHYTallMask (1<<0)
#define MHYRichMask (1<<1)
#define MHYHandsomeMask (1<<2)
@interface MHYPerson()
{
    char _tallRichHansome;//定义一个char类型,Bool类型的值是0和1,只需一个二进制位就可以存储,char类型占1个字节,1个字节8位;(一个字节足以存储3个属性的值)
}
@end

@implementation MHYPerson
- (instancetype)init
{
    if (self = [super init]) {
        _tallRichHansome = 0b00000000;
    }
    return self;
}

- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHansome |= MHYTallMask;
    } else {
        _tallRichHansome &= ~MHYTallMask;
    }
}

- (BOOL)isTall
{
    return !!(_tallRichHansome & MHYTallMask);
}

- (void)setRich:(BOOL)rich
{
    if (rich) {
        _tallRichHansome |= MHYRichMask;
    } else {
        _tallRichHansome &= ~MHYRichMask;
    }
}

- (BOOL)isRich
{
    return !!(_tallRichHansome & MHYRichMask);
}

- (void)setHandsome:(BOOL)handsome
{
    if (handsome) {
        _tallRichHansome |= MHYHandsomeMask;
    } else {
        _tallRichHansome &= ~MHYHandsomeMask;
    }
}

- (BOOL)isHandsome
{
    return !!(_tallRichHansome & MHYHandsomeMask);
}
@end

main.m 类:

#import <Foundation/Foundation.h>
#import "MHYPerson.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MHYPerson *person = [[MHYPerson alloc] init];
        person.tall = YES;
        person.rich = YES;
        person.handsome = YES;
        NSLog(@"tall:%d rich:%d hansome:%d", person.isTall, person.isRich, person.isHandsome);
    }
    return 0;
}

打印结果:

2020-01-08 16:09:21.072299+0800 位运算添加属性[59170:365382] tall:1 rich:1 hansome:1
Program ended with exit code: 0
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;

例子:设定特殊的枚举值,2的倍数,通过&可计算得出。

typedef enum {
    MHYOptionsNone = 0,     // 0b0000
    MHYOptionsOne = 1<<0,   // 0b0001
    MHYOptionsTwo = 1<<1,   // 0b0010
    MHYOptionsThree = 1<<2, // 0b0100
    MHYOptionsFour = 1<<3   // 0b1000
} MHYOptions;
@interface ViewController ()

@implementation ViewController

- (void)setOptions:(MHYOptions)options
{
    if (!(options & MHYOptionsNone)) {
        NSLog(@"包含了MHYOptionsNone");
    }
    
    if (options & MHYOptionsOne) {
        NSLog(@"包含了MHYOptionsOne");
    }
    
    if (options & MHYOptionsTwo) {
        NSLog(@"包含了MHYOptionsTwo");
    }
    
    if (options & MHYOptionsThree) {
        NSLog(@"包含了MHYOptionsThree");
    }
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setOptions:MHYOptionsOne|MHYOptionsFour];
}

@end

打印结果:

2020-01-12 18:21:22.236731+0800 Interview01-位运算[55728:300211] 包含了MHYOptionsNone
2020-01-12 18:21:22.236847+0800 Interview01-位运算[55728:300211] 包含了MHYOptionsOne

现在主菜来了,从这个例子入手,通过位域如何给类添加属性呢?

#import <Foundation/Foundation.h>

@interface MHYPerson : NSObject

- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;

- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
@end
#import "MHYPerson.h"
@interface MHYPerson()
{
    // 位域
    struct {
        char tall : 1;
        char rich : 1;
        char handsome : 1;
    } _tallRichHandsome;
}
@end

@implementation MHYPerson
- (void)setTall:(BOOL)tall
{
    _tallRichHandsome.tall = tall;
}

- (BOOL)isTall
{
    return !!_tallRichHandsome.tall;
}

- (void)setRich:(BOOL)rich
{
    _tallRichHandsome.rich = rich;
}

- (BOOL)isRich
{
    return !!_tallRichHandsome.rich;//不怎么做的话,赋值1有可能取值的是-1;因为bool返回的是8位,但是现在rich只有1位(ob1),强制转换8位,就会是0b11111111;但是如果在位域中为每个属性2位,就不会有这种情况。
}

- (void)setHandsome:(BOOL)handsome
{
    _tallRichHandsome.handsome = handsome;
}

- (BOOL)isHandsome
{
    return !!_tallRichHandsome.handsome;
}

@end

main.m 类:

#import <Foundation/Foundation.h>
#import "MHYPerson.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MHYPerson *person = [[MHYPerson alloc] init];
        person.tall = NO;
        person.rich = YES;
        person.handsome = YES;
        NSLog(@"tall:%d rich:%d hansome:%d", person.isTall, person.isRich, person.isHandsome);
    }
    return 0;
}

打印结果:

2020-01-08 17:25:55.178796+0800 通过位域添加属性[73471:451017] tall:0 rich:1 hansome:1
Program ended with exit code: 0
(2)、共用体 union
  • union中可以定义多个成员,union的大小由最大的成员的大小决定。
  • union成员共享同一块大小的内存,一次只能使用其中的一个成员。
  • 对某一个成员赋值,会覆盖其他成员的值(也不奇怪,因为他们共享一块内存。但前提是成员所占字节数相同,当成员所占字节数不同时只会覆盖相应字节上的值,比如对char成员赋值就不会把整个int成员覆盖掉,因为char只占一个字节,而int占四个字节)
  • union的存放顺序是所有成员都从低地址开始存放的

使用共用体可以使代码存储数据高效率的同时,有较强的可读性,可以使用共用体来增强代码可读性,同时使用位运算来提高数据存取的效率。

#import <Foundation/Foundation.h>

@interface MHYPerson : NSObject

- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (void)setThin:(BOOL)thin;

- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
- (BOOL)isThin;
@end
#import "MHYPerson.h"

#define MHYTallMask (1<<0)
#define MHYRichMask (1<<1)
#define MHYHandsomeMask (1<<2)
#define MHYThinMask (1<<3)

/*利用位域增加可读性(纯粹摆设),利用位运算进行数据存储*/
/*class、mata-class的地址值,最后三位一定是0*/
@interface MHYPerson()
{
     union {
         char bits; //占一个字节;用来存取数据信息;
         struct {
             char tall : 1;
             char rich : 1;
             char handsome : 1;
             char thin : 1;
         };
     } _tallRichHandsome;
}
@end

@implementation MHYPerson

- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHandsome.bits |= MHYTallMask;
    } else {
        _tallRichHandsome.bits &= ~MHYTallMask;
    }
}

- (BOOL)isTall
{
    return !!(_tallRichHandsome.bits & MHYTallMask);
}

- (void)setRich:(BOOL)rich
{
    if (rich) {
        _tallRichHandsome.bits |= MHYRichMask;
    } else {
        _tallRichHandsome.bits &= ~MHYRichMask;
    }
}

- (BOOL)isRich
{
    return !!(_tallRichHandsome.bits & MHYRichMask);
}

- (void)setHandsome:(BOOL)handsome
{
    if (handsome) {
        _tallRichHandsome.bits |= MHYHandsomeMask;
    } else {
        _tallRichHandsome.bits &= ~MHYHandsomeMask;
    }
}

- (BOOL)isHandsome
{
    return !!(_tallRichHandsome.bits & MHYHandsomeMask);
}



- (void)setThin:(BOOL)thin
{
    if (thin) {
        _tallRichHandsome.bits |= MHYThinMask;
    } else {
        _tallRichHandsome.bits &= ~MHYThinMask;
    }
}

- (BOOL)isThin
{
    return !!(_tallRichHandsome.bits & MHYThinMask);
}

@end

main.m类:

#import <Foundation/Foundation.h>
#import "MHYPerson.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MHYPerson *person = [[MHYPerson alloc] init];
        person.tall = NO;
        person.rich = YES;
        person.handsome = NO;
        person.thin = YES;
        NSLog(@"tall:%d rich:%d hansome:%d thin:%d ",person.isTall, person.isThin, person.isRich, person.isHandsome);
    }
    return 0;
}

打印结果:

2020-01-09 16:38:49.211422+0800 通过共用体添加属性[13128:1190952] tall:0 rich:1 hansome:1 thin:0
Program ended with exit code: 0

通过上述三个例子,我们对位运算、位域、共用体有了一定的了解了,那么我们就回归主题,继续探讨isa指针。


isa指针

从上图我们可以看出,用bits来存储isa指针所有的数据。

下面我们来解析一下位域中的都代表什么: isa指针-位域信息

ISA_MASK 0x0000000ffffffff8ULL

换成二进制:

0b0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111 1000

问题:为什么获取类/元类对象的地址值,为什么要用isa的地址&ISA_MASK((Class)(isa.bits & ISA_MASK)),才能获取类/元类对象的地址。
因为:arm64架构之后,isa指针被优化成了共用体,然后共用体里面的位域(shiftcls占了64位中间的33位)。要想获取shiftcls代表的地址,只能& ISA_MASK(33位1)。

问题:class、mata-class的地址值,最后三位一定是0。
因为:低位的不能消除,必须保留着。
例子:必须创建iOS工程,真机运行。否则不准确,因为mac和模拟器用的是x86_64架构的

    NSLog(@"%p", [ViewController class]);
    NSLog(@"%p", object_getClass([ViewController class]));
    NSLog(@"%p", [MHYPerson class]);
    NSLog(@"%p", object_getClass([MHYPerson class]));

打印结果:

2020-01-09 19:04:12.266 Interview04-Class地址[3291:1331928] 0x100028dc0
2020-01-09 19:04:12.267 Interview04-Class地址[3291:1331928] 0x100028de8
2020-01-09 19:04:12.267 Interview04-Class地址[3291:1331928] 0x100028e88
2020-01-09 19:04:12.267 Interview04-Class地址[3291:1331928] 0x100028e60

一个十六进制位代表4个二进制位。
0x100028dc0 最后一位准成二进制位:0000
0x100028de8最后一位准成二进制位: 1000
可以得出class、mata-class的地址值,最后三位一定是0。

(二)、Class的结构

类的所有方法,属性、成员变量、协议一开始都是存储在class_ro_t里,当我们程序运行起来时,才将分类的信息和我们之前类的信息合并到一起赋值到class_rw_t里面。所以class_rw_t的信息,有一部分是class_ro_t的。所以一开始不存在class_rw_t。

我们知道实例对象想调用对象方法:

  • 通过isa指针找到类对象,去类对象的cache里面看一下有没有方法缓存,由于第一次调用的时候,cache里面是空的(就是没有这个方法),于是就通过bits去class_rw_t里面去找(遍历methods数组去找),如果找到有这个方法,就调用这个方法,并且把这个方法存到自己的cache缓存里面;下次去调用这个方法的时候,直接通过isa指针得到类对象,然后在类对象的cache里面去取,这次找到就直接调用,就不会再通过bits去class_rw_t遍历methods了。
  • 如果对象方法不是在当前类里面(假设在父类/基类里面),通过isa指针找到类对象,去类对象的cache里面看一下有没有方法缓存,但是一开cache里面是空的,就通过bits去class_rw_t里面去找(遍历methods数组去找),还是没找到就通过superclass指针找到父类的类对象,先看父类的cache里面是否有这个方法,如果有就拿过来调用,并且缓存到自己类对象里面(下次就直接取自己类对象里面的对象方法就可以了,就不用再去父类里面找了); 如果没有找到,那就通过父类的bits去class_rw_t里面去找(遍历methods数组去找),如果找到,就调用这个方法,并且缓存到自己的类对象里面,下次调用直接去自己的cache里面去去就行。若果父类的类对象里面还是没有,那就再通过superclass一层一层往上找,直到找到为止。


    Class的结构
(1)、class_rw_t(读写)

class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容


class_rw_t
(2)、class_ro_t(只读)

class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容。


class_ro_t
(2)、method_t
  • 可以通过@selector()和sel_registerName()获得
  • 可以通过sel_getName()和NSStringFromSelector()转成字符串
  • 不同类中相同名字的方法,所对应的方法选择器是相同的
SEL image.jpeg
(3)、Type Encoding
iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码。 Type Encoding
(4)、cache_t(方法缓存)
Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度。(散列表底层就是个数组) cache_t

objc-cache.mm (源码文件)
bucket_t * cache_t::find(cache_key_t k, id receiver

怎么查找呢? 通过key&mask查找(索引)


说明
//
//  HYClassInfo.h
//  方法缓存-cache
//
//  Created by 莫浩洋 on 2020/1/13.
//  Copyright © 2020 mhy. All rights reserved.
//

#ifndef HYClassInfo_h
#define HYClassInfo_h

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;

#if __arm__  ||  __x86_64__  ||  __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;
}

#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(*m***ask_t i, mask_t mask) {
    return i ? i-1 : mask;
}

#else
#error unknown architecture
#endif

struct bucket_t {
    cache_key_t _key;
    IMP _imp;
};

struct cache_t {
    bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
    
    IMP imp(SEL selector)
    {
        mask_t begin = _mask & (long long)selector;
        mask_t i = begin;
        do {
            if (_buckets[i]._key == 0  ||  _buckets[i]._key == (long long)selector) {
                return _buckets[i]._imp;
            }
        } while ((i = cache_next(i, _mask)) != begin);
        return NULL;
    }
};

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
};

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

struct method_list_t : entsize_list_tt {
    method_t first;
};

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    uint32_t alignment_raw;
    uint32_t size;
};

struct ivar_list_t : entsize_list_tt {
    ivar_t first;
};

struct property_t {
    const char *name;
    const char *attributes;
};

struct property_list_t : entsize_list_tt {
    property_t first;
};

struct chained_property_list {
    chained_property_list *next;
    uint32_t count;
    property_t list[0];
};

typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
    uintptr_t count;
    protocol_ref_t list[0];
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  // instance对象占用的内存空间
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;  // 类名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;  // 成员变量列表
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t * methods;    // 方法列表
    property_list_t *properties;    // 属性列表
    const protocol_list_t * protocols;  // 协议列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

#define FAST_DATA_MASK          0x00007ffffffffff8UL
struct class_data_bits_t {
    uintptr_t bits;
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

/* OC对象 */
struct hy_objc_object {
    void *isa;
};

/* 类对象 */
struct hy_objc_class : hy_objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
public:
    class_rw_t* data() {
        return bits.data();
    }
    
    hy_objc_class* metaClass() {
        return (hy_objc_class *)((long long)isa & ISA_MASK);
    }
};

#endif /* HYClassInfo_h */

HYPerson 、HYStudent、HYGoodStudent三个类(HYGoodStudent继承自HYStudent,HYStudent继承自HYPerson)

HYPerson

HYPerson.h

#import <Foundation/Foundation.h>

@interface MJPerson : NSObject
- (void)personTest;
@end


HYPerson.m
#import "HYPerson.h"

@implementation HYPerson
- (void)personTest
{
    NSLog(@"%s", __func__);
}
@end

HYStudent

HYStudent.h
#import "HYPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface HYStudent : HYPerson
- (void)studentTest;
@end

NS_ASSUME_NONNULL_END


HYStudent.m
#import "HYStudent.h"

@implementation HYStudent
- (void)studentTest
{
    NSLog(@"%s", __func__);
}
@end

HYGoodStudent

HYGoodStudent.h

#import "HYStudent.h"

NS_ASSUME_NONNULL_BEGIN

@interface HYGoodStudent : HYStudent
- (void)goodStudentTest;
@end

NS_ASSUME_NONNULL_END


HYGoodStudent.m
#import "HYGoodStudent.h"

@implementation HYGoodStudent
- (void)goodStudentTest
{
    NSLog(@"%s", __func__);
}
@end

main.mm:因为引入了c++文件,所以后缀必须为.mm

#import <Foundation/Foundation.h>
#import "HYGoodStudent.h"
#import "HYClassInfo.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        HYGoodStudent *goodStudent = [[HYGoodStudent alloc] init];
        hy_objc_class *goodStudentClass = (__bridge hy_objc_class *)[HYGoodStudent class];
        
        [goodStudent goodStudentTest];
        [goodStudent studentTest];
        [goodStudent personTest];
        [goodStudent goodStudentTest];
        [goodStudent studentTest];
        
        
        NSLog(@"----------------直接取缓存的方法-----------------------------------");
        
        cache_t cache = goodStudentClass->cache;
        bucket_t *buckets = cache._buckets;
        bucket_t bucket = buckets[(long long)@selector(goodStudentTest) & cache._mask];
        NSLog(@"%s %p", bucket._key, bucket._imp);
        
        
        NSLog(@"------------------这样取出来一定是对的-----------------------");
        NSLog(@"%s %p", @selector(personTest), cache.imp(@selector(personTest)));
        NSLog(@"%s %p", @selector(studentTest), cache.imp(@selector(studentTest)));
        NSLog(@"%s %p", @selector(goodStudentTest), cache.imp(@selector(goodStudentTest)));
    
        
         NSLog(@"----------------------遍历散列表--------------------------");
        for (int i = 0; i<=cache._mask; i++) {
            bucket_t bucket = buckets[I];
            NSLog(@"%s %p", bucket._key, bucket._imp);
        }
    }
    return 0;
}

临界点 扩容后 扩容后 不对应和一定对应

对于init的方法缓存:如果有自己的init方法,调用哪个就缓存自己的init方法,没有则缓存父类的。

上一篇下一篇

猜你喜欢

热点阅读