类的内容

类是个结构体

objc-private.hobjc-runtime-new.h文件中,我们找到了objc_objectobjc_class两个结构体。

struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    ...
}

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
    
    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    ......
}

typedef struct objc_class *Class;
typedef struct objc_object *id;

通过objc_objectobjc_class两个结构体,我们可以看到主要内容有四个

  • Class ISA
  • Class superclass
  • cache_t cache
  • class_data_bits_t bits

ISA是类的关联。
superclass是父类信息的地址指针。

cache_t cache

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic _buckets;
    explicit_atomic _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    ...
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    ...
#else
#error Unknown cache mask storage type.
#endif
    
#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;

public:
    ...
    void insert(Class cls, SEL sel, IMP imp, id receiver);
    ...
};

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 _imp;
    explicit_atomic _sel;
#else
    explicit_atomic _sel;
    explicit_atomic _imp;
#endif
    ...
};

cache_t cache通过备注描述是一个指针的缓存和虚函数表;
包含了一个结构体指针struct bucket_t *_buckets,以及_mask_flags_occupied
而根据struct bucket_t *_buckets中的参数_imp_sel,可以知道其缓存的是方法。

cache_t结构中,发现有一个void insert(Class cls, SEL sel, IMP imp, id receiver);函数,而调用这个函数的Caller则是cache_fill函数。
这里我们不深究调用栈,而是要了解一下cache存储填充逻辑。

void cache_fill(Class cls, SEL sel, IMP imp, id receiver) {
    runtimeLock.assertLocked();

#if !DEBUG_TASK_THREADS
    // Never cache before +initialize is done
    if (cls->isInitialized()) {
        cache_t *cache = getCache(cls);
#if CONFIG_USE_CACHE_LOCK
        mutex_locker_t lock(cacheUpdateLock);
#endif
        cache->insert(cls, sel, imp, receiver);
    }
#else
    _collecting_in_critical();
#endif
}

cache_fill函数中:

  • runtimeLock.assertLocked();开启线程锁;
  • if(cls->isInitialized())如果类没有进行初始化操作,则不能进行相应的缓存操作;
  • cache->insert(cls, sel, imp, receiver);进行缓存操作。
// 对方法进行插入缓存操作
ALWAYS_INLINE
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver) {
#if CONFIG_USE_CACHE_LOCK
    cacheUpdateLock.assertLocked();
#else
    runtimeLock.assertLocked();
#endif

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

    // Use the cache as-is if it is less than 3/4 full
    mask_t newOccupied = occupied() + 1;    // 当前占有量 + 1
    unsigned oldCapacity = capacity(), capacity = oldCapacity;  //获取当前最大容量
    if (slowpath(isConstantEmptyCache())) {
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE;    // 默认INIT_CACHE_SIZE容量
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
    else if (fastpath(newOccupied <= capacity / 4 * 3)) {
        // Cache is less than 3/4 full. Use it as-is.
    }
    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 because the
    // minimum size is 4 and we resized at 3/4 full.
    do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();
            b[i].set(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);
}
// 进行bucket的创建/扩容
ALWAYS_INLINE
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);
    }
}

通过上面的源码,缓存主要有三种情况:

  • if (slowpath(isConstantEmptyCache()))
    如果是一个空的cache,那就创建一个新的桶子bucket_t,然后进行缓存。
  • else if (fastpath(newOccupied <= capacity / 4 * 3))
    如果存储的容量没有超过3/4,不进行其他操作,直接进行缓存。
  • else
    最后,如果存储的容量已经超出3/4,那就创建一个更大(原来的两倍)的新桶子bucket_t,释放掉旧桶子bucket_t,然后进行缓存。

Do-while函数中,
如果在bucket_t中找到空位,则通过b[i].set(sel, imp, cls);bucket_t进行设值。
如果在bucket_t中找到相同的sel,则不进行操作。
最后经过一圈的循环,没有找到符合的bucket_t或者是一个空的bucket_t,则调用bad_cache

class_data_bits_t bits

struct class_data_bits_t {

    // Values are the FAST_ flags above.
    uintptr_t bits;
private:
    bool getBit(uintptr_t bit) {
        return bits & bit;
    }
    ...
}

根据描述中的class_rw_t *,结合源码中的struct class_rw_t结构体,可以看出bits包含了类中的大部分信息,包含了mehtodpropertyprotocol等。

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif
    ...
}

既然觉得bits结构中存在属性,方法,协议列表等,那就需要实际去验证一番。
为了验证,需要先声明一个类,包含了属性,成员变量,实例方法,类方法。

