美文网首页
runtime(二)

runtime(二)

作者: dandelionYD | 来源:发表于2019-03-29 20:57 被阅读0次

Class的结构

runtime的底层代码可详见:配置objc4-750

本文Demo代码见gitHubDemo

下面我们根据里面的代码分析的


runtime_03.png runtime_04.png
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        SEL sel1 = @selector(test);
        SEL sel2 = sel_registerName("test");
        NSLog(@"%p",sel1);
        NSLog(@"%p",sel2);
        NSLog(@"%p",sel_registerName("test"));
    }
    return 0;
}
打印:
05.SEL[39903:12173170] 0x7fff4b57ffdf
05.SEL[39903:12173170] 0x7fff4b57ffdf
05.SEL[39903:12173170] 0x7fff4b57ffdf
三个地址是一样的哟

Type Encoding

runtime_05.png
myPerson.h
#import <Foundation/Foundation.h>
@interface myPerson : NSObject
- (int)test:(int)age height:(float)height;
@end

myPerson.m
#import "myPerson.h"
@implementation myPerson
- (int)test:(int)age height:(float)height{
    NSLog(@"%s", __func__);
    return 0;
}
@end

【main】
#import <Foundation/Foundation.h>
#import "myPerson.h"
#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {        
        NSLog(@"%s", method_getTypeEncoding(class_getInstanceMethod([myPerson class], @selector(test:height:))));
    }
    return 0;
}

打印:
05.SEL[43843:12233135] i24@0:8i16f20

分析:
"i  24  @0 :8  i16  f20"  
i->int (返回值类型)
24->总共24个字节 (整个方法参数占位的总长度)
@0  0 -> 从第0个字节开始  (表示在offset为0的地方有一个objective-c的对象,在objective-c method里,首个对象是self自身)
:8 ->从第8个字节开始(在offset为8的地方有一个SEL)
i->int    16->从第16个字节开始 (第一个参数的位置)
f->float  20->从第20个字节开始 (第二个参数的位置)

方法缓存

Class内部结构中会有一个方法缓存(cache_t),用散列表(哈希表)来缓存曾经使用过的方法,可以提高方法的查找速度

objc-runtime-new.h

runtime_06.png

下面我们根据objc-runtime-new.hobjc-cache.mm重新构造类信息,如下

myClassInfo.h

#ifndef myClassInfo_h
#define myClassInfo_h

//isa.h LINE-57
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif


//objc-runtime-new.h  LINE-27
#if __LP64__
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;


//objc-runtime-new.h  LINE-505
#define FAST_DATA_MASK          0x00007ffffffffff8UL

//objc-runtime-new.h  LINE-37
struct bucket_t {
public:
    // 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__
    MethodCacheIMP _imp;
    cache_key_t _key;
#else
    cache_key_t _key;
    IMP _imp;
#endif
};

//objc-cache.mm LINE-155
#if __arm__  ||  __x86_64__  ||  __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;
}

#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}

#else
#error unknown architecture
#endif


/*   objc-runtime-new.h  LINE-524
 bucket_t * cache_t::find(cache_key_t k, id receiver)
 {
 assert(k != 0);
 
 bucket_t *b = buckets();
 mask_t m = mask();
 mask_t begin = cache_hash(k, m);
 mask_t i = begin;
 do {
 if (b[i].key() == 0  ||  b[i].key() == k) {
 return &b[I];
 }
 } while ((i = cache_next(i, m)) != begin);
 
 // hack
 Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
 cache_t::bad_cache(receiver, (SEL)k, cls);
 }
 */

//objc-runtime-new.h  LINE-59
struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
    
    IMP imp(SEL selector)
    {
        mask_t begin = _mask & (long long)selector;
        mask_t i = begin;
        do {
            if (_buckets[i]._key == 0  ||  _buckets[i]._key == (long long)selector) {
                return _buckets[i]._imp;
            }
        } while ((i = cache_next(i, _mask)) != begin);
        return NULL;
    }
};

/* OC对象 */
struct my_objc_object {
    void *isa;
};

//objc-runtime-new.h  LINE-222
struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

//objc-runtime-new.h  LINE-100
struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
};

