底层

iOS-OC底层06:cache方法缓存

2020-09-18  本文已影响0人  MonKey_Money

前沿

对象的底层实现是结构体objc_object,类的底层实现是结构体objc_class,因为objc_class继承自objc_object(c++中结构体是可以继承的),所以类也是特殊的对象。objc_object有成员变量isa,内部实现通过一个共同体isa_t,isa_t下有一个结构体,里面保存对象所属的类等等信息在底层03可以详细了解。在04可以了解方法属性等信息的详细情况,在这篇中我们详细了解一下,方法缓存。

通过内存偏移访问cache内存

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
}
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

我们可以通过类的首地址偏移16字节访问到objc_class的cache成员。为什么是16字节呢?我们可以看到cache前面有变量superclass占8字节,但是objc_class继承自objc_object,objc_object有一个成员变量isa也是占8字节。
既然研究类的方法缓存,所以我们先声明一个类

@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;
- (void)sayHello;
- (void)sayCode;
- (void)sayMaster;
- (void)sayNB;
+ (void)sayHappy;
@end
@implementation LGPerson
- (void)sayHello{
    NSLog(@"LGPerson say : %s",__func__);
}
- (void)sayCode{
    NSLog(@"LGPerson say : %s",__func__);
}
- (void)sayMaster{
    NSLog(@"LGPerson say : %s",__func__);
}
- (void)sayNB{
    NSLog(@"LGPerson say : %s",__func__);
}
+ (void)sayHappy{
    NSLog(@"LGPerson say : %s",__func__);
}
@end
//创建对象
        LGPerson *p  = [LGPerson alloc];
        Class pClass = [LGPerson class];
//        p.lgName     = @"Cooci";
//        p.nickName   = @"KC";
        // 缓存一次方法 sayHello
        // 4
        [p sayHello];
        [p sayCode];
        [p sayMaster];

我们通过内存偏移访问cache

(lldb) p/x [LGPerson class]
(Class) $0 = 0x0000000100002298 LGPerson
//0x0000000100002298偏移16字节变为0x00000001000022a8,因
//为0x00000001000022a8是cache_t类型,类型转换打印结果。
(lldb) p (cache_t*)0x00000001000022a8
(cache_t *) $1 = 0x00000001000022a8

我们在调用实例方法之前和之后打印cache_t信息,

//调用实例方法之前
(lldb) p *$0 
(cache_t) $1 = {
  _buckets = {
    std::__1::atomic<bucket_t *> = 0x000000010032e410 {
      _sel = {
        std::__1::atomic<objc_selector *> = (null)
      }
      _imp = {
        std::__1::atomic<unsigned long> = 0
      }
    }
  }
  _mask = {
    std::__1::atomic<unsigned int> = 0
  }
  _flags = 32804
  _occupied = 0
}
//调用实例方法之后
(lldb) p *$0 
(cache_t) $2 = {
  _buckets = {
    std::__1::atomic<bucket_t *> = 0x000000010075f880 {
      _sel = {
        std::__1::atomic<objc_selector *> = ""
      }
      _imp = {
        std::__1::atomic<unsigned long> = 11928
      }
    }
  }
  _mask = {
    std::__1::atomic<unsigned int> = 3
  }
  _flags = 32804
  _occupied = 1
}

可知_occupied前后方法方法了变化。
分析我们在源码中全局搜索_occupied,发现只有在incrementOccupied中有对_occupied的++,我们在全局搜索incrementOccupied,发现只有void cache_t::insert调用,我们通过打断点可知当方法调用的时候会调用cache_t::insert。

探究cache_t::insert

开辟内存的探究

在cache_t::insert中,我们通过阅读代码可知在下面代码是对开辟内存的处理

    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 <= capacity / 4 * 3)) { // 4  3 + 1 bucket cache_t
        // Cache is less than 3/4 full. Use it as-is.
    }
    else {
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;  // 扩容两倍 4
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true);  // 内存 库容完毕
    }