@interface Person : NSObject {
    NSString *hobby;
}

@property (nonatomic, copy) NSString *nickName;

- (void)sayHello;
+ (void)sayHappy;

@end

@implementation Person

- (void)sayHello {
    NSLog(@"-- %s", __func__);
}

+ (void)sayHappy {
    NSLog(@"-- %s", __func__);
}

@end

通过结构体,我们知道ISA占8字节,superclass占8字节,cache占16字节,所以偏移32字节(0x20)后就是bits。

类的结构

由于class_data_bits_t bits并不是对象类型,所以在lldb打印时,需要进行类型转换,并且输出class_rw_t结构中的data内容。

bits内容

在上面打印的结构中,发现了methodspropertiesprotocols三个结构,其中都包含了list_array_tt

template 
class list_array_tt {
    struct array_t {
        uint32_t count;
        List* lists[0];

        static size_t byteSize(uint32_t count) {
            return sizeof(array_t) + count*sizeof(lists[0]);
        }
        size_t byteSize() {
            return byteSize(count);
        }
    };

 protected:
 ...
}
  • 属性

在输出的class_rw_t结构中,按照字面意思,属性应该存储于properties数组中,为了验证,需要进行内容的打印。

properties

  • 成员变量

在上面的properties中,只发现了属性中的nickName,却没有发现对应的成员变量hobby,那成员变量存储在哪里?

发现只有ro是一个未知内容,那么我们就输出看看ro里面是什么内容。

ro.baseProperties

此时,我们发现ro存储了更多的信息,不仅有属性,还有一个ivars
ro.ivars

通过ivars的打印,我们发现了定义好的成员变量hobby,并且发现了数组count = 2,经过继续打印还发现了_nickName,这就印证了系统会根据属性自动生成带下划线(_)的成员变量

  • 实例方法

在探索属性和成员变量的过程中,我们发现在class_rw_t中的ro不仅包含了成员变量ivar,也一样包含了mehtodpropertyprotocol。因此我们直接在ro中探索实例方法。
ro中的baseMethodList中,发现了method_list_t结构,并在里面找到了方法列表,而且count = 4,为此我们逐一打印出来。

实例方法

不仅查找到定义的实例方法,还发现了系统自动为属性生成的gettersetter方法

  • 类方法

查找到了属性,成员变量,实例方法,但是却没有找到定义的类方法。
既然当前类中找不到类方法,我们尝试的在相关联的类中查找。

void testInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    NSLog(@"class_getInstanceMethod");
    NSLog(@"sayHello - %p-%p", method1, method2);
    NSLog(@"sayHappy - %p-%p", method3, method4);
}

void testClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    NSLog(@"class_getClassMethod");
    NSLog(@"sayHello - %p-%p" ,method1, method2);
    NSLog(@"sayHappy - %p-%p" ,method3, method4);
}
类方法
  • class_getInstanceMethod中,
    实例方法(sayHello)在Class中有值,而类方法(sayHappy)在metaClass中有值。
  • class_getClassMethod中,
    实例方法(sayHello)在ClassmetaClass中没有值,而类方法(sayHappy)在ClassmetaClass中有值。

为什么类方法在元类的实例方法和类方法中都有值呢?

/***********************************************************************
* class_getClassMethod.  Return the class method for the specified
* class and selector.
**********************************************************************/
Method class_getClassMethod(Class cls, SEL sel) {
    if (!cls  ||  !sel) return nil;
    return class_getInstanceMethod(cls->getMeta(), sel);
}

struct objc_class : objc_object {
  ...
// NOT identical to this->ISA when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }
  ...
}

class_getClassMethod函数的实现中就能了解问题的所在,最后取得是metaClasssel。因此我们可以猜测其类方法存储于metaClass中。

类方法存在于metaClass

经过lldb的打印,也证实了类方法存在于对应的metaClass

最后

  • 类是一个继承于objc_object的结构体。
  • 类结构中cache_t cache顾名思义就是缓存,缓存的是调用过的方法。
  • 类结构中class_data_bits_t bits包含的属性,成员变量,实例方法,类方法,其中:
    属性存储于ro.baseProperties
    成员变量存储于ro.ivars,且系统根据属性生成的成员变量也存储于此;
    实例方法存储于ro.baseMethodList,且系统根据属性生成的gettersetter也存储于此;
    类方法存储于metaClass中的ro.baseMethodList

你可能感兴趣的:(类的内容)