//objc-runtime-new.h  LINE-265
struct method_list_t : entsize_list_tt{
    bool isFixedUp() const;
    void setFixedUp();
};


//objc-runtime-new.h  LINE-287
typedef uintptr_t protocol_ref_t;  // protocol_t *, but unremapped


//objc-runtime-new.h  LINE-343
struct protocol_list_t {
    // count is 64-bit by accident.
    uintptr_t count;
    protocol_ref_t list[0]; // variable-size
};


struct property_list_t : entsize_list_tt{
};


//objc-runtime-new.h  LINE-277
struct ivar_list_t : entsize_list_tt {
   
};


//objc-runtime-new.h  LINE-553
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;

    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

//objc-runtime-new.h  LINE-802
class property_array_t{
};


//objc-runtime-new.h  LINE-826
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_list_t *methods;
    property_array_t properties;
    protocol_list_t *protocols;
    
    Class firstSubclass;
    Class nextSiblingClass;
    
    char *demangledName;
};

//objc-runtime-new.h  LINE-870
struct class_data_bits_t {
    uintptr_t bits;
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

struct my_objc_class : my_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();
    }
    my_objc_class* metaClass() {
        return (my_objc_class *)((long long)isa & ISA_MASK);
    }
};
#endif /* myClassInfo_h */

使用:

myPerson.h
#import <Foundation/Foundation.h>
@interface myPerson : NSObject
-(void)test;
@end

myPerson.m
#import "myPerson.h"
@implementation myPerson
-(void)test{
    NSLog(@"%s",__FUNCTION__);
}
@end

myBoy.h
#import "myPerson.h"
@interface myBoy : myPerson
@end

myBoy.m
#import "myBoy.h"
@implementation myBoy
@end

main

#import <Foundation/Foundation.h>
#import "myClassInfo.h"
#import "myBoy.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        myPerson *p = [[myPerson alloc]init];
        [p test];
        [p test];
        
        my_objc_class *myPersonClass = (__bridge my_objc_class *)[myPerson class];
        cache_t cache = myPersonClass->cache;
        
        NSLog(@"%s %p", @selector(test), cache.imp(@selector(test)));
        NSLog(@"--------");
        
        bucket_t *buckets = cache._buckets;
        bucket_t bucket = buckets[(long long)@selector(test) & cache._mask];
        NSLog(@"%s %p", bucket._key, bucket._imp);
        
        NSLog(@"--------");
        for (int i = 0; i <= cache._mask; i++) {
            bucket_t bucket = buckets[I];
            NSLog(@"%s %p", bucket._key, bucket._imp);
        }
        
        NSLog(@"-----------------Boy-----------------");
        myBoy *boy = [[myBoy alloc]init];
        [boy test];
        [boy test];
        my_objc_class *myBoyClass = (__bridge my_objc_class *)[myBoy class];
        cache_t cache2 = myBoyClass->cache;
        
        NSLog(@"%s %p", @selector(test), cache2.imp(@selector(test)));
        NSLog(@"--------");
        
        bucket_t *buckets2 = cache2._buckets;
        bucket_t bucket2 = buckets2[(long long)@selector(test) & cache2._mask];
        NSLog(@"%s %p", bucket2._key, bucket2._imp);
        
        NSLog(@"--------");
        for (int i = 0; i <= cache2._mask; i++) {
            bucket_t bucket2 = buckets2[I];
            NSLog(@"%s %p", bucket2._key, bucket2._imp);
        }
    }
    return 0;
}
打印结果:
06.方法缓存[84860:13598078] -[myPerson test]
06.方法缓存[84860:13598078] -[myPerson test]
06.方法缓存[84860:13598078] test 0x100001890
06.方法缓存[84860:13598078] --------
06.方法缓存[84860:13598078] test 0x100001890
06.方法缓存[84860:13598078] --------
06.方法缓存[84860:13598078] (null) 0x0
06.方法缓存[84860:13598078] init 0x1003aae50
06.方法缓存[84860:13598078] (null) 0x0
06.方法缓存[84860:13598078] test 0x100001890
06.方法缓存[84860:13598078] -----------------Boy-----------------
06.方法缓存[84860:13598078] -[myPerson test]
06.方法缓存[84860:13598078] -[myPerson test]
06.方法缓存[84860:13598078] test 0x100001890
06.方法缓存[84860:13598078] --------
06.方法缓存[84860:13598078] test 0x100001890
06.方法缓存[84860:13598078] --------
06.方法缓存[84860:13598078] (null) 0x0
06.方法缓存[84860:13598078] init 0x1003aae50
06.方法缓存[84860:13598078] (null) 0x0
06.方法缓存[84860:13598078] test 0x100001890

