Runtime

作者: 川少叶 | 来源:发表于2018-12-09 14:40 被阅读8次

    isa

    在ARM 64之前,isa是一个指针,存储着ClassMeta-Class对象的内存地址。
    ARM 64之后,isa是一个共用体,除了存储ClassMeta-Class对象的内存地址,还保存了更多的信息。ARM 64情况下,isa共用体的定义。

    union isa_t 
    {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
        Class cls;
        uintptr_t bits;
    #   define ISA_MASK        0x0000000ffffffff8ULL
    #   define ISA_MAGIC_MASK  0x000003f000000001ULL
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
        struct {
            uintptr_t nonpointer        : 1;
            uintptr_t has_assoc         : 1;
            uintptr_t has_cxx_dtor      : 1;
            uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
            uintptr_t magic             : 6;
            uintptr_t weakly_referenced : 1;
            uintptr_t deallocating      : 1;
            uintptr_t has_sidetable_rc  : 1;
            uintptr_t extra_rc          : 19;
    #       define RC_ONE   (1ULL<<45)
    #       define RC_HALF  (1ULL<<18)
        };
    }
    

    isa的位域的意义

    • nonpointer
      - 0 ,表示指针,存储Class对象地址
      - 1,使用共用体,存储更多信息
    • has_assoc
      - 是否设置过关对象。如果没有,该对象会释放地更快
    • has_cxx_dtor
      - 是否有C++析构函数。如果没有,该对象会释放地更快
    • shiftcls
      - 类对象,元类对象的内存地址
    • magic
      - 对象是否完成初始化
    • weakly_referenced
      - 是否被若引用指向过。如果没有,该对象会释放地更快
    • deallocating
      - 是否正在被释放
    • has_sidetable_rc
      - 是否引用计数器过大,不能保存在isa中。如果过大,会保存在SideTable
    • extra_rc
      - 保存该对象的引用计数减1

    method_t

    struct method_t {
        SEL name;       // 底层结构类似于char *
        const char *types;
        IMP imp;     // 该表函数的具体实现
    };
    

    SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似

    • 可以通过@selector()sel_registerName()获得
    • 可以通过sel_getName()NSStringFromSelector()
    • 不同类中相同名字的方法,所对应的方法选择器是相同的

    types包含了函数返回值,参数编码的字符串

    // i 24  @ 0  : 8   i 16  f 20
    /*  i 24,表示所有参数占据的字节数
        @ 0,表示第一个参数,self,从第0个参数开始
        : 8,表示第二个参数,SEL,从第8个开始
        i 16,表示(int)age
        f 20,表示(float)height
     */
    - (void)test:(int)age height:(float)height {
     
    }
    

    IMP表示函数的具体实现

    typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
    

    cache_t

    struct cache_t {
        struct bucket_t *_buckets;      // 数组
        mask_t _mask;          // 数组长度
        mask_t _occupied;    // 已经使用的长度
    }
    
    struct bucket_t {
    private:
        cache_key_t _key;      // SEL
        IMP _imp;
    }
    

    散列表使用的hash算法,就是&运算

    static inline mask_t cache_hash(cache_key_t key, mask_t mask) 
    {
        return (mask_t)(key & mask);
    }
    

    如果发生Hash碰撞,则从前一个开始找,直到索引为0。如果还找不到,就再从数组最后一个,倒序开始找。

    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);
    }
    

    子类调用父类方法之后,先在子类找不到方法,然后去父类找,

    • 也是先找父类的缓存,再找父类的方法列表
    • 找到之后,会把方法缓存到子类的cache中

    缓存的时候,如果缓存满了,则清除所有缓存,并且2倍扩容

    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);
    }
    

    obj_msgSend()

    OC中的方法调用,都是转换成调用obj_msgSend()函数。整个过程可以分成3个阶段:

    • 消息发送:根据OC对象模型图,从子类到父类,去找方法
    • 动态方法解析:可能会向类对象添加方法
    • 消息转发:可能将该方法调用,转到其他对象去调用。
    _class_lookupMethodAndLoadCache3
    lookUpImpOrForward
    getMethodNoSuper_nolock、search_method_list、log_and_fill_cache
    cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache
    _class_resolveInstanceMethod
    _objc_msgForward_impcache
    

    汇编调用

    由于obj_msgSend()调用十分频繁,所以obj_msgSend()有一部分是使用汇编实现的。
    一般如果是C函数是objc_msgSend,则对应的汇编函数就是加上下划线_objc_msgSend
    汇编做了如下工作:

    • 判断receiver是否为nil,如果是nil就直接返回
    • 查找缓存,如果缓存没有命中,就在方法列表查找方法
    objc-msg-arm64.s
    ENTRY _objc_msgSend
    b.le    LNilOrTagged
    CacheLookup NORMAL
    .macro CacheLookup
    .macro CheckMiss
    STATIC_ENTRY __objc_msgSend_uncached
    .macro MethodTableLookup
    __class_lookupMethodAndLoadCache3
    

    查找方法

    汇编函数是__class_lookupMethodAndLoadCache3,则对应的runtime方法是_class_lookupMethodAndLoadCache3
    在C函数还是会查找一遍缓存,原因是:再执行到这次的查找缓存之前,可能动态添加一些方法,缓存方法变化。

    • 在本类进行查找
      1. 再次查找缓存,
      2. 查找方法,在本类对象的方法列表,还可以分成二分查找(如果已经排好序)和线性查找。如果找到了,会进行缓存
    • 不断地向上,在父类进行查找。不管是
      1. 先找父类的缓存。如果找到了,在自己的类缓存
      2. 在找父类的方法列表。如果找到了,在自己的类缓存

    动态方法解析

    当通过上面的方法查找,找不到方法,就会进入动态方法解析。

    • 通过triedResolver,只解析一次
    • goto retry会重新进入方法查找:先查找本类缓存,在找本类方法列表;再父类
      if (resolver  &&  !triedResolver) {
            runtimeLock.unlockRead();
            _class_resolveMethod(cls, sel, inst);
            runtimeLock.read();
            // Don't cache the result; we don't hold the lock so it may have 
            // changed already. Re-do the search from scratch instead.
            triedResolver = YES;
            goto retry;
        }
    

    使用如下示例代码,进行动态方法解析

    void c_anotherMethod(id self, SEL _cmd)
    {
        NSLog(@"c_another class method");
    }
    
    - (void)anotherTest {
        NSLog(@"instance %s",__func__);
    }
    
    + (BOOL)resolveClassMethod:(SEL)sel
    {
        if (sel == @selector(test)) {
            // 第一个参数是object_getClass(self)
            class_addMethod(object_getClass(self), sel, (IMP)c_anotherMethod, "v16@0:8");
            return YES;
        }
        return [super resolveClassMethod:sel];
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        if (sel == @selector(test)) {
            Method anotherMethod = class_getInstanceMethod(self, @selector(anotherTest));
            class_addMethod(self,
                            sel,
                            method_getImplementation(anotherMethod),
                            method_getTypeEncoding(anotherMethod));
            return YES;
        }
        
        return [super resolveInstanceMethod:sel];
    }
    

    消息转发

    消息转发.png

    首先是调用forwardingTargetForSelector:,如果返回不为nil,就进入target的方法调用流程。forwarding:没有开源,可以通过逆向,了解其实现的。
    如果最终能够走到forwardInvocation:,即使forwardInvocation:是空实现,该方法调用也能成功完成。

    1. 调用forwardInvocation之前,系统会先调用resolveInstanceMethod:,传入的selector_forwardStackInvocation:
    2. 如果实现methodSignatureForSelector :,但是没有实现forwardInvocation :,也会unrecognized selector
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        return [[ForwardTarget alloc] init];
    }
    
    // 方法签名:返回值类型、参数类型
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        if (aSelector == @selector(test)) {
            return [NSMethodSignature signatureWithObjCTypes:"v24@0:8@16"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    // NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
    //    anInvocation.target 方法调用者
    //    anInvocation.selector 方法名
    //    anInvocation.methodSignature methodSignatureForSelector:方法返回的
    //    [anInvocation getArgument:NULL atIndex:0]
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        [anInvocation invokeWithTarget:[[ForwardTarget alloc] init]];
    }
    

    Super

    [super message]的底层实现,最初是objc_msgSendSuper(arg, sel),其中第一个参数是结构体,该结构体包含两个成员变量:

    1. 消息接收者,就是子类实例对象
    2. 父类类对象,找方法会从父类类对象开始找
    struct objc_super {
        __unsafe_unretained _Nonnull id receiver; // 消息接收者
        __unsafe_unretained _Nonnull Class super_class; // 消息接收者的父类
    };
    

    后来实现变成objc_msgSendSuper2(arg, sel),第一个参数是结构体,其成员变量是:

    1. 消息接收者,仍然是子类对象
    2. 消息接收者的类对象。但是其内部实现,仍然是获取父类类对象,从父类开始找
    struct objc_super2 {
        __unsafe_unretained _Nonnull id receiver; // 消息接收者
        __unsafe_unretained _Nonnull Class current_class; // 消息接收者的类对象
    };
    
    - (instancetype)init
    {
        if (self = [super init]) {
            NSLog(@"[self class] = %@", [self class]); // MJStudent
            NSLog(@"[self superclass] = %@", [self superclass]); // MJPerson
    
            NSLog(@"--------------------------------");
    
            // objc_msgSendSuper({self, [MJPerson class]}, @selector(class));
            NSLog(@"[super class] = %@", [super class]); // MJStudent
            NSLog(@"[super superclass] = %@", [super superclass]); // MJPerson
        }
        return self;
    }
    

    isKindOf

    objc的源码如下,要特别注意的是

    1. + (BOOL)isKindOfClass:,要求传入的是metaClass,但是最后一步会走到NSObject的class对象。
    // 这句代码的方法调用者不管是哪个类(只要是NSObject体系下的),都返回YES
    NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1
    NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0
    NSLog(@"%d", [MJPerson isKindOfClass:[Person class]]); // 0
    NSLog(@"%d", [MJPerson isMemberOfClass:[Person class]]); // 0
    
    - (BOOL)isMemberOfClass:(Class)cls {
        return [self class] == cls;
    }
    
    - (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    + (BOOL)isMemberOfClass:(Class)cls {
        return object_getClass((id)self) == cls;
    }
    
    + (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    

    面试题

    以下代码中,是否能够正常打印

    @interface Person : NSObject
    @property (copy, nonatomic) NSString *name;
    
    - (void)print;
    @end
    
    @implementation MJPerson
    
    - (void)print
    {
        NSLog(@"my name is %@", self->_name);
    }
    
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        id cls = [MJPerson class];
    
        void *obj = &cls;
    
        [(__bridge id)obj print];
    }
    

    首先分析,变量之间的关系,如下图所示

    1. 与正常调用实例方法的结构相同,都包含指针变量,对象的ISA指针,类对象
    2. 所以可以调用到print方法
      变量之间的关系.png

    执行方法时,栈中变量地址的位置,如下图所示

    1. 栈中的变量地址是从高到低,即先出现的变量,占据高地址
    2. [super viewDidLoad];,上文说过,会有一个结构体变量;接着是cls,接着是obj
    3. 根据实例对象的结构图,访问name成员变量,就是访问self占据的指针
      栈中的变量地址.png

    API使用

    • 成员变量是基本数据类型,需要先转成指针,再bridge
        // 创建类
        Class newClass = objc_allocateClassPair([NSObject class], "MJDog", 0);
        class_addIvar(newClass, "_age", 4, 1, @encode(int));
        class_addIvar(newClass, "_weight", 4, 1, @encode(int));
        class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
        // 注册类
        objc_registerClassPair(newClass);
        // 如果是基本数据类型,需要先转成指针,再bridge
        object_setIvar(person, ageIvar, (__bridge id)(void *)10);
    

    具体应用

    • 利用关联对象(AssociatedObject)给分类添加属性
    • 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
    • 交换方法实现(交换系统的方法,method_exchangeImplementions会清空方法缓存
    • 利用消息转发机制解决方法找不到的异常问题

    数组和字典的方法交换

    集合类型,由于使用了类簇.

    • 可变数组真正的类型是__NSArrayM
    • 可变字典真正的类型是__NSDictionaryM
    • 不可变字典真正的类型是__NSDictionaryI
       static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
           // 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
            Class cls = NSClassFromString(@"__NSArrayM");
            Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
            Method method2 = class_getInstanceMethod(cls, @selector(my_insertObject:atIndex:));
            method_exchangeImplementations(method1, method2);
        });
    
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class cls = NSClassFromString(@"__NSDictionaryM");
            Method method1 = class_getInstanceMethod(cls, @selector(setObject:forKeyedSubscript:));
            Method method2 = class_getInstanceMethod(cls, @selector(my_setObject:forKeyedSubscript:));
            method_exchangeImplementations(method1, method2);
            
            Class cls2 = NSClassFromString(@"__NSDictionaryI");
            Method method3 = class_getInstanceMethod(cls2, @selector(objectForKeyedSubscript:));
            Method method4 = class_getInstanceMethod(cls2, @selector(my_objectForKeyedSubscript:));
            method_exchangeImplementations(method3, method4);
        });
    

    相关文章

      网友评论

          本文标题:Runtime

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