最近一直在听朋友用runtime进行装逼,就去学习了一下,顺便整理出来方便以后查看。
runtime是底层C语言的API,从表面理解就是动态运行时。它会在程序运行时把OC转换成C,比如:[self name]会装换成objc_msgSend(self,@selector(name)).
/// An opaque type that represents a method in a class definition.
typedefstruct objc_method *Method;
/// An opaque type that represents an instance variable.
typedefstruct objc_ivar *Ivar;
/// An opaque type that represents a category.
typedefstruct objc_category *Category;
/// An opaque type that represents an Objective-C declared property.
typedefstruct objc_property *objc_property_t;
struct objc_class {
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;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
3.runtime的实际应用
(1)获取类中相关属性
1.首先建立一个类,如下:
#import
@interface Student : NSObject
{
int _age;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *number;
@property (nonatomic, copy) NSString *sex;
- (void)setAge:(int)age;
- (int)age;
2.在主控制器里调用,记得导入#import
unsigned int outCount = 0;
//获取成员变量列表
Ivar *ivarList = class_copyIvarList([Student class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivarList[i];
const char *ivarName = ivar_getName(ivar);
NSLog(@"ivar = %@",[NSString stringWithUTF8String:ivarName]);
}
//获取属性列表
objc_property_t *propertyList = class_copyPropertyList([Student class], &outCount);
for (int i = 0; i < outCount; i ++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property = %s",propertyName);
}
//获取方法列表
Method *methodList = class_copyMethodList([Student class], &outCount);
for (int i = 0; i < outCount; i ++) {
Method method = methodList[i];
NSLog(@"method = %@",NSStringFromSelector(method_getName(method)));
}
//获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([Student class], &outCount);
for (int i = 0; i < outCount; i ++) {
Protocol *protocol = protocolList[i];
const char *protocolName = protocol_getName(protocol);
NSLog(@"protocol = %s",protocolName);
}
3.打印结果如下:
4.应用场景
可以利用遍历类的属性进行快速的归档
在没用runtime之前,归档是这样的:
对于属性少的还好说,如果有几十上百个,那就要写上百个[aCoderencodeObject:self.nameforKey:@"name"];那样就会非常麻烦,然而利用runtime就非常方便了,如图:
(2)交换方法
应用场景:比如想要获取当前控制器,就可以用自己的方法来交换系统的方法,要先建立一个UIViewController的分类,代码如下:
该代码是在网上复制粘贴的。
#import "UIViewController+swizzling.h"
#import @implementation UIViewController (swizzling)
//load方法会在类第一次加载的时候被调用
//调用的时间比较靠前,适合在这个方法里做方法交换
+ (void)load{
//方法交换应该被保证,在程序中只会执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//获得viewController的生命周期方法的selector
SEL systemSel = @selector(viewWillAppear:);
//自己实现的将要被交换的方法的selector
SEL swizzSel = @selector(swiz_viewWillAppear:);
//两个方法的Method
Method systemMethod = class_getInstanceMethod([self class], systemSel);
Method swizzMethod = class_getInstanceMethod([self class], swizzSel);
//首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
if (isAdd) {
//如果成功,说明类中不存在这个方法的实现
//将被交换方法的实现替换到这个并不存在的实现
class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
}else{
//否则,交换两个方法的实现
method_exchangeImplementations(systemMethod, swizzMethod);
}
});
}
- (void)swiz_viewWillAppear:(BOOL)animated{
//这时候调用自己,看起来像是死循环
//但是其实自己的实现已经被替换了
[self swiz_viewWillAppear:animated];
NSLog(@"swizzle");
}
@end
在一个自己定义的viewController中重写viewWillAppear- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSLog(@"viewWillAppear");
}
(3)拦截调用
//当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。
+ (BOOL)resolveClassMethod:(SEL)sel;
//和第一个方法相似,只不过处理的是实例方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。
- (id)forwardingTargetForSelector:(SEL)aSelector;
//将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。
- (void)forwardInvocation:(NSInvocation *)anInvocation;
(4)动态添加方法
class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)
其中class_addMethod的四个参数分别是:
1.Class cls 给哪个类添加方法,比如self
2.SEL name 添加的方法,是重写的拦截调用传进来的selector。
3.IMP imp 方法的实现,C方法的方法实现可以直接获得。如果是OC方法,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;获得方法的实现。
4.代表有无参数的方法。
首先从外部调用一个不存在的方法
//调用不存在的方法
[student performSelector:@selector(resolveAdd:) withObject:@"test"];
然后再student对象内部重写拦截方法
void runAddMethod(id self, SEL _cmd, NSString *string){
NSLog(@"add C IMP ", string);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
//给本类动态添加一个方法
if ([NSStringFromSelector(sel) isEqualToString:@"resolveAdd:"]) {
class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
}
return YES;
}
(5)关联对象
关联对象可以给一个类添加新的属性。
总结:runtime在实际开发中用到的很少,用的较多就是交换方法,但是一定要保证只交换一次,否则会弄的很乱,其次用的多的是遍历类的属性进行归档,不过现在都有很多第
三方库,也很少用到了。不过了解一下其机制还是不错的。