一、简介
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