美文网首页
OC底层原理-cache和bits探索

OC底层原理-cache和bits探索

作者: 卡布奇诺_95d2 | 来源:发表于2021-01-26 18:00 被阅读0次

类的属性存储

之前已经描述了类的存储,类的内存中存储了isa、superclass、cache、bits,前面已经了解了isa和superclass,那cache和bits分别是什么东西呢?接下来,分别对cache、bits进行探索。

cache

cache_t用来快速查找执行函数的一种机制。
我们知道 Objective-C 为了实现其动态性,将函数地址的调用,转变成了通过SEL查找IMP的过程,这带来的后果是降低了方法调用的效率,为了解决这一问题。Objective-C 采用了方法缓存机制来提高效率。

lldb查看cache中的内容

方法:
main 函数定义一个HQPerson对象,并分两个步骤分别调用 init、 instanceMethod方法。通过lldb来观察cache中内容的变化。

//main.m
int main(int argc, const char * argv[]) {
    HQPerson* person = [HQPerson alloc];
    [person init];
    [person instanceMethod];
    return 0;
}
  • 断点1,断在init方法调用之前。查看调用init方法之前cache的内容。
(lldb) x/4g person
0x101105170: 0x011d800100008305 0x0000000000000000
0x101105180: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008305 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x0000000100008300

//偏移16字节,获取cache_t的指针地址,在objc_class结构体中,isa占8字节,superclass占8字节
(lldb) p (cache_t*)($1 + 0x10)
(cache_t *) $2 = 0x0000000100008310
(lldb) p *$2
(cache_t) $3 = {
  _bucketsAndMaybeMask = {
    std::__1::atomic<unsigned long> = {
      Value = 4298437520
    }
  }
   = {
     = {
      _maybeMask = {
        std::__1::atomic<unsigned int> = {
          Value = 0
        }
      }
      _flags = 32820
      _occupied = 0
    }
    _originalPreoptCache = {
      std::__1::atomic<preopt_cache_t *> = {
        Value = 0x0000803400000000
      }
    }
  }
}
//获取cache中的buckets地址
(lldb) p $3.buckets()
(bucket_t *) $4 = 0x000000010034f390

//获取当前bucket的SEL
(lldb) p (*($4+0)).sel()
(SEL) $5 = <no value available>

//获取当前bucket中的IMP
(lldb) p (*($4+0)).imp($4, person.class)
(IMP) $6 = 0x0000000000000000
  • 断点2,断在instanceMethod方法调用之前。查看调用init方法之后cache的内容。
(lldb) p *$2
(cache_t) $7 = {
  _bucketsAndMaybeMask = {
    std::__1::atomic<unsigned long> = {
      Value = 4302567520
    }
  }
   = {
     = {
      _maybeMask = {
        std::__1::atomic<unsigned int> = {
          Value = 7
        }
      }
      _flags = 32820
      _occupied = 1
    }
    _originalPreoptCache = {
      std::__1::atomic<preopt_cache_t *> = {
        Value = 0x0001803400000007
      }
    }
  }
}
(lldb) p $7.buckets()
(bucket_t *) $8 = 0x000000010073f860
(lldb) p (*($8+0)).sel()
(SEL) $9 = <no value available>
(lldb) p (*($8+0)).imp($8, person.class)
(IMP) $10 = 0x000000010033d0c0 (libobjc.A.dylib`-[NSObject respondsToSelector:] at NSObject.mm:2296)
(lldb) p (*($8+1)).sel()
(SEL) $11 = <no value available>
(lldb) p (*($8+1)).imp($8, person.class)
(IMP) $12 = 0x0000000000000000
(lldb) p (*($8+2)).sel()
(SEL) $13 = <no value available>
(lldb) p (*($8+2)).imp($8, person.class)
(IMP) $14 = 0x0000000000000000
(lldb) p (*($8+3)).sel()
(SEL) $15 = "init"
(lldb) p (*($8+3)).imp($8, person.class)
(IMP) $16 = 0x000000010033dd60 (libobjc.A.dylib`-[NSObject init] at NSObject.mm:2557)

