iOS-类结构(下)

在iOS-isa指向图&类结构中提到了类的结构,并分析了isabits中的内容,superclass结构与isa相同,就不多做分析了,今天我们就来看看cache里都有些什么有意思的东西吧。

类结构图

其实按照咱们之前的知识栈,其实很容易猜到,这里就是存储方法列表的嘛,嗯,很简单嘛~ 真的简单吗?我们先看看他的数据结构吧。

cache_t数据结构

可以看到其中用了联合体union,union相关可以看看iOS-对象的本质,ISA分析
,由此我们可以知道,cache_t会占用16字节。其实细心的朋友会发现上面的图并不是cache_t的全部内容,其后还有类似下图的内容
image.png

后面还有这么多变量你怎么就算出cache_t只占用16字节呢,其实这是变量都是static修饰,会存到全局区,不会增大cache_t的大小。

接下来看看cache_t里的方法,会发现insert方法,按照cache_t缓存方法列表的作用,大致能猜到这个方法就是将新数据插入列表的方法。

cache_t部分方法

看看insert的实现吧


insert实现

会发现里面用到bucket_t这个结构体,并且对他进行一些操作,猜测这是一个很重要的结构,来康康吧。

image.png

嘿嘿嘿,终于看到了熟悉的东西——selimp。显然咱们的缓存方法列表得到印证。且是借助bucket_t这一结构体缓存的。

接下来我们通过lldb来验证一下:
1.断点执行到下图:

image.png

2.通过lldb调试:

(lldb) p/x pclass //拿到类对象
(Class) $0 = 0x0000000100008930 LGPerson
(lldb) p (cache_t *)0x0000000100008940//平移16字节,因为cache_t前面有isa和superclass
(cache_t *) $1 = 0x0000000100008940
(lldb) p *$1//输出cache_t
(cache_t) $2 = {
  _bucketsAndMaybeMask = {
    std::__1::atomic = {
      Value = 4301517904
    }
  }
   = {
     = {
      _maybeMask = {
        std::__1::atomic = {
          Value = 3
        }
      }
      _flags = 32816
      _occupied = 1
    }
    _originalPreoptCache = {
      std::__1::atomic = {
        Value = 0x0001803000000003
      }
    }
  }
}
(lldb) p $2.buckets()//分析源码,找到buckets()方法获取buckets
(bucket_t *) $3 = 0x000000010063f450
(lldb) p *$3//获取第一个bucket,发现是空的。
(bucket_t) $4 = {
  _sel = {
    std::__1::atomic = (null) {
      Value = (null)
    }
  }
  _imp = {
    std::__1::atomic = {
      Value = 0
    }
  }
}
(lldb) p $2.buckets()[1]//获取第二个bucket,找到了已执行过的say1,注意buckets是结构体,不是数组,那为什么可以用下标取值呢,其实这里是取内存平移,[1]相当于平移1个单位
(bucket_t) $5 = {
  _sel = {
    std::__1::atomic = "" {
      Value = ""
    }
  }
  _imp = {
    std::__1::atomic = {
      Value = 45088
    }
  }
}
(lldb) p $5.sel()
(SEL) $6 = "say1"

到这虽然我们找到了say1的缓存,但为什么他没有存在第一个位置呢???嘤嘤嘤?

又没办法偷懒了,只有看看insert方法是咋把他存进去的了。

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;//occupied()直接return _occupied
    unsigned oldCapacity = capacity(), capacity = oldCapacity;//capacity()直接return mask() ? mask()+1 : 0;
    if (slowpath(isConstantEmptyCache())) {//判断缓存是否为空,相当于第一次进来
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE;//INIT_CACHE_SIZE 为 4,由此可见初始化分配4个存储空间
        reallocate(oldCapacity, capacity, /* freeOld */false);//分配空间,初始化_occupied为0,设置_bucketsAndMaybeMask中MaybeMask为capacity-1
    }
    else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {//判断空间是否占用超过3/4,没超过则不处理
        // 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 {//超过3/4则进行扩容,直接2倍扩容
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true);//最后一个参数为true,表示需要释放旧的内存,说明每次扩容都会重新分配内存,并释放旧内存,
    }
//以上是计算要分配的空间,下面就是插入的逻辑啦
    bucket_t *b = buckets();//拿到bucket_t
    mask_t m = capacity - 1;//这里也是maybemask,是存在_bucketsAndMaybeMask中的
    mask_t begin = cache_hash(sel, m);//通过hash计算存储位置,
    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(b, sel, imp, cls());//插入数据到i,也就是begin
            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
}

有了源码,就是yyds,可以看到

    mask_t begin = cache_hash(sel, m);//通过hash计算存储位置,

原来是通过hash来计算下标,难怪不是从第一个位置存储的。

关键属性解释
1、_mask是什么?

_mask是指掩码数据,用于在哈希算法或者哈希冲突算法中计算哈希下标,其中mask 等于capacity - 1

2、_occupied 是什么?

_occupied表示哈希表中 sel-imp 的占用大小 (即可以理解为分配的内存中已经存储了sel-imp的的个数),

init会导致occupied变化

属性赋值,也会隐式调用,导致occupied变化

方法调用,导致occupied变化

你可能感兴趣的:(iOS-类结构(下))