1.第一次调用实例方法
第一次调用实例方法会有第一个if,capacity = INIT_CACHE_SIZE,设置capacity为4。
2.不重新分配内存的情况
当新的占位标记加上最后空留的标记1小于总分配的空间的4分子3时不做处理
3.需要重新开辟内存
当不满足1,2时,需要扩容。当capacity不为空时,扩容为capacity的二倍,当capacity为空时,扩容为4.扩容不是无限制的,最大扩容为2的16次方就是65536,当扩容大于65536时,设置扩容为65536。

reallocate探究

void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
    bucket_t *oldBuckets = buckets();
    bucket_t *newBuckets = allocateBuckets(newCapacity);

    // Cache's old contents are not propagated. 
    // This is thought to save cache memory at the cost of extra cache fills.
    // fixme re-measure this

    ASSERT(newCapacity > 0);
    ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);

    setBucketsAndMask(newBuckets, newCapacity - 1);
    
    if (freeOld) {
        cache_collect_free(oldBuckets, oldCapacity);
    }
}

1.获取之前的buckets()
2.分配新的内存allocateBuckets

bucket_t *allocateBuckets(mask_t newCapacity)
{
    bucket_t *newBuckets = (bucket_t *)
        calloc(cache_t::bytesForCapacity(newCapacity), 1);

    bucket_t *end = cache_t::endMarker(newBuckets, newCapacity);
#if __arm__
    // End marker's sel is 1 and imp points BEFORE the first bucket.
    // This saves an instruction in objc_msgSend.
    end->set<NotAtomic, Raw>((SEL)(uintptr_t)1, (IMP)(newBuckets - 1), nil);
#else
    // End marker's sel is 1 and imp points to the first bucket.
    end->set<NotAtomic, Raw>((SEL)(uintptr_t)1, (IMP)newBuckets, nil);
#endif
    if (PrintCaches) recordNewCache(newCapacity);
    return newBuckets;
}

分配新空间的内存,最后的一个位置设置标记。
3.设置Buckets和Mask setBucketsAndMask
用新的值覆盖旧的值
4.垃圾回收cache_collect_free

static void cache_collect_free(bucket_t *data, mask_t capacity)
{
#if CONFIG_USE_CACHE_LOCK
    cacheUpdateLock.assertLocked();
#else
    runtimeLock.assertLocked();
#endif

    if (PrintCaches) recordDeadCache(capacity);

    _garbage_make_room ();
    garbage_byte_size += cache_t::bytesForCapacity(capacity);
    garbage_refs[garbage_count++] = data;
    cache_collect(false);
}

_garbage_make_room:确定有足够的内存保存需要释放的对象
garbage_byte_size:计算一共释放的内存大小。
garbage_refs[garbage_count++] = data;把要释放的对象保存起来
cache_collect(false);在合适的时候释放,false不会立即释放

方法存到缓存

写入的代码如下

    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 because the
    // minimum size is 4 and we resized at 3/4 full.
    do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();
            b[i].set<Atomic, Encoded>(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));

    cache_t::bad_cache(receiver, (SEL)sel, cls);

1.获取bucket数组:bucket_t *b = buckets();
2.获取哈希后的值:通过mask对方法sel进行哈希得出begin
3.写入bucket数组中
写入到bucket数组是一个循环,我们先判断bucket数组中begin位置的bucket_t的sel方法是不是为0,如果是0,则写入到该位置的bucket_t中并返回,如果该位置的sel方法等于我们要写入的sel方法就不用重新写入并返回。如果都不满足前面两种情况,则对i值进行再hash进行下一个循环。

因为hash的结果没有规律,所以方法写入到缓存的位置也没有规律.
因为会存在扩容,所以扩容前写入到缓存的方法会被释放.

cache的流程图如下


Cooci 关于Cache_t原理分析图.png
上一篇下一篇

猜你喜欢

热点阅读