标签:
运行时常用
什么是Runtime(前面的文章已经说的很清楚了,这里就简单的介绍一下)
OC中一切都被设计成了对象,我们都知道一个类被初始化成一个实例,这个实例是一个对象。实际上一个类本质上也是一个对象,在runtime中用结构体表示。
相关的定义:
1 /// 描述类中的一个方法 2 typedef struct objc_method *Method; 3 /// 实例变量 4 typedef struct objc_ivar *Ivar; 5 /// 类别Category 6 typedef struct objc_category *Category; 7 /// 类中声明的属性 8 typedef struct objc_property *objc_property_t;
类在runtime中的表示
1 //类在runtime中的表示 2 struct objc_class { 3 Class isa;//指针,顾名思义,表示是一个什么, 4 //实例的isa指向类对象,类对象的isa指向元类 5 #if !__OBJC2__ 6 Class super_class; //指向父类 7 const char *name; //类名 8 long version; 9 long info; 10 long instance_size 11 struct objc_ivar_list *ivars //成员变量列表 12 struct objc_method_list **methodLists; //方法列表 13 struct objc_cache *cache;//缓存 14 //一种优化,调用过的方法存入缓存列表,下次调用先找缓存 15 struct objc_protocol_list *protocols //协议列表 16 #endif 17 } OBJC2_UNAVAILABLE; 18 /* Use `Class` instead of `struct objc_class *` */
获取列表
有时候会有这样的需求,我们需要知道当前类中每个属性的名字(比如字典转模型,字典的Key和模型对象的属性名字不匹配)。
我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)。
1 unsigned int count; 2 //获取属性列表 3 objc_property_t *propertyList = class_copyPropertyList([self class], &count); 4 for (unsigned int i=0; i<count; i++) { const char *propertyname =" property_getName(propertyList[i]);" nslog(@"property----="">%@", [NSString stringWithUTF8String:propertyName]); 5 } 6 //获取方法列表 7 Method *methodList = class_copyMethodList([self class], &count); 8 for (unsigned int i; i<count; i++) { method method =" methodList[i];" nslog(@"method----="">%@", NSStringFromSelector(method_getName(method))); 9 } 10 //获取成员变量列表 11 Ivar *ivarList = class_copyIvarList([self class], &count); 12 for (unsigned int i; i<count; i++) { ivar myivar =" ivarList[i];" const char *ivarname =" ivar_getName(myIvar);" nslog(@"ivar----="">%@", [NSString stringWithUTF8String:ivarName]); 13 } 14 //获取协议列表 15 __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count); 16 for (unsigned int i; i<count; i++) { protocol *myprotocal =" protocolList[i];" const char *protocolname =" protocol_getName(myProtocal);" nslog(@"protocol----="">%@", [NSString stringWithUTF8String:protocolName]);
在Xcode上跑一下看看输出吧,需要给你当前的类写几个属性,成员变量,方法和协议,不然获取的列表是没有东西的。
注意,调用这些获取列表的方法别忘记导入头文件#import。
方法调用
让我们看一下方法调用在运行时的过程(参照前文类在runtime中的表示)
如果用实例对象调用实例方法,会到实例的isa指针指向的对象(也就是类对象)操作。
如果调用的是类方法,就会到类对象的isa指针指向的对象(也就是元类对象)中操作。
以上的过程给我带来的启发:
拦截调用
在方法调用中说到了,如果没有找到方法就会转向拦截调用。
那么什么是拦截调用呢。
拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理。
1 + (BOOL)resolveClassMethod:(SEL)sel; 2 + (BOOL)resolveInstanceMethod:(SEL)sel; 3 //后两个方法需要转发到其他的类处理 4 - (id)forwardingTargetForSelector:(SEL)aSelector; 5 - (void)forwardInvocation:(NSInvocation *)anInvocation;
动态添加方法
重写了拦截调用的方法并且返回了YES,我们要怎么处理呢?
有一个办法是根据传进来的SEL类型的selector动态添加一个方法。
首先从外部隐式调用一个不存在的方法:
1 //隐式调用方法 2 [target performSelector:@selector(resolveAdd:) withObject:@"test"]; 3 然后,在target对象内部重写拦截调用的方法,动态添加方法。 4 5 6 void runAddMethod(id self, SEL _cmd, NSString *string){ 7 NSLog(@"add C IMP ", string); 8 } 9 + (BOOL)resolveInstanceMethod:(SEL)sel{ 10 //给本类动态添加一个方法 11 if ([NSStringFromSelector(sel) isEqualToString:@"resolveAdd:"]) { 12 class_addMethod(self, sel, (IMP)runAddMethod, "v@:*"); 13 } 14 return YES; 15 }
其中class_addMethod的四个参数分别是:
关联对象
现在你准备用一个系统的类,但是系统的类并不能满足你的需求,你需要额外添加一个属性。
这种情况的一般解决办法就是继承。
但是,只增加一个属性,就去继承一个类,总是觉得太麻烦类。
这个时候,runtime的关联属性就发挥它的作用了。
1 //首先定义一个全局变量,用它的地址作为关联对象的key 2 static char associatedObjectKey; 3 //设置关联对象 4 objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串属性", OBJC_ASSOCIATION_RETAIN_NONATOMIC); //获取关联对象 5 NSString *string = objc_getAssociatedObject(target, &associatedObjectKey); 6 NSLog(@"AssociatedObject = %@", string);
objc_setAssociatedObject的四个参数:
1 enum { 2 OBJC_ASSOCIATION_ASSIGN = 0, 3 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 4 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 5 OBJC_ASSOCIATION_RETAIN = 01401, 6 OBJC_ASSOCIATION_COPY = 01403 7 };
如果你熟悉OC,看名字应该知道这几种策略的意思了吧。
其实,你还可以把添加和获取关联对象的方法写在你需要用到这个功能的类的类别中,方便使用。
1 //添加关联对象 2 - (void)addAssociatedObject:(id)object{ 3 objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 4 } 5 //获取关联对象 6 - (id)getAssociatedObject{ 7 return objc_getAssociatedObject(self, _cmd); 8 }
注意:这里面我们把getAssociatedObject方法的地址作为唯一的key,_cmd代表当前调用方法的地址。
方法交换
话不多说,这是参考Mattt大神在NSHipster上的文章自己写的代码。
1 #import "UIViewController+swizzling.h" 2 #import @implementation UIViewController (swizzling) 3 //load方法会在类第一次加载的时候被调用 4 //调用的时间比较靠前,适合在这个方法里做方法交换 5 + (void)load{ 6 //方法交换应该被保证,在程序中只会执行一次 7 static dispatch_once_t onceToken; 8 dispatch_once(&onceToken, ^{ 9 //获得viewController的生命周期方法的selector 10 SEL systemSel = @selector(viewWillAppear:); 11 //自己实现的将要被交换的方法的selector 12 SEL swizzSel = @selector(swiz_viewWillAppear:); 13 //两个方法的Method 14 Method systemMethod = class_getInstanceMethod([self class], systemSel); 15 Method swizzMethod = class_getInstanceMethod([self class], swizzSel); 16 //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败 17 BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod)); 18 if (isAdd) { 19 //如果成功,说明类中不存在这个方法的实现 20 //将被交换方法的实现替换到这个并不存在的实现 21 class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod)); 22 }else{ 23 //否则,交换两个方法的实现 24 method_exchangeImplementations(systemMethod, swizzMethod); 25 } 26 }); 27 } 28 - (void)swiz_viewWillAppear:(BOOL)animated{ 29 //这时候调用自己,看起来像是死循环 30 //但是其实自己的实现已经被替换了 31 [self swiz_viewWillAppear:animated]; 32 NSLog(@"swizzle"); 33 } 34 @end
在一个自己定义的viewController中重写viewWillAppear
- (void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; NSLog(@"viewWillAppear"); }
Run起来看看输出吧!
我的理解:
标签:
原文地址:http://www.cnblogs.com/iCocos/p/4782532.html