Objective-C Runtime

一、简介

Runtime,Objecive-c运行时,一套底层C语言的API。
我们平常写的OC底层都是基于Runtime来实现的, 例如:

//注意使用以下代码,请在xcode工程设置中找到Enable Strict Checking of objc_msgSend Calls并关闭该选项。
[self tryUpgradeApp];
//其实会转化为
objc_msgSend(self, @selector(tryUpgradeApp));

//如有参数:
[self tryUpgradeApp:arg1...];
//则转为
objc_msgSend(self, @selector(tryUpgradeApp), arg1, ...);

消息是直到运行的时候才和方法实现绑定的,编译器会把一个消息表达式转化成一个objc_msgSend的调用,该方法主要参数有消息接受者和方法标号,同时有可变参数可以接受相应的参数。

多数情况下开发者只需要编写objc代码即可,编译器会将objc代码转为runtime代码,在运行时确定数据结构和函数。

@implementation TestCls

+(instancetype) create
{
   TestCls* instance = [[[self class] alloc] init];
    [instance onCreate];
    return instance;
}

-(void) onCreate{
    NSLog(@"onCreate >> in >>");
}

@end

执行以下命令得到一个cpp文件。

clang -rewrite-objc TestCls.m 

生成的文件很大我们只看以下内容,从下代码可以看到,实际上objc的方法调用都是用过objc_msgSend消息机制实现的。

static __NSConstantStringImpl __NSConstantStringImpl__var_folders__9_4xdmxtdd1sv16b8cry0qlxzm0000gn_T_TestCls_b47c1a_mi_0 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"onCreate >> in >>",17};

static instancetype _C_TestCls_create(Class self, SEL _cmd) {
   TestCls* instance = ((id (*)(id, SEL))(void *)objc_msgSend)((id)((id (*)(id, SEL))(void *)objc_msgSend)((id)((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")), sel_registerName("alloc")), sel_registerName("init"));
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)instance, sel_registerName("onCreate"));
    return instance;
}

static void _I_TestCls_onCreate(TestCls * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders__9_4xdmxtdd1sv16b8cry0qlxzm0000gn_T_TestCls_b47c1a_mi_0);
}

二、runtime的数据结构

// 参考 objc/objc.h 

// id 
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;



//sel
typedef struct objc_selector *SEL;

// class
typedef struct objc_class *Class;

struct objc_class {
    //class中也有一个isa指针指向所属的元类
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
   //指向父类
    Class super_class                                        OBJC2_UNAVAILABLE;
   //类名
    const char *name                                         OBJC2_UNAVAILABLE;
   //类的版本信息
    long version                                             OBJC2_UNAVAILABLE;
   //信息
    long info                                                OBJC2_UNAVAILABLE;
   //实例大小
    long instance_size                                       OBJC2_UNAVAILABLE;
   //成员变量列表
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
   //成员方法列表
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
   //系统调用的方法会缓存到cache中使调下次调用效率更高
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
   //该类实现的协议列表
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

NSObject所定义的方法

因为objc的对象和类型在运行时决定,
objc大多数类的父类(祖先类)都源自nsobject (除nsproxy外),nsobject定义了一组方法,可以从runtime中获取信息,例如:

- (BOOL)isKindOfClass:(Class)aClass;  //判断self的class或者super_class(递归)的class是否为class
- (BOOL)isMemberOfClass:(Class)aClass; //判断self的classs是否为传入class
- (BOOL)conformsToProtocol:(Protocol *)aProtocol; //判断是否实现了某个协议
+ (Class)class; 返回class对象
//....等等等等

参考:

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

inline Class 
objc_object::getIsa() 
{
    if (isTaggedPointer()) {
        uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
        return objc_tag_classes[slot];
    }
    return ISA();
}

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
    return (Class)(isa.bits & ISA_MASK);
}

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (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)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

三、消息机制



1、寻找方法
消息机制的本质就是让对象发送消息,objc_msgSend(),runtime中寻找方法的顺序为:
实例中的缓存方法列表->实例中的方法列表->super_class(递归到root)实例的缓存方法列表 和 实例方法列表->resolve拦截方法调用消息转发或者报错。

2、方法中的隐藏参数
当objc_msgSend找到相应的方法时,他讲调用该方法的实现。并讲消息传递给该奋斗。同时会带上2个隐藏的参数: 接受消息的对象、当前方法的sel指针。 (self, __cmd)

- strange
{
    id  target = getTheReceiver();
    SEL method = getTheMethod();
 
    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}

3、获取方法地址
NSObject类中有个methodForSelector:实例方法,你可以用它来获取某个方法选择器对应的IMP。

4、动态方法解析
runtime在cache和实例中(包括super_class)找不到要执行的方法的时候,runtime会自动调用resolveInstanceMethod:或resolveClassMethod: 以使你有一次动态添加方法的机会。 这时候我们只需要使用 class_addMethod函数完成向类添加所需要的方法即可。

void dynamicMethodIMP(id self, SEL _cmd) {
    //代码实现
}
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(testFun1)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}

v@:代码返回值和参数, 详情参考 apple官方说明

