OC是运行时语言,只有在程序运行时,才会去肯定对象的类型,并调用类与对象相应的方法。利用runtime机制让咱们能够在程序运行时动态修改类、对象中的全部属性、方法,就算是私有方法以及私有属性都是能够动态修改的。本文旨在对runtime的部分特性小试牛刀,更多更全的方法能够参考系统API文件<objc/runtime.h>,demo例子能够参见CSDN的runtime高级编程系列文章。编程
咱们出发吧!api
先看一个很是日常的Father类:函数
#import <Foundation/Foundation.h>@interface Father : NSObject @property (nonatomic, assign) int age;@end
#import "Father.h"@interface Father () { NSString *_name; }- (void)sayHello;@end@implementation Father- (id)init { if (self = [super init]) { _name = @"wengzilin"; [_name copy]; self.age = 27; } return self; }- (void)dealloc { [_name release]; _name = nil; [super dealloc]; }- (NSString *)description { return [NSString stringWithFormat:@"name:%@, age:%d", _name, self.age]; }- (void)sayHello { NSLog(@"%@ says hello to you!", _name); }- (void)sayGoodbay { NSLog(@"%@ says goodbya to you!", _name); }
若是你没接触过runtime,那当我问你:“Father以外的类能控制的属性有哪些?能控制的方法有哪些?”时,你估计会回答:“咱们能够访问age属性,不能访问_name变量;能够访问age的setter/getter方法,其余方法都不行”。这种回答是OK的,由于教科书上以及面向对象的思想告诉咱们,事实如此。可是,我会说,有一种方法是APPLE容许的并且能够不受这些规则限制的途径能够作到想访问什么就访问什么、想修改什么就修改什么,那就是本文的主题:RUNTIME!atom
如今咱们简单地将本文的主题分为两部分:(1)控制私有变量 (2)控制私有函数,由于两者所用的runtime差别较大,函数部分会复杂一些spa
(1)控制变量.net
想要控制一个类的私有变量,那第一步就要知道这个类到底有哪些隐藏的变量,以及这些隐藏的变量类型是什么。或许你会说:“这不是很显然吗?.h文件都写着呢!”。若是你真这么想就特错特错了,不少正规的写法都是尽可能避免在.h文件中出现私有变量,绝大部分都会选择方法.m文件的extension中,extension就是匿名的category。我猜想这也是一种防止hack的措施吧。无论这些变量放在何处,runtime均可以让他们无所遁形!先看代码,看不懂没关系,后面会有解释:code
- (void)tryMember { Father *father = [[Father alloc] init]; NSLog(@"before runtime:%@", [father description]); unsigned int count = 0; Ivar *members = class_copyIvarList([Father class], &count); for (int i = 0 ; i < count; i++) { Ivar var = members[i]; const char *memberName = ivar_getName(var); const char *memberType = ivar_getTypeEncoding(var); NSLog(@"%s----%s", memberName, memberType); } }
显示以下:orm
2015-03-17 16:10:28.003 WZLCodeLibrary[38574:3149577] before runtime:name:wengzilin, age:272015-03-17 16:10:28.003 WZLCodeLibrary[38574:3149577] _name----@"NSString"2015-03-17 16:10:28.003 WZLCodeLibrary[38574:3149577] _age----i
从log中咱们知道了,Father类有两个变量,一个公开的包装成属性的age, 类型是int,一个花括号{}内的私有变量_name,类型是NSString。代码中标红色的部分就是runtime.h的api,对象
class_copyIvarList:获取类的全部属性变量,count记录变量的数量IVar是runtime声明的一个宏,是实例变量的意思,instance variable,在runtime中定义为 typedef struct objc_ivar *Ivariblog
var_getName:将IVar变量转化为字符串
ivar_getTypeEncoding:获取IVar的类型
若是咱们如今想对_name动手,不通过Father赞成偷偷修改它呢?咱们继续往下作:(接着上面的代码)
Ivar m_name = members[0]; object_setIvar(father, m_name, @"zhanfen"); NSLog(@"after runtime:%@", [father description]);
显示以下:
2015-03-17 16:10:28.004 WZLCodeLibrary[38574:3149577] after runtime:name:zhanfen, age:27
咱们发现,_name属性被强制改过来了,有wengzilin改成如今zhanfen。
(2)控制私有函数
对于私有变量,咱们能作的顶多修改变量的值,但对于私有函数,咱们能够玩很是多的花样,好比:在运行时动态添加新的函数、修改私有函数、交换其中两个私有函数的实现、替换私有函数...
一样地,控制的第一步是得到Father类的全部私有方法,咱们能够获得.m文件中全部有显式实现的方法以及属性变量的setter+getter方法都会被找到:
- (void)tryMemberFunc { unsigned int count = 0; Method *memberFuncs = class_copyMethodList([Father class], &count);//全部在.m文件显式实现的方法都会被找到 for (int i = 0; i < count; i++) { SEL name = method_getName(memberFuncs[i]); NSString *methodName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding]; NSLog(@"member method:%@", methodName); } }
显示以下:
2015-03-17 17:02:33.343 WZLCodeLibrary[38748:3170794] member method:setAge:2015-03-17 17:02:33.343 WZLCodeLibrary[38748:3170794] member method:age2015-03-17 17:02:33.344 WZLCodeLibrary[38748:3170794] member method:sayHello2015-03-17 17:02:33.344 WZLCodeLibrary[38748:3170794] member method:sayGoodbay2015-03-17 17:02:33.344 WZLCodeLibrary[38748:3170794] member method:description2015-03-17 17:02:33.344 WZLCodeLibrary[38748:3170794] member method:dealloc2015-03-17 17:02:33.344 WZLCodeLibrary[38748:3170794] member method:init
Method:runtime声明的一个宏,表示一个方法,typedef struct objc_method *Method;
class_copyMethodList:获取全部方法
method_getName:读取一个Method类型的变量,输出咱们在上层中很熟悉的SEL
=========
接下来咱们试着添加新的方法试试(这种方法等价于对Father类添加Category对方法进行扩展):
- (void)tryAddingFunction { class_addMethod([Father class], @selector(method::), (IMP)myAddingFunction, "i@:i@"); }//具体的实现,即IMP所指向的方法int myAddingFunction(id self, SEL _cmd, int var1, NSString *str) { NSLog(@"I am added funciton"); return 10; }
- (void)tryMemberFunc { //动态添加方法 [self tryAddingFunction]; count = 0; memberFuncs = class_copyMethodList([Father class], &count);//全部在.m文件显式实现的方法都会被找到 for (int i = 0; i < count; i++) { SEL name = method_getName(memberFuncs[i]); NSString *methodName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding]; NSLog(@"member method:%@", methodName); } //尝试调用新增的方法 Father *father = [[Father alloc] init]; [father method:10 :@"111"];//当你敲入father实例后,是没法得到method的提示的,只能靠手敲。并且编译器会给出"-method" not found的警告,能够忽略 [father release]; }
输出结果:
2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:method::2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:setAge:2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:age2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:sayHello2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:sayGoodbay2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:description2015-03-17 17:02:33.346 WZLCodeLibrary[38748:3170794] member method:dealloc2015-03-17 17:02:33.346 WZLCodeLibrary[38748:3170794] member method:init
咱们能够看到,method::方法的确被添加进类中了。有童鞋会问,若是在其余类文件中实例化Father类,还能调用到-method方法吗?答案是能够的,我试验过,在MRC下尽管没法得到代码提示,但请坚决不移地敲入[father method:xx :xx]方法!(在ARC下会报no visible @interface 错误)
接下来,咱们拿系统函数玩玩,目标是让NSString函数的大小写转换功能对调,让APPLE乱套:
- (void)tryMethodExchange { Method method1 = class_getInstanceMethod([NSString class], @selector(lowercaseString)); Method method2 = class_getInstanceMethod([NSString class], @selector(uppercaseString)); method_exchangeImplementations(method1, method2); NSLog(@"lowcase of WENG zilin:%@", [@"WENG zilin" lowercaseString]); NSLog(@"uppercase of WENG zilin:%@", [@"WENG zilin" uppercaseString]); }
输出结果:
2015-03-17 17:20:16.073 WZLCodeLibrary[38861:3180978] lowcase of WENG zilin:WENG ZILIN2015-03-17 17:20:16.290 WZLCodeLibrary[38861:3180978] uppercase of WENG zilin:weng zilin
咱们能够看到,结果乱套了!
私有函数控制还有其余多种玩法,这里就不一一举例了,算是抛砖引玉吧!