▲点击上方“CocoaChina”关注即可免费学习iOS开发
来源:http://blog-lision.com/
看完本篇之后你将获得:
了解什么是runtime
知道可以利用runtime做到哪些事情
掌握用runtime开发的常用方法
Runtime是开源的,任何时候你都可以从http://opensource.apple.com获取。事实上查看 Objective-C 源码是我理解它是如何工作的第一种方式,在某些问题上要比读苹果的文档要好。
引言
相信很多从事iOS开发的小伙伴们都听过这样一句形容runtime的话:
runtime就像是iOS开发中的妖怪,谁都听说过,但少有人见(用)到过!
这句话是某知名培训机构内某老师对学生们说的一句话,相信不少人尤其是初学的萌新们还没了解过runtime,听了这句话就被吓到了!直接在心里给runtime打个一打标签[危险,慎用,底层,难,用不到,不用掌握]。以至于很多人做了有一段时间的iOS开发却依然对其一知半解……
定义
Objective-C 的 Runtime 是一个运行时库(Runtime Library),它是一个主要使用 C 和汇编写的库,为 C 添加了面相对象的能力并创造了 Objective-C。这就是说它在类信息(Class information) 中被加载,完成所有的方法分发,方法转发,等等。Objective-C runtime 创建了所有需要的结构体。
其实就在下个人的理解:runtime就是丫Objective-C 的灵魂!Objective-C之所以叫Objective-C是因为他比C语言不同,是面向对象的。但是Objective-C为什么有面相对象的能力?就是因为有runtime这个鬼东西!
进阶
我们为什么要学习runtime?
其实runtime所能做的还不止这些,你甚至可以利用它来把一个Class A的实例对象a在程序中当作Class B的实例对象来用。所以很多iOS开发者把runtime叫做obj-C的黑魔法!
常用方法
先来个最简单最基本的也是几乎所有runtime文必备的例子:
obj-C: [obj func];
runtime:objc_msgSend(obj, @selector(func);
很多初学者除了知道runtime把对象的方法调用转化成消息发送的代码之后就不知道其他的了,但是显然仅仅知道上述的转化并没有什么“吡-”用,我们来看runtime中比较常用(实用)的几种基本用法:
首先定义一个简单的类Person
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
然后在需要遍历对象的属性时
id personClass = objc_getClass("Person");
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList(personClass, &outCount);
for (int i = 0; i
objc_property_t property = properties[i];
printf("%s:%s\n", property_getName(property), property_getAttributes(property));
}
free(properties);
这时就会打印出这个类对象的属性相关信息:
name:T@"NSString",C,N,V_name
age:Tq,N,V_age
[消息转发]指的就是我上面提到的动态方法解析,重定向以及消息转发,我们先来看一张图:
动态方法解析:
从上图可以知道,当对一个实例对象obj发送一条消息func时[obj func],当前obj如果没有对func实现对应的方法,那么就runtime会调用+ (BOOL)resolveInstanceMethod:(SEL)sel方法允许开发者对当前受到的消息func做出响应,这就是动态方法解析。
继续拿上面的Person举例子,给Person类加一个体重weight属性
@property (nonatomic, assign) NSInteger weight;
然后在.m文件中加入一下代码
@implementation Person
@dynamic weight; //避免自动生成getter/setter方法
//重写resolveInstanceMethod方法,动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(setWeight:)) {
class_addMethod([self class], sel, (IMP)setPropertyDynamic, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
//用来响应setWeight的c语言方法
void setPropertyDynamic(id self, SEL _cmd) {
NSLog(@"Dynamic setWeight");
}
@end
然后可以在代码就调用Person的setWeight方法
Person *lision = [[Person alloc] init];
lision.weight = 75;
这时候如果不重写+ (BOOL)resolveInstanceMethod:(SEL)sel方法本应该异常的,但是你可以发现程序会打印出信息:
Dynamic setWeight
重定向:
那么还是看图说话,如果没有重写+ (BOOL)resolveInstanceMethod:(SEL)sel方法,那就就会调用- (id)forwardingTargetForSelector:(SEL)aSelector方法,把这个消息让另一个对象来处理,这次叫做重定向。
跟着上面的例子走,先另一个类People用来等待重定向:
@interface People : NSObject
@end
给新写的People类加一个weight方法,但是注意:People没有weight属性!
- (NSInteger)weight
{
return 70;
}
接下来我们重写- (id)forwardingTargetForSelector:(SEL)aSelector方法:
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(weight)) {
People *people = [[People alloc] init];
return people;
}
return [super forwardingTargetForSelector:aSelector];
}
然后我们在刚才的执行代码中:
NSLog(@"weight = %ld", lision.weight);
然后运行,经历过上面的例子你肯定知道不会异常啦,而且你会发现虽然你给weight属性赋值明明是75,可是打印结果是:weight = 70。这就是Person类- (id)forwardingTargetForSelector:(SEL)aSelector方法中把这条信息抛给了people对象,调用了People类的weight方法!
消息转发:
那么如果上面的两个方法都没有重写,并且消息依然是当前对象没有实现的方法,runtime才会启用消息转发调用– (void)forwardInvocation:(NSInvocation *)anInvocation,需要注意的是很多文章没有提到这个方法花费代价较大,如果要实现把消息转发类似的功能建议最好使用重定向,而且再调用这个方法前runtime会先调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法。
我们跟着上面的例子,继续给Person类加入属性:
@property (nonatomic, copy) NSString *ID;
以及上面提到的两个方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(setID:)) {
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v@:"];
//"v@:"代表的意思参见Objective-C Type Encodings,这里的意思是返回值为空
return sig;
}
return nil;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
People *people = [[People alloc] init];
if ([people respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:people];
}
}
别忘记了在People类中添加对应的方法:
- (void)setID:(NSString *)ID
{
NSLog(@"People setID: %@", ID);
}
最后,我门只需要在执行代码块中加入代码:
lision.ID = @"xxxx";
结果显而易见,相信各位都知道将会打印信息:
People setID: xxxx
写在最后
其实runtime就是我们无时无刻不在用的东西,只是人们习惯对看不到的东西怀有恐惧心理而已。我们平时的obj-C代码都是被runtime转译为c和汇编语言运行的。我个人认为大公司为什么喜欢在面试时问runtime相关的东西是因为大公司往往不仅仅要会干活的人,它还会要求这些会干活的人知道其中的原理!我们自己也应该要求自己或多或少的理解这些原理,知道我们为什么写出的obj-C代码经历了哪些过程run到我们的设备上,不要敲了很多年的代码还是一只只会干活的码农。
微信号:CocoaChinabbs
▲长按二维码“识别”关注即可免费学习 iOS 开发
月薪十万、出任CEO、赢娶白富美、走上人生巅峰不是梦
--------------------------------------
商务合作QQ:2408167315
投稿邮箱:[email protected]