cache 原理分析 - [objc_class] objc4-818.2

177 阅读2分钟

cache_t 数据结构

struct cache_t {
    /**
     explicit_atomic 显示原子性,目的是为了能够 保证 增删改查时 线程的安全性 等价于 struct bucket_t * _buckets; 
     _buckets 中放的是 sel imp
     _buckets的读取 有提供相应名称的方法 buckets()
    */
   explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
   
  // 省略代码。。。。。。。。。
    mask_t mask() const;
 // 省略代码。。。。。。。。。
    void incrementOccupied();
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);


public:
     // 省略代码。。。。。。。。。
     
    //_buckets的读取 有提供相应名称的方法 buckets()
    struct bucket_t *buckets() const; //_buckets 中放的是 sel imp
    Class cls() const;
    mask_t occupied() const;
    // 省略代码。。。。。。。。。
    void insert(SEL sel, IMP imp, id receiver);
};

bucket_t 数据结构

struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    explicit_atomic<uintptr_t> _imp; //explicit_atomic 是加了原子性的保护
    explicit_atomic<SEL> _sel;
#else
    explicit_atomic<SEL> _sel;
    explicit_atomic<uintptr_t> _imp;
#endif
//省略代码。。。。。
}

通过lldb分析

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;
@property (nonatomic, strong) NSString *hobby;

- (void)saySomething;

@end

Person *p  = [Person alloc];
Class pClass = [Person class];
(lldb) p/x pClass
(Class) $2 = 0x0000000100008428 Person
(lldb) p (cache_t *)0x0000000100008438 //cache属性的获取,需要通过pclass的首地址平移16字节,即首地址+0x10获取cache的地址
(cache_t *) $3 = 0x0000000100008438
(lldb) p *$3
(cache_t) $4 = {
  _bucketsAndMaybeMask = {
    std::__1::atomic<unsigned long> = {
      Value = 4298515392
    }
  }
   = {
     = {
      _maybeMask = {
        std::__1::atomic<unsigned int> = {
          Value = 0
        }
      }
      _flags = 32808
      _occupied = 0
    }
    _originalPreoptCache = {
      std::__1::atomic<preopt_cache_t *> = {
        Value = 0x0000802800000000
      }
    }
  }
}

(lldb) p $4.buckets() //从源码的分析中,我们知道sel-imp是在cache_t的_buckets属性中(目前处于macOS环境),而在cache_t结构体中提供了获取_buckets属性的方法buckets()
(bucket_t *) $5 = 0x00000001003623c0
(lldb) p *$5
(bucket_t) $6 = {
  _sel = {
    std::__1::atomic<objc_selector *> = (null) {
      Value = nil
    }
  }
  _imp = {
    std::__1::atomic<unsigned long> = {
      Value = 0
    }
  }
}

(lldb) p [p saySomething]
 -[Person saySomething]
(lldb) p $4.buckets()
(bucket_t *) $10 = 0x000000010143ae80
(lldb) p  *$10
(bucket_t) $11 = {
  _sel = {
    std::__1::atomic<objc_selector *> = (null) {
      Value = nil
    }
  }
  _imp = {
    std::__1::atomic<unsigned long> = {
      Value = 0
    }
  }
}
(lldb) p $4.buckets()[1]
(bucket_t) $12 = {
  _sel = {
    std::__1::atomic<objc_selector *> = "" {
      Value = ""
    }
  }
  _imp = {
    std::__1::atomic<unsigned long> = {
      Value = 47128
    }
  }
}
(lldb) p $12.sel()
(SEL) $13 = "saySomething"
(lldb) p $12.imp(nil,pClass)
(IMP) $14 = 0x0000000100003c30 (ObjcBuild`-[Person saySomething])
(lldb) p $4.buckets()[2]
(bucket_t) $15 = {
  _sel = {
    std::__1::atomic<objc_selector *> = (null) {
      Value = nil
    }
  }
  _imp = {
    std::__1::atomic<unsigned long> = {
      Value = 0
    }
  }
}
(lldb) 

由上可知,在没有执行方法调用时,此时的cache是没有缓存的,执行了一次方法调用,cache中就有了一个缓存,即调用一次方法就会缓存一次方法。

脱离源码分析

typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

struct ypy_bucket_t {
    SEL _sel;
    IMP _imp;
};
struct ypy_cache_t {
    struct ypy_bucket_t *_bukets; // 8
    mask_t    _maybeMask; // 4
    uint16_t  _flags;  // 2
    uint16_t  _occupied; // 2
};

struct ypy_class_data_bits_t {
    uintptr_t bits;
};

// cache class
struct ypy_objc_class {
    Class isa;
    Class superclass;
    struct ypy_cache_t cache;             // formerly cache pointer and vtable
    struct ypy_class_data_bits_t bits;
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p  = [[Person alloc] init];
        Class pClass = p.class;  // objc_clas
//        [p say1];
//        [p say2];
//        [p say3];
//        [p say4];
//        [p say1];
//        [p say2];
//        [p say3];

        [pClass sayHappy];
        struct ypy_objc_class *ypy_class = (__bridge struct ypy_objc_class *)(pClass);
        NSLog(@"%hu - %u",ypy_class->cache._occupied,ypy_class->cache._maybeMask);
        for (mask_t i = 0; i<ypy_class->cache._maybeMask; i++) {
            struct ypy_bucket_t bucket = ypy_class->cache._bukets[i];
            NSLog(@"%@ - %pf",NSStringFromSelector(bucket._sel),bucket._imp);
        }
        NSLog(@"Hello, World!");
    }
    return 0;
}

示例一 1.png 示例二 2.png 示例三 3.png 示例四 4.png

针对上面的打印结果,有以下几点疑问

  • 1、_mask是什么?
  • 2、_occupied 是什么?
  • 3、为什么随着方法调用的增多,其打印的occupied 和 mask会变化
  • 4、bucket数据为什么会有丢失的情况?,例如示例三中,只有say1、say2方法有函数指针
  • 5、方法的打印是有序的么?
  • 6、init 为何会打印出来 带着上述的这些疑问,下面来进行cache底层原理的探索