发现:

1.方法名(_key)与_mask 得到的位置(索引值),就缓存到数组的下标位置处

objc-cache.mm
【bucket_t * cache_t::find(cache_key_t k, id receiver)】
--> mask_t begin = cache_hash(k, m);
-->
static inline mask_t cache_hash(cache_key_t key, mask_t mask) {
    return (mask_t)(key & mask);
}

发现:(key & mask)

2.左边的值不管怎么 & 都不会比右边的mask的值要大(【&】的性质)

3.有可能&_mask的值是一样的,但发现里面的key不是与的key,底层会 i-1再去找(往上找),到0还没有,会从mask处(数组的最大处在循环往前去找)
bucket_t * cache_t::find(cache_key_t k, id receiver){
    assert(k != 0);
    bucket_t *b = buckets();
    mask_t m = mask();
    mask_t begin = cache_hash(k, m);
    mask_t i = begin;
    do {
        if (b[i].key() == 0  ||  b[i].key() == k) {
            return &b[I];
        }
    } while ((i = cache_next(i, m)) != begin);
    // hack
    Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
    cache_t::bad_cache(receiver, (SEL)k, cls);
}

【__arm64__架构】
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}

4.数组的长度不够了,会扩容(原来的长度X2)(mask的值变化),此时会清除里面的所有的,重新开始缓存(比如一开始会分配10个,不够了可能会给20个空间)

myPerson.h
#import <Foundation/Foundation.h>
@interface myPerson : NSObject
-(void)test;
-(void)test2;
-(void)test3;
@end

myPerson.m
#import "myPerson.h"
@implementation myPerson
-(void)test{
    NSLog(@"%s",__FUNCTION__);
}
-(void)test2{
    NSLog(@"%s",__FUNCTION__);
}
-(void)test3{
    NSLog(@"%s",__FUNCTION__);
}
@end
runtime_07.png runtime_08.png runtime_09.png
发现:
默认的_mask 是 3 ,说明槽位是 4 个,实际占用槽位是3个。然后我们再多调用一个方法,
发现_occupied数量等于_mask时,再次加入一个缓存方法时,槽位的总量会变大,槽位会变为原来的2倍
源码:
=======================
默认的 槽位
objc-cache.mm LINE--91
/* Initial cache bucket count. INIT_CACHE_SIZE must be a power of two. */
enum {
    INIT_CACHE_SIZE_LOG2 = 2,
    INIT_CACHE_SIZE      = (1 << INIT_CACHE_SIZE_LOG2)
};


objc-cache.mm  LINE-544
void cache_t::expand()
{
    cacheUpdateLock.assertLocked();
    
    uint32_t oldCapacity = capacity();
    uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;

    if ((uint32_t)(mask_t)newCapacity != newCapacity) {
        // mask overflow - can't grow further
        // fixme this wastes one bit of mask
        newCapacity = oldCapacity;
    }

    reallocate(oldCapacity, newCapacity);
}

(4-1,2)-->(4-1,3)->(2*4-1,1) 
5.如果子类调用了父类的方法,此时会缓存一份到自己的方法缓存列表里面

方法缓存的作用:以空间换取时间

总结:

1、每个Class 里会有一个cache 方法缓存。
2、cache 本质是一个 Hash表。
3、hash 函数式  f(@selector()) = index,  @selector() & _mask。
4、槽位如果不够,_mask 会变换,变为原来的2倍,并且扩展槽位的时候,会清空数组里原有的缓存内容。
5、子类没有实现方法会调用父类的方法,会将父类方法加入到子类自己的cache 里。

友情链接:

相关文章

网友评论

      本文标题:runtime(二)

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