以上可以看到,当发送了init的消息之后,cache的buckets中缓存了initimp和sel

  • 断点3,断在 return 之前。查看调用instanceMethod方法之后cache的内容。
(lldb) p *$2
(cache_t) $17 = {
  _bucketsAndMaybeMask = {
    std::__1::atomic<unsigned long> = {
      Value = 4302567520
    }
  }
   = {
     = {
      _maybeMask = {
        std::__1::atomic<unsigned int> = {
          Value = 7
        }
      }
      _flags = 32820
      _occupied = 4
    }
    _originalPreoptCache = {
      std::__1::atomic<preopt_cache_t *> = {
        Value = 0x0004803400000007
      }
    }
  }
}
(lldb) p $17.buckets()
(bucket_t *) $18 = 0x000000010073f860
(lldb) p (*($18+0)).sel()
(SEL) $19 = "respondsToSelector:"
(lldb) p (*($18+0)).imp($18, person.class)
(IMP) $20 = 0x000000010033d0c0 (libobjc.A.dylib`-[NSObject respondsToSelector:] at NSObject.mm:2296)
(lldb) p (*($18+1)).sel()
(SEL) $21 = "instanceMethod"
(lldb) p (*($18+1)).imp($18, person.class)
(IMP) $22 = 0x0000000100003b10 (HQObjc`-[HQPerson instanceMethod])
(lldb) p (*($18+2)).sel()
(SEL) $23 = <no value available>
(lldb) p (*($18+2)).imp($18, person.class)
(IMP) $24 = 0x0000000000000000
(lldb) p (*($18+3)).sel()
(SEL) $25 = "init"
(lldb) p (*($18+3)).imp($18, person.class)
(IMP) $26 = 0x000000010033dd60 (libobjc.A.dylib`-[NSObject init] at NSObject.mm:2557)

以上可以看到,当发送了instanceMethod的消息之后,cache的buckets中缓存了instanceMethodimp和sel

以上通过lldb调试得知cache是用来缓存消息的SEL和IMP。但是如何进行缓存的还得进一步探索。
可在后续消息查找中,进行描述。

探索如何将消息缓存至cache中

当向对象发送消息时,如果未在cache中找到该消息的IMP,则会调用cache::insert接口,将消息的SEL和IMP插入至cache中。

接下来重要分析cache::insert接口,即如何将消息的SEL和IMP插入至 cache 中。

void cache_t::insert(SEL sel, IMP imp, id receiver){
    runtimeLock.assertLocked();

    // Never cache before +initialize is done
    if (slowpath(!cls()->isInitialized())) {
        return;
    }

    if (isConstantOptimizedCache()) {
        _objc_fatal("cache_t::insert() called with a preoptimized cache for %s",
                    cls()->nameForLogging());
    }

#if DEBUG_TASK_THREADS
    return _collecting_in_critical();
#else
#if CONFIG_USE_CACHE_LOCK
    mutex_locker_t lock(cacheUpdateLock);
#endif

    ASSERT(sel != 0 && cls()->isInitialized());

    // Use the cache as-is if until we exceed our expected fill ratio.
    mask_t newOccupied = occupied() + 1;
    unsigned oldCapacity = capacity(), capacity = oldCapacity;
    if (slowpath(isConstantEmptyCache())) {
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE;
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
    else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
        // Cache is less than 3/4 or 7/8 full. Use it as-is.
    }
#if CACHE_ALLOW_FULL_UTILIZATION
    else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
        // Allow 100% cache utilization for small buckets. Use it as-is.
    }
#endif
    else {
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true);
    }

    bucket_t *b = buckets();
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot.
    do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();
            b[i].set<Atomic, Encoded>(b, sel, imp, cls());
            return;
        }
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));

    bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}

该接口主要完成三个功能

  • 获取当前缓存量

  • 根据当前缓存量判断当前缓存内存的情况

    • 当缓存为0时,开辟一段4个bucket大小的内存,缓存量为4
    • 当缓存量大于0小于缓存填充率(75%)时,不对缓存内存操作
    • 当缓存量超过缓存填充率(75%)时,将对当前的缓存进行2倍扩容,扩容后是新的缓存内存地址,旧的缓存会通过垃圾回收机制清理掉。注意:当开辟新的缓存时,不会将旧缓存中的数据拷贝过来,因此,在旧缓存中保存的bucket会被清除。
  • 通过cache_hash方法计算存储bucket的索引,判断当前索引中是否已经有值。

    • 若当前索引有值且不为当前的SEL,则说明hash冲突,则通过cache_next计算下一个索引。
    • 若当前索引无值,则直接向当前索引的数组中插入bucket

探索cache_t中的变量

_bucketsAndMaybeMask:通过cache::insert方法将SEL和IMP存储在buckets数组中,而该变量是指向buckets数组的指针。

_maybeMask:掩码数据,当需要将当前SEL和IMP存储在缓存内存时,需要通过该掩码数据计算hash索引,以及当hash冲突时,需要使用掩码数据计算下一个hash索引。

//计算hash索引
static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
    value ^= value >> 7;
#endif
    return (mask_t)(value & mask);
}

//当发生hash冲突时,计算下一个hash索引
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;
}

_flags:存储一些标志值,具体如下:

// class or superclass has .cxx_construct/.cxx_destruct implementation
//   FAST_CACHE_HAS_CXX_DTOR is the first bit so that setting it in
//   isa_t::has_cxx_dtor is a single bfi
#define FAST_CACHE_HAS_CXX_DTOR       (1<<0)
#define FAST_CACHE_HAS_CXX_CTOR       (1<<1)
// Denormalized RO_META to avoid an indirection
#define FAST_CACHE_META               (1<<2)

// Fast Alloc fields:
//   This stores the word-aligned size of instances + "ALLOC_DELTA16",
//   or 0 if the instance size doesn't fit.
//
//   These bits occupy the same bits than in the instance size, so that
//   the size can be extracted with a simple mask operation.
//
//   FAST_CACHE_ALLOC_MASK16 allows to extract the instance size rounded
//   rounded up to the next 16 byte boundary, which is a fastpath for
//   _objc_rootAllocWithZone()
#define FAST_CACHE_ALLOC_MASK         0x1ff8
#define FAST_CACHE_ALLOC_MASK16       0x1ff0
#define FAST_CACHE_ALLOC_DELTA16      0x0008

// class's instances requires raw isa
#define FAST_CACHE_REQUIRES_RAW_ISA   (1<<13)
// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define FAST_CACHE_HAS_DEFAULT_AWZ    (1<<14)
// class or superclass has default new/self/class/respondsToSelector/isKindOfClass
#define FAST_CACHE_HAS_DEFAULT_CORE   (1<<15)

_occupied:当前缓存中存储的bucket的个数。

  • 当调用实例方法时,_occupied加1;
  • 当调用init方法时,_occupied加1;
  • 当获取属性值或者设置属性值时,_occupied加1;(获取属性值或者设置属性值是向对象发送getter/setter消息)

bits

bitsclass_data_bits_t类型的结构体,其中只有一个成员变量uintptr_t类型的bits。在编译阶段该指针会指向一个class_ro_t结构体,在运行时会封装成class_rw_t结构体。
bits到底是存储了什么呢?接下来我们一步一步分析。

class_ro_t

class_ro_t存储了很多在编译时期就确定的类的信息。其内部包含了类名、ivar、方法、属性等。是只读类型的结构。

struct class_ro_t {
    uint32_t flags; //配合mask 可以用来判断,元类,根类等
    uint32_t instanceStart; //non-fragile判断依据
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    // With ptrauth, this is signed if it points to a small list, but
    // may be unsigned if it points to a big list.
    void *baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
}

接下来我们尝试读取一下class_ro_t的信息。为了验证class_ro_t是在编译时就已经生成的,我们将断点断在_objc_init函数中。由于objc_initruntime入口函数(runtime还没有处理类的元数据)。

  • 根据类在编译时就已经确定其地址,可得到HQPerson类的地址为:0x0000000100008380
  • 由HQPerson类的首地址,偏移32字节,即可得到class_data_bits_t的地址。
(lldb) p/x 0x0000000100008380+0x20
(long) $2 = 0x00000001000083a0
(lldb) p (class_data_bits_t*)$2
(class_data_bits_t *) $3 = 0x00000001000083a0
  • class_data_bits_t结构体,提供data方法获取class_rw_t,由于此时还未对类的元数据进行处理,读取class_rw_t时,会报读取失败的错误,说明class_rw_t是运行时才有内容。将class_rw_t结构体强转为class_ro_t结构体,再读取其中内容时,发现是可以读取的,因此可以验证class_ro_t结构体是在编译时就生成的。
(lldb) p $3->data()
(class_rw_t *) $4 = 0x00000001000080f8

//由于此时还未处理类的元数据,因此无法获取class_rw_t结构体中内容
(lldb) p $4.methods()
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x48).
The process has been returned to the state before expression evaluation.

//将class_rw_t结构体强转成class_ro_t
(lldb) p (class_ro_t*)$4
(class_ro_t *) $5 = 0x00000001000080f8

//读取class_ro_t的内容
(lldb) p *$5
(class_ro_t) $6 = {
  flags = 388
  instanceStart = 8
  instanceSize = 40
  reserved = 0
   = {
    ivarLayout = 0x0000000100003f77 "\x04"
    nonMetaclass = 0x0000000100003f77
  }
  name = {
    std::__1::atomic<const char *> = "HQPerson" {
      Value = 0x0000000100003f6e "HQPerson"
    }
  }
  //类的实例方法存储地址
  baseMethodList = 0x0000000100008140
  //类的协议存储地址
  baseProtocols = 0x0000000000000000
  //成员变量存储地址
  ivars = 0x0000000100008238
  weakIvarLayout = 0x0000000000000000
  //类的属性存储地址
  baseProperties = 0x00000001000082c0
  _swiftMetadataInitializer_NEVER_USE = {}
}
  • 既然class_ro_t结构体在编译时已经确定类的信息,那使用lldb来将这些类的信息打印出来看看
//验证存储实例方法的地址中是否存储了实例方法
(lldb) p $6.baseMethodList
(void *) $7 = 0x0000000100008140
(lldb) p (method_list_t*)$7
(method_list_t *) $8 = 0x0000000100008140
(lldb) p ($8->get(0)).big()
(method_t::big) $9 = {
  name = "instanceMethod"
  types = 0x0000000100003f79 "v16@0:8"
  imp = 0x00000001000039a0 (HQObjc`-[HQPerson instanceMethod])
}
(lldb) p ($8->get(1)).big()
(method_t::big) $10 = {
  name = "instanceMethod1"
  types = 0x0000000100003f79 "v16@0:8"
  imp = 0x00000001000039d0 (HQObjc`-[HQPerson instanceMethod1])
}

//验证存储属性的地址中是否存储了属性
(lldb) p $6.baseProperties
(property_list_t *) $12 = 0x00000001000082c0
(lldb) p $12->get(0)
(property_t) $13 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $12->get(1)
(property_t) $14 = (name = "propertyName", attributes = "T@\"NSString\",C,N,V_propertyName")

//验证存储成员变量的地址中是否存储了成员变量
(lldb) p $6.ivars
(const ivar_list_t *) $15 = 0x0000000100008238
(lldb) p $15->get(0)
(ivar_t) $16 = {
  offset = 0x0000000100008338
  name = 0x0000000100003d51 "hobby"
  type = 0x0000000100003f81 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $15->get(1)
(ivar_t) $17 = {
  offset = 0x0000000100008340
  name = 0x0000000100003d57 "memberHobby"
  type = 0x0000000100003f81 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $15->get(2)
(ivar_t) $18 = {
  offset = 0x0000000100008348
  name = 0x0000000100003d63 "_name"
  type = 0x0000000100003f81 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $15->get(3)
(ivar_t) $19 = {
  offset = 0x0000000100008350
  name = 0x0000000100003d69 "_propertyName"
  type = 0x0000000100003f81 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
总结
  • class_ro_t结构体是用来存储类的相关信息,如类名、变量、属性、实例方法等;
  • class_ro_t结构体在编译时期已经存储了类的信息。
  • class_ro_t结构体的内容是只读的。

class_rw_t

从上面的章节看出class_ro_t存储的大多是类在编译时就已经确定的信息,但是Objective-C又是一门动态语言,因此需要另一个可以运行时读写数据,也就是class_rw_t

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;
}

接下来我们尝试读取一下class_rw_t的信息。由于class_rw_t是在运行时生成的,因此,需要将objc_init的断点过掉。

  • 将断点断在[HQPerson alloc]之后。通过类的首地址,偏移32字节得到class_data_bits_t的地址。
(lldb) p/x 0x0000000100008380+0x20
(long) $2 = 0x00000001000083a0
(lldb) p (class_data_bits_t*)$2
(class_data_bits_t *) $3 = 0x00000001000083a0
  • 通过class_data_bits_t结构体中的data方法,获取class_rw_t结构体指针
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101998be0
  • 读取class_rw_t结构体中的实例方法(methods)属性(properties),并与class_ro_t结构中的baseMethodListivarsbaseProperties进行比较。注意:由于class_rw_t是在运行时生成的,而成员变量是无法在运行进行添加的,因此在class_rw_t结构体中没有成员变量。
//获取class_rw_t中的实例方法(methods)
(lldb) p $3->methods()
(const method_array_t) $4 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008140    //与class_ro_t中的baseMethodList = 0x0000000100008140相同
      }
      arrayAndFlag = 4295000384
    }
  }
}
(lldb) p (method_list_t*)0x0000000100008140
(method_list_t *) $5 = 0x0000000100008140
(lldb) p ($5->get(0).big())
(method_t::big) $6 = {
  name = "instanceMethod"
  types = 0x0000000100003f79 "v16@0:8"
  imp = 0x00000001000039a0 (HQObjc`-[HQPerson instanceMethod])
}
(lldb) p ($5->get(1).big())
(method_t::big) $7 = {
  name = "instanceMethod1"
  types = 0x0000000100003f79 "v16@0:8"
  imp = 0x00000001000039d0 (HQObjc`-[HQPerson instanceMethod1])
}

//获取class_rw_t中的属性(properties)
(lldb) p $3->properties()
(const property_array_t) $8 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x00000001000082c0    //与class_ro_t中的baseProperties = 0x00000001000082c0相同
      }
      arrayAndFlag = 4295000768
    }
  }
}
(lldb) p (property_list_t*)0x00000001000082c0
(property_list_t *) $9 = 0x00000001000082c0
(lldb) p $9->get(0)
(property_t) $10 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $9->get(1)
(property_t) $11 = (name = "propertyName", attributes = "T@\"NSString\",C,N,V_propertyName")
动态的增加方法后查看rw、ro的变化情况

在HQPerson中,通过动态方法解析的函数来为当前类增加一个方法。

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%@ 来了", NSStringFromSelector(sel));
    if (sel == @selector(say666)) {
        //获取instanceMethods方法的imp
        IMP imp = class_getMethodImplementation(self, @selector(instanceMethods));
        //获取instanceMethods的实例方法
        Method sayMethod  = class_getInstanceMethod(self, @selector(instanceMethods));
        //获取instanceMethods的丰富签名
        const char *type = method_getTypeEncoding(sayMethod);
        //将sel的实现指向sayMaster
        class_addMethod(self, sel, imp, type);
        return NO;
    }
    
    return NO;
}
  • 【步骤1】在class_addMethod方法放置断点,查看当前未添加方法时rw、ro的情况。
(lldb) p/x person.class
(Class) $0 = 0x0000000100008248 HQPerson
(lldb) p/x 0x0000000100008248+0x20
(long) $1 = 0x0000000100008268
(lldb) p (class_data_bits_t*)$1
(class_data_bits_t *) $2 = 0x0000000100008268
(lldb) p $2->safe_ro()
(const class_ro_t *) $3 = 0x00000001000080e8
(lldb) p *$3
(const class_ro_t) $4 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
   = {
    ivarLayout = 0x0000000100003f63 "\x02"
    nonMetaclass = 0x0000000100003f63
  }
  name = {
    std::__1::atomic<const char *> = "HQPerson" {
      Value = 0x0000000100003f5a "HQPerson"
    }
  }
  baseMethodList = 0x0000000100008130
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100008198
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000081e0
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $2->data()
(class_rw_t *) $5 = 0x0000000101b04190
(lldb) p $5->methods()
(const method_array_t) $6 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008130
      }
      arrayAndFlag = 4295000368
    }
  }
}
(lldb) p $6.beginLists()
(const method_list_t_authed_ptr<method_list_t> *) $7 = 0x00000001005db7c0
(lldb) x/4g 0x00000001005db7c0
0x1005db7c0: 0x0000000100008130 0x0000000000000000
0x1005db7d0: 0x0000000000000000 0x0000000000000000

此时可以看出,robaseMethodList = 0x0000000100008130rwptr = 0x0000000100008130是一致的。

rw的结构体中,暂时只存储了一个方法列表的地址,即baseMethodList

  • 跳过断点,向HQPerson类中添加say666的方法,此时再来观察一下ro、rw的情况
(lldb) p $2->safe_ro()
(const class_ro_t *) $3 = 0x00000001000080e8
(lldb) p *$3
(const class_ro_t) $4 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
   = {
    ivarLayout = 0x0000000100003f63 "\x02"
    nonMetaclass = 0x0000000100003f63
  }
  name = {
    std::__1::atomic<const char *> = "HQPerson" {
      Value = 0x0000000100003f5a "HQPerson"
    }
  }
  baseMethodList = 0x0000000100008130
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100008198
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000081e0
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $2->data()
(class_rw_t *) $14 = 0x0000000101b04190
(lldb) p $14->methods()
(const method_array_t) $15 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000101a562e1
      }
      arrayAndFlag = 4322583265
    }
  }
}
(lldb) p $15.beginLists()
(const method_list_t_authed_ptr<method_list_t> *) $16 = 0x0000000101a562e8
(lldb) x/4g 0x0000000101a562e8
0x101a562e8: 0x0000000101a56290 0x0000000100008130
0x101a562f8: 0x0000000000000000 0x0000000000000000
(lldb) p (method_list_t*)0x0000000101a56290
(method_list_t *) $24 = 0x0000000101a56290
(lldb) p $24->get(0).big()
(method_t::big) $27 = {
  name = "say666"
  types = 0x0000000100003f65 "v16@0:8"
  imp = 0x0000000100003cb0 (HQObjc`-[HQPerson instanceMethods] at HQPerson.m:13)
}

对比未添加方法之前,ro不发生变化。
添加方法之后,rw的中保存着两个方法列表的地址。其中一个地址是baseMethodList,另一个地址是存储了动态添加方法的方法列表地址。

总结

class_rw_t结构体是在运行时用来存储类的相关信息,如类名、属性、实例方法等。
class_rw_t结构体中没有成员变量。
class_rw_t结构体的内容是可读写的。在动态添加方法class_rw_t结构体会动态的添加方法列表,用于存储动态添加的方法

相关文章

网友评论

      本文标题:OC底层原理-cache和bits探索

      本文链接:https://www.haomeiwen.com/subject/mwwgzktx.html