前言 系列第五篇:将展示几个runtime的例子以及runtime的实际应用。这些例子包括:
动态添加属性 以及添加用来存储属性对应的实例变量的关联对象
方法决议:父类实现resolveInstanceMethod
,然后在子类中添加方法
消息转发示例:
forwardingTargetForSelector
forwardInvocation
本文的demo代码可以在我的github 上找到
几个有趣的示例 动态添加任意属性
class_addProperty
runtime添加属性API
class_addMethod
runtime添加方法API
仅仅添加属性是没什么用的,因为还需要添加属性对应的实例变量。
虽然runtime提供了class_addIvar
方法来给类添加实例变量,但是注意,该方法只能在创建新的类的时候才能使用;对于已经存在的类,是不允许添加实例变量的
鉴于上述原因,所以可以采用动态添加关联对象来存储属性对应的实例变量。
添加属性 BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
cls
: 要添加属性的类
name
: 添加属性的名字
attributes
: 属性的特性
attributeCount
: 属性特性数量
1 2 3 4 5 6 // runtime add property objc_property_attribute_t attribute1 = {"T", @encode(NSString *)}; objc_property_attribute_t attribute2 = {"N", ""}; objc_property_attribute_t attribute3 = {"&", ""}; objc_property_attribute_t attribute[] = {attribute1, attribute2, attribute3}; class_addProperty([HCBase class], "sb", attribute, 3);
如上代码,添加了一个属性sb
,该属性有3个特性:1. NSString类型 2. nonatomic 3. strong
添加关联对象 为了向正常属性那样访问,给该类添加sb
属性的存取方法sb
和 setSb:
1 2 3 4 5 // add property getter and setter //class_addMethod([HCBase class], @selector(sb), (IMP) sb, "@@:"); class_addMethod([HCBase class], NSSelectorFromString(@"sb"), (IMP) customGetter, "@@:"); //class_addMethod([HCBase class], @selector(setSb:), (IMP) setSb, "v@:@"); class_addMethod([HCBase class], NSSelectorFromString(@"setSb:"), (IMP) customSetter, "v@:@");
注意该方法的最后一个参数是添加的方法的签名:返回类型 + self + selector + 参数
的字符串:
sb
方法,返回类型为id(@ ),self为id(@ ),选择子(: ),参数为空 => @@:
setSb:
方法,返回类型为void(v ),self为id(@ ),选择子(: ),参数为id(@ ) => v@:@
现在来看看setter和getter的通用实现方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // custom getter && setter void customSetter(id self, SEL _cmd, id value) { NSString *propertyStr = NSStringFromSelector(_cmd); // divide set NSString *realProperty = [propertyStr substringFromIndex:3]; realProperty = [realProperty substringToIndex:realProperty.length - 1]; realProperty = [realProperty lowercaseString]; //const NSString *key = [realProperty copy]; objc_setAssociatedObject(self, NSSelectorFromString(realProperty), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); //objc_setAssociatedObject(self, "sb", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } id customGetter(id self, SEL _cmd) { id result = objc_getAssociatedObject(self, _cmd); //id result = objc_getAssociatedObject(self, "sb"); return result; }
这两个方法通过判断selector来判断要添加的是哪一个属性的关联对象,也就是说这个方法是对任何属性通用的(之后新添加其他的属性,也可以使用该实现)
注意:objc_setAssociationObject
和 objc_getAssociationObject
方法中的第二个参数,也就是对象的key是一个 const void *
类型:常量指针;正好@selector也是常量指针。
访问属性 1 2 3 4 5 6 // new HCBase and set && printf sb HCBase *baseOBJ = [[HCBase alloc] init]; //[baseOBJ performSelector:@selector(setSb:) withObject:@"sb"]; [baseOBJ performSelector:NSSelectorFromString(@"setSb:") withObject:@"sb"]; //NSLog(@"vanney code log : new property is %@", [baseOBJ performSelector:@selector(sb)]); NSLog(@"vanney code log : new property is %@", [baseOBJ performSelector:NSSelectorFromString(@"sb")]);
已经添加了属性,并添加了存取方法。现在可以使用performSelector
方法来执行存取方法
方法决议
1 + (BOOL)resolveInstanceMethod:(SEL)sel;
当没有找到方法的实现时,会执行该方法,可以在该方法里面动态添加缺失的方法的实现。
来看看两个类的声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /*--------- HCBase -----------*/ #import "runtime.h" #import <Foundation/Foundation.h> @interface HCBase : NSObject //@property (nonatomic, strong) NSString *curry; //@property (nonatomic, copy) NSString *kd; //@property (nonatomic, assign) int kt; - (void)needResolve; - (void)needForwardTarget; - (void)needFinalForward; @end /*----------- HCSon -----------*/ #import "HCBase.h" @interface HCSon : HCBase @end
HCSon
继承自 HCBase
类,另外HCBase
中声明了一个方法needResolve
,但是没有提供实现。
那么当一个HCSon
对象调用needResolve
方法时,无法找到实现;接着会去寻找resolveInstanceMethod:
,并执行该方法。但是该resolveInstanceMethod
方法定义在父类HCBase
里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void resolveImp(id self, SEL _cmd) { NSLog(@"vanney code log : resolved function"); } @implementation HCBase + (BOOL)resolveInstanceMethod:(SEL)sel { if ([NSStringFromSelector(sel) isEqualToString:@"needResolve"]) { class_addMethod(self, @selector(needResolve), (IMP) resolveImp, "v@:"); return YES; } else { return [[self superclass] resolveInstanceMethod:sel]; } } @end
该方法给self类添加一个needResolve
方法,之后会重新执行needResolve
方法。
现在来看看一个HCSon对象调用needResolve
的过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 // resolve // print method int methodCount; Method *methodList = class_copyMethodList([HCBase class], &methodCount); for (int j = 0; j < methodCount; ++j) { Method curMethod = methodList[j]; printf("before HCBase method name is %s, and type encoding is %s\n", method_getName(curMethod), method_getTypeEncoding(curMethod)); } int methodCountS; Method *methodListS = class_copyMethodList([HCSon class], &methodCountS); for (int j = 0; j < methodCountS; ++j) { Method curMethod = methodListS[j]; printf("before HCSon method name is %s, and type encoding is %s\n", method_getName(curMethod), method_getTypeEncoding(curMethod)); } HCSon *son = [[HCSon alloc] init]; //[baseOBJ needResolve]; [son needResolve]; Method *newMethodList = class_copyMethodList([HCBase class], &methodCount); for (int j = 0; j < methodCount; ++j) { Method curMethod = newMethodList[j]; printf("new HCBase method name is %s, and type encoding is %s\n", method_getName(curMethod), method_getTypeEncoding(curMethod)); } Method *newMethodListS = class_copyMethodList([HCSon class], &methodCountS); for (int j = 0; j < methodCountS; ++j) { Method curMethod = newMethodListS[j]; printf("new HCSon method name is %s, and type encoding is %s\n", method_getName(curMethod), method_getTypeEncoding(curMethod)); }
在执行[son needResolve]
之前,先打印父类和子类的方法,可以发现都没有needResolve
方法;执行之后再次打印,发现HCSon
里面增加了needResolve
方法。
因为执行resolveInstanceMethod
方法时,传入的self
是HCSon
,所以方法就添加在HCSon
里面
注意图片的最后一行
消息转发 备援对象
- (id)forwardingTargetForSelector:(SEL)selector;
HCBase
声明了needForwardTarget
方法,但是没有实现;但是HCBeitai
类有该方法。可以使用forwardingTargetForSelector
方法,返回一个HCBeitai
对象,让该对象来执行needForwardTarget
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 /*------------ HCBeiTai -----------*/ #import <Foundation/Foundation.h> @interface HCBeiTai : NSObject - (void)needForwardTarget; - (void)beitaiForwarding:(int)index; @end @implementation HCBeiTai - (void)needForwardTarget { NSLog(@"bei tai 作用了"); } - (void)beitaiForwarding:(int)index { NSLog(@"vanney code log : bei tai 终极forwarding起作用了 : index is %d", index); } @end /*-------------- HCBase ------------*/ - (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(needForwardTarget)) { NSLog(@"vanney code log : forwarding Target start"); HCBeiTai *beitai = [[HCBeiTai alloc] init]; return beitai; } else { return [super forwardingTargetForSelector:aSelector]; } }
这么一来,就好像HCBase
继承了HCBeitai
一样,可以用来模拟多重继承。来看看运行的结果:
1 2 // forwardingTarget [baseOBJ needForwardTarget];
完整的消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)invocation;
当所有的手段都无法处理该方法时,就开启了完整的消息转发。先调用methodSignatureForSelector
方法,给该未知方法签名— 类似**@@:** 。当签名不为空时,调用forwardInvocation
,将位置方法的所有信息存放在NSInvocation
对象中;这时可以更改方法的信息,比如selector,signature等等,然后交由其他对象来执行更改之后的方法。
来看看一个示例:HCBase
声明了needFinalForward
方法,但是没有提供实现;HCBeitai
实现了beitaiForwarding:
方法。消息转发,将给HCBase
对象的needFinalForward
方法 转发给了 HCBeitai
对象的beitaiForwarding:
方法。注意:这两个方法的selector和signature都不一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSLog(@"vanney code log : selector is %@", NSStringFromSelector(aSelector)); NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector]; if (!methodSignature) { NSLog(@"vanney code log : inside create method signature"); methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:i"]; } return methodSignature; } - (void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(@"vanney code log : forwarding invocation selector is %@", NSStringFromSelector(anInvocation.selector)); NSLog(@"vanney code log : invocation is %@, and method signature is %@", anInvocation, anInvocation.methodSignature); if (anInvocation.selector == @selector(needFinalForward)) { NSLog(@"vanney code log : inside forwarding"); anInvocation.selector = NSSelectorFromString(@"beitaiForwarding:"); int *a; *a = 99; [anInvocation setArgument:a atIndex:2]; HCBeiTai *beiTai = [[HCBeiTai alloc] init]; [anInvocation invokeWithTarget:beiTai]; } else { NSLog(@"vanney code log : else forwarding"); [super forwardInvocation:anInvocation]; } }
转发的同时,还可以设置参数。把99传递给新的方法。来看看运行结果:
1 2 // final forwarding [baseOBJ needFinalForward];
runtime应用 Method Swizzling 可以在类的load
方法中,通过class_addMethod
class_replaceMethod
来改变方法的默认实现方式。因为load
方法执行时,所有类的方法都有了,但都还没有执行过一次
JSON -> Model 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 - (instancetype)initWithDict:(NSDictionary *)dict { if (self = [self init]) { //(1)获取类的属性及属性对应的类型 NSMutableArray * keys = [NSMutableArray array]; NSMutableArray * attributes = [NSMutableArray array]; /* * 例子 * name = value3 attribute = T@"NSString",C,N,V_value3 * name = value4 attribute = T^i,N,V_value4 */ unsigned int outCount; objc_property_t * properties = class_copyPropertyList([self class], &outCount); for (int i = 0; i < outCount; i ++) { objc_property_t property = properties[i]; //通过property_getName函数获得属性的名字 NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding]; [keys addObject:propertyName]; //通过property_getAttributes函数可以获得属性的名字和@encode编码 NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding]; [attributes addObject:propertyAttribute]; } //立即释放properties指向的内存 free(properties); //(2)根据类型给属性赋值 for (NSString * key in keys) { if ([dict valueForKey:key] == nil) continue; [self setValue:[dict valueForKey:key] forKey:key]; } } return self; }
该方法参考自[iOS] runtime 的使用场景–实战篇
KVO, KVC 海带学习
JSPatch JSPatch充分的应用了runtime的性质,详细内容可以参考我的这篇文章JSPatch源码分析
参考