runtime总结

2018-09-19  本文已影响18人  zziazm

在Objective-C里调用一个方法是这样的:

[object method];

编译器会把它翻译成:

id objc_msgSend(object, selector,arg1,arg2,...)

id

id的定义:

/// A pointer to an instance of a class.
typedef struct objc_object *id;

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

objc_object结构体里的成员变量isa指向了实例对象的类对象。

Class

Class的定义:

typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

objc_class结构体里的成员变量isa指向了类的元类;成员变量super_class指向了类的父类。

类也是对象,可以看做是元类的实例,元类里的方法列表存储的是类方法。

SEL

SEL的定义:

typedef struct objc_selector *SEL;

runtime的源码里没有给出objc_selector的定义。SEL类型是方法的selector,可以理解为区分方法的 ID。其实它就是个映射到方法的C字符串,你可以用 Objc 编译器命令 @selector() 或者 Runtime 系统的 sel_registerName 函数来获得一个 SEL 类型的方法选择器。

objc_cache

类有一个存放方法的方法列表和一个存放缓存方法的哈希表objc_msgSend这个函数的内部是现实是先从类的缓存哈希表里寻找方法,如果没找到,则从类的方法列表里寻找,找到后会将方法填充到缓存哈希表里。

runtime源码
objc_msgSend的源码解析
类的方法的缓存解析

消息转发

objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类对象,然后在该类对象中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这抛出异常之前,objc的运行时会给出三次拯救程序崩溃的机会:

Dynamic Method resolution

首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法)。在这个方法中,我们有机会为该未知消息新增一个”处理方法””。不过使用该方法的前提是我们已经实现了该”处理方法”,只需要在运行时通过class_addMethod函数动态添加到类里面就可以了。如下代码所示:


void dynamicMethodIMP(id self, SEL _cmd) {//至少要有两个参数self 和 _cmd.
    // implementation ....
}

@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}
@end


不过这种方案更多的是为了实现@dynamic属性:

@interface MyClass : NSObject
@property (nonatomic, copy) NSString *name;
@end

#import "MyClass.h"
#import <objc/runtime.h>


NSString * dynamicNameIMP(id self, SEL _cmd)
{
    NSString * name = objc_getAssociatedObject(self, _cmd);
    return name;
}

void dynamicSetNameIMP(id self, SEL _cmd, NSString *name)
{
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY);
}

@implementation MyClass
@dynamic name;

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(name)) {
        class_addMethod([self class], sel, (IMP)dynamicNameIMP, "v@:");
    }
    if (sel == @selector(setName:)) {
        class_addMethod([self class], sel, (IMP)dynamicSetNameIMP, "v@:@");
    }
    
    return [super resolveInstanceMethod:sel];
}
@end

如果使用class_addMethod添加类方法,则class_addMethod的第一个参数要传入一个元类对象object_getClass(self)

Fast forwarding

如果在上一步无法处理消息,且目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。

@interface SUTRuntimeMethodHelper : NSObject
- (void)method2;
@end
@implementation SUTRuntimeMethodHelper
- (void)method2 {
    NSLog(@"%@, %p", self, _cmd);
}
@end
#pragma mark -
@interface SUTRuntimeMethod () {
    SUTRuntimeMethodHelper *_helper;
}
@end
@implementation SUTRuntimeMethod
+ (instancetype)object {
    return [[self alloc] init];
}
- (instancetype)init {
    self = [super init];
    if (self != nil) {
        _helper = [[SUTRuntimeMethodHelper alloc] init];
    }
    return self;
}
- (void)test {
    [self performSelector:@selector(method2)];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"forwardingTargetForSelector");
    NSString *selectorString = NSStringFromSelector(aSelector);
    // 将消息转发给_helper来处理
    if ([selectorString isEqualToString:@"method2"]) {
        return _helper;
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end

This method gives an object a chance to redirect an unknown message sent to it before the much more expensive forwardInvocation: machinery takes over. This is useful when you simply want to redirect messages to another object and can be an order of magnitude faster than regular forwarding. It is not useful where the goal of the forwarding is to capture the NSInvocation, or manipulate the arguments or return value during the forwarding.

Normal forwarding

如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。
这一步是Runtime最后一次给你挽救的机会。首先它会调用-methodSignatureForSelector:方法获得NSMethodSignature类型的函数签名,如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并调用-forwardInvocation:方法。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。
NSObject的forwardInvocation:方法实现只是简单调用了doesNotRecognizeSelector:方法,它不会转发任何消息。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {
            signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
        }
    }
    return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:_helper];
    }else{
       [super forwardInvocation:anInvocation];
    }
}

Category

category笔记

Method Swizzling

#import <objc/runtime.h>

@implementation UIViewController (Tracking)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);
        // ...
        // Method originalMethod = class_getClassMethod(class, originalSelector);
        // Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
        //当类中没有originalSelector的实现的时候才会添加成功,否则添加失败
        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

#pragma mark - Method Swizzling

- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}

@end

为什么上面不直接使用method_exchangeImplementations,而是先使用class_addMethod呢?因为直接交换 IMP 是很危险的。如果这个类中没有实现这个方法,但是父类实现了,那么class_getInstanceMethod() 返回的是某个父类的 Method 对象,这样 method_exchangeImplementations() 就把父类的原始实现(IMP)跟这个类的 Swizzle 实现交换了。这样其他父类及其其他子类的方法调用就会出问题,最严重的就是 Crash。

有时为了避免方法命名冲突和参数 _cmd 被篡改,也会使用下面这种『静态方法版本』的 Method Swizzle。CaptainHook 中的宏定义也是采用这种方式,比较推荐:

def IMP *IMPPointer;

static void MethodSwizzle(id self, SEL _cmd, id arg1);
static void (*MethodOriginal)(id self, SEL _cmd, id arg1);

static void MethodSwizzle(id self, SEL _cmd, id arg1) {
    // do custom work
    MethodOriginal(self, _cmd, arg1);
}

BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if (method) {
        const char *type = method_getTypeEncoding(method);
        imp = class_replaceMethod(class, original, replacement, type);
        if (!imp) {
            imp = method_getImplementation(method);
        }
    }
    if (imp && store) { *store = imp; }
    return (imp != NULL);
}

+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
    return class_swizzleMethodAndStore(self, original, replacement, store);
}

+ (void)load {
    [self swizzle:@selector(originalMethod:) with:(IMP)MethodSwizzle store:(IMP *)&MethodOriginal];
}

store传入了MethodOriginal的地址,通过*store = imp;把之前的orginal映射的IMP赋值给了MethodOriginal,之后就可以通过 MethodOriginal(self, _cmd, arg1);调用之前的方法了。

常用方法

class_getInstanceMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name):当类和其父类都没有selector映射的方法是现时,会返回null。
class_addMethod():若果类里没有要添加的方法(不管父类有没有),则会添加一个新的方法(如果父类也有这个方法,则相当于重写了父类的方法),返回值为YES;如果类里已经有了要添加的方法,则不会添加,返回值为NO。

上一篇下一篇

猜你喜欢

热点阅读