四、常用方法

常用的runtime方法以及使用方式如下:

//获取类:
Class PersonClass = object_getClass([Person class]);

//SEL是selector在 Objc 中的表示:
SEL oriSEL = @selector(test1);

//获取类方法:
Method oriMethod = Method class_getClassMethod(Class cls , SEL name);

//获取实例方法:
Method class_getInstanceMethod(Class cls , SEL name)

//添加方法:
BOOL addSucc = class_addMethod(cls, oriSEL, method_getImplementation(cusMethod), 
method_getTypeEncoding(oriMethod));

//替换原方法实现:
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

//交换两个方法:
method_exchangeImplementations(oriMethod, cusMethod);

//获取一个类的属性列表(返回值是一个数组):
objc_property_t *propertyList = class_copyPropertyList([self class], &count);

//获取一个类的方法列表(返回值是一个数组):
Method *methodList = class_copyMethodList([self class], &count);

//获取一个类的成员变量列表(返回值是一个数组):
Ivar *ivarList = class_copyIvarList([self class], &count);

//获取成员变量的名字:
const char *ivar_getName(Ivar v)

//获取成员变量的类型:
const char *ivar_getTypeEndcoding(Ivar v)

//获取一个类的协议列表(返回值是一个数组):
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);

//set方法:
//将值value 跟对象object 关联起来(将值value 存储到对象object 中)
//参数 object:给哪个对象设置属性
//参数 key:一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节
//参数 value:给属性设置的值
//参数policy:存储策略 (assign 、copy 、 retain就是strong)
void objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy)


//利用参数key 将对象object中存储的对应值取出来:
id objc_getAssociatedObject(id object , const void *key)

//判断类中是否包含某个方法的实现
BOOL class_respondsToSelector(Class cls, SEL sel)
  
  
//获取类中的方法实现
IMP class_getMethodImplementation(Class cls, SEL name)
//获取类中的方法的实现,该方法的返回值类型为struct
IMP class_getMethodImplementation_stret(Class cls, SEL name) 
//获取Method中的SEL
SEL method_getName(Method m)   
//获取方法的Type字符串(包含参数类型和返回值类型)
const char *method_getTypeEncoding(Method m) 
//获取参数个数
unsigned int method_getNumberOfArguments(Method m)
//获取返回值类型字符串
char *method_copyReturnType(Method m)
//获取方法中第n个参数的Type
char *method_copyArgumentType(Method m, unsigned int index)
//获取Method的描述
struct objc_method_description *method_getDescription(Method m)
//设置Method的IMP
IMP method_setImplementation(Method m, IMP imp) 
//替换Method
void method_exchangeImplementations(Method m1, Method m2)
//获取SEL的名称
const char *sel_getName(SEL sel)
//注册一个SEL
SEL sel_registerName(const char *str)
//判断两个SEL对象是否相同
BOOL sel_isEqual(SEL lhs, SEL rhs) 
//通过块创建函数指针,block的形式为^ReturnType(id self,参数,...)
IMP imp_implementationWithBlock(id block)
//获取IMP中的block
id imp_getBlock(IMP anImp)
//移出IMP中的block
BOOL imp_removeBlock(IMP anImp)
//调用target对象的sel方法
id objc_msgSend(id target, SEL sel, 参数列表...)

五、实践

1、给category添加属性

@interface NSObject (RT)
@property(nonatomic, strong) NSString* runtime_name;
@end

@implementation NSObject (RT)

@dynamic runtime_name;

- (NSString *)runtime_name {
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setRuntime_name:(NSString *)runtime_name{
    objc_setAssociatedObject(self, @selector(runtime_name), runtime_name, OBJC_ASSOCIATION_RETAIN);
}
@end

//使用
#include "NSObject+RT.h"
NSObject* obj = [[NSObject alloc] init];
obj.runtime_name = @"runtime/category/property";
NSLog(@"%@",obj.runtime_name);

2、自己实现简单的KVO

基本思路:例如给TestModel添加一个kvo事件,则使用runtime创建一个继承于TestModel的类(如KVO_TestModel类),修改原来类的isa指针指向这个子类,并记录需要监听的key列表写入关联对象中,并在子类中新增setXXX的方法(重写父类方法)。在外部setXX方法时进入这个子类实现的set方法,该方法中除了给super class发消息修改值外,并从监听列表中判断,如确实在监听则调用其block方法。

#import <Foundation/Foundation.h>

typedef void(^KVOBlock)(id obj, NSString *key, id oldValue, id newValue);


@interface NSObject (KVO)

- (void)kvo_addObserver:(NSObject *)observer
                forKey:(NSString *)key
             withBlock:(KVOBlock)block;

- (void)kvo_removeObserver:(NSObject *)observer forKey:(NSString *)key;

@end
#import "NSObject+KVO.h"
#import <objc/message.h>

NSString *const kKVOClassPrefix = @"KVO_";
NSString *const kKVOAssociatedObservers = @"KVOAssociated";


#pragma mark - ObservationInfo

@interface ObservationInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) KVOBlock block;
@end

@implementation ObservationInfo

