Runtime

2018-12-09  本文已影响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的位域的意义

method_t

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

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

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

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

缓存的时候,如果缓存满了,则清除所有缓存,并且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个阶段:

_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
汇编做了如下工作:

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函数还是会查找一遍缓存,原因是:再执行到这次的查找缓存之前,可能动态添加一些方法,缓存方法变化。

动态方法解析

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

  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使用

    // 创建类
    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);

具体应用

数组和字典的方法交换

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

   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);
    });
上一篇下一篇

猜你喜欢

热点阅读