- (instancetype)initWithObserver:(NSObject *)observer Key:(NSString *)key block:(KVOBlock)block
{
    self = [super init];
    if (self) {
        _observer = observer;
        _key = key;
        _block = block;
    }
    return self;
}

@end




//将setter方法转为geter方法
static NSString * getterForSetter(NSString *setter)
{
    if (setter.length <=0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) {
        return nil;
    }
    
    
    NSRange range = NSMakeRange(3, setter.length - 4);
    NSString *key = [setter substringWithRange:range];
    
    // lower case the first letter
    NSString *firstLetter = [[key substringToIndex:1] lowercaseString];
    key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1)
                                       withString:firstLetter];
    
    return key;
}


//将getter方法转为setter方法
static NSString * setterForGetter(NSString *getter)
{
    if (getter.length <= 0) {
        return nil;
    }
    
    NSString *firstLetter = [[getter substringToIndex:1] uppercaseString];
    NSString *remainingLetters = [getter substringFromIndex:1];

    NSString *setter = [NSString stringWithFormat:@"set%@%@:", firstLetter, remainingLetters];
    
    return setter;
}


#pragma mark - 


static void kvo_setter(id self, SEL _cmd, id newValue)
{
    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *getterName = getterForSetter(setterName);
    
    if (!getterName) {
        NSString *reason = [NSString stringWithFormat:@"没有%@方法", setterName];
        @throw [NSException exceptionWithName:NSInvalidArgumentException
                                       reason:reason
                                     userInfo:nil];
        return;
    }
    
    //获得原来的值
    id oldValue = [self valueForKey:getterName];
    
    
    struct objc_super superclazz = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };
    
    //发消息给super进行修改值
    void (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;
    objc_msgSendSuperCasted(&superclazz, _cmd, newValue);
    
    //block方式发送通知
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kKVOAssociatedObservers));
    for (ObservationInfo *each in observers) {
        if ([each.key isEqualToString:getterName]) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                each.block(self, getterName, oldValue, newValue);
            });
        }
    }
}


static Class kvo_class(id self, SEL _cmd)
{
    return class_getSuperclass(object_getClass(self));
}


#pragma mark - KVO Category
@implementation NSObject (KVO)

- (void)kvo_addObserver:(NSObject *)observer
                forKey:(NSString *)key
             withBlock:(KVOBlock)block
{
    SEL setterSelector = NSSelectorFromString(setterForGetter(key));
    Method setterMethod = class_getInstanceMethod([self class], setterSelector);
    if (!setterMethod) {
        @throw [NSException exceptionWithName:NSInvalidArgumentException
                                       reason:@"找不到这个setter方法"
                                     userInfo:nil];
        
        return;
    }
    
    Class clazz = object_getClass(self);
    NSString *clazzName = NSStringFromClass(clazz);
    
    
    if (![clazzName hasPrefix:kKVOClassPrefix]) {
        //创建一个新的类并修改isa指针指向这个类
        clazz = [self makeKvoClassWithOriginalClassName:clazzName];
        object_setClass(self, clazz);
    }
    
    //实现seter方法
    if (![self hasSelector:setterSelector]) {
        const char *types = method_getTypeEncoding(setterMethod);
        class_addMethod(clazz, setterSelector, (IMP)kvo_setter, types);
    }
    
    //记录kvo列表
    ObservationInfo *info = [[ObservationInfo alloc] initWithObserver:observer Key:key block:block];
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kKVOAssociatedObservers));
    if (!observers) {
        observers = [NSMutableArray array];
        objc_setAssociatedObject(self, (__bridge const void *)(kKVOAssociatedObservers), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [observers addObject:info];
}


- (void)kvo_removeObserver:(NSObject *)observer forKey:(NSString *)key
{
    NSMutableArray* observers = objc_getAssociatedObject(self, (__bridge const void *)(kKVOAssociatedObservers));
    
    ObservationInfo *infoToRemove;
    for (ObservationInfo* info in observers) {
        if (info.observer == observer && [info.key isEqual:key]) {
            infoToRemove = info;
            break;
        }
    }
    
    [observers removeObject:infoToRemove];
}


- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName
{
    NSString *kvoClazzName = [kKVOClassPrefix stringByAppendingString:originalClazzName];
    Class clazz = NSClassFromString(kvoClazzName);
    
    if (clazz) {
        return clazz;
    }
    
    // class doesn't exist yet, make it
    Class originalClazz = object_getClass(self);
    Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);
    
    // grab class method's signature so we can borrow it
    Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
    const char *types = method_getTypeEncoding(clazzMethod);
    class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
    
    objc_registerClassPair(kvoClazz);
    
    return kvoClazz;
}


- (BOOL)hasSelector:(SEL)selector
{
    Class clazz = object_getClass(self);
    unsigned int methodCount = 0;
    Method* methodList = class_copyMethodList(clazz, &methodCount);
    for (unsigned int i = 0; i < methodCount; i++) {
        SEL thisSelector = method_getName(methodList[i]);
        if (thisSelector == selector) {
            free(methodList);
            return YES;
        }
    }
    
    free(methodList);
    return NO;
}


@end