对Objective-C中runtime的理解
Objective-C是面向runtime(运行时)的语言,在应用程序运行的时候来决定函数内部实现什么以及做出其它决定的语言。程序员可以在程序运行时创建,检 查,修改类,对象和它们的方法,Objective-C runtime库也负责找出方法的最终执行代码。举例说明,当程序执行[object doSomething]时,不会直接调用doSomething这个方法,而是一条消息(doSomething)会发送给对象(object)。runtime库里有个c函数来传递这条消息,[object doSomething]这条代码将会被转化成c函数形式的函数调用:objc_msgSend(object,@selector(doMethodWith:)),函数内部按照以下顺序进行:
1.检查接受对象是否为nil,如果是,调用nil处理程序。直接表现就是什么都不做。
2.如果是在支持垃圾收集环境中,进行一些处理,由于ios不支持垃圾收集,所以我们不必关心这步。
3.检查类缓存中是不是已经有方法实现了,有的话,直接调用。(每个类都有一些经常执行的方法,有些方法则很少执行,当代码里执行到某一个方法时如果都要去类的方法列表里找一遍就太影响效率了,所以运行时系统会把调用过的方法缓存起来,以提高查找的效率,这一步就是在缓存里查找)
4.比较请求的选择器(就是方法名,比如doSomething)和类中定义的选择器,如果找到了,调用方法执行。
5.比较请求的选择器和父类中定义的选择器,找到就执行,找不到继续向父类的父类寻找,如果找到就执行,如果一直找到根类都找不到,那么将执行第6步。
6.调用resolveInstanceMethod:(或resolveClassMethod:)。这个方法返回一个BOOL值,如果返回no,将执行第7步,如果返回yes,则重新从上面的第1步开始。我们可以在这个方法里用runtime给我们提供的class_addMethod函数为类添加方法,这便是给类动态加方法的一个机会。
7.调用forwardingTargetForSelector:,这个方法返回一个对象,消息将转发给我们返回的对象,这是我们可以把通过以上6步都不能处理的方法转发到其他对象上的机会。
8.调用methodSignatureForSelector:,生成一个方法签名,创建一个NSInvocation(这个类把target,selector,方法签名和参数打包在一起)并传给forwardInvocation:方法。在forwardInvocation:里这是我们对这个消息进行处理的最后机会,里如果我们不做处理,默认的程序就会crash,抛出找不到该方法的错误信息。
在这个过程中我们可以给类添加方法(第6步),可以将消息转发到别的对象上(第7步),对NSInvocation(包含了这条消息的所有信息)进行我们想要的处理(第8步)。
下面我们将通过代码来看看如何在运行时给类添加方法。
假设我们有个Person类,
@interface Person : NSObject @end @implementation Person @end
这个类没有任何方法,接下来我们在执行的地方创建一个Person对象,出于测试目的,我们将person定为id类型,这样我们就可以随便调用一个NSObject子类的方法,这里我们调用tableView的reloadData方法。
id person = [[Person alloc] init];
[person reloadData];
然后执行以上语句,我们来详细看看执行[person reloadData]时的过程,[person reloadData]执行时其实是c函数objc_msgSend(person,@selector(reloadData)),进入函数内部,由于在Person类中我们并没有reloadData这个方法,按照我们前面列出的顺序,于是就走到了第6步,消息分发函数内部调用这个类中的resolveInstanceMethod:方法,我们可以在这一步中给person类加上reloadData方法,
@implementation Person static void testIMP(id self, SEL _cmd)
{
NSLog(@"reload data");
} + (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if ([NSStringFromSelector(aSEL) isEqualToString:@"reloadData"]) {//这里只是测试,只针对person调用了reloadData这个方法 class_addMethod([self class], aSEL, (IMP)testIMP, "v@:");//这样我们就在运行时为person类动态添加了reloadData方法。
return YES;//retrun YES后消息分发函数又会从头开始执行,由与我们上面已经给类添加了relodData方法,消息分发函数将会终止在第5步不再往下执行。
}
return NO;
} @end
对class_addMethod(Class cls, SEL name, IMP imp,const char *types)函数参数的说明:
Class cls:就是我们要给哪个类添加方法
SEL name:添加的方法叫什么名字(方法名)
IMP:一个函数指针,这是它的定义:typedef id (*IMP)(id self,SEL _cmd,...),由编译器生成,指向最终执行的函数的地址。该函数类型接受一个目标对象,一个选择器,以及其他参数。
const char *types:方法的返回类型、参数类型编码,我们可以用@encode(type)获得类型的字符串编码,比如上面的-(void)reloadData方法的返回类型和参数类型(有两个参数会隐式传递)编码为“v@:”,详细是v=@encode(void),@=encode(id),:=encode(SEL),(每个object方法会把id self和SEL _cmd隐式传递)
到这,我们就成功的为person类添加了他本来没有的方法。
下面我们继续来看通过运行时如何将消息转发。还是这个Person类,假如我们没有动态的给类添加方法,那么消息分发函数将会运行到第7步,我们可以在这一步通过方法forwardingTargetForSelector:把消息转发到另一个对象上。
假设有另一个类叫Computer类,它有实现reloadData方法。
@interface Computer : NSObject
-(void)reloadData;
@end @implementation Computer -(void)reloadData
{
NSLog(@"reload data");
} @end
下面我们就在Person类里将消息转发到Computer类去
@implementation Person - (id)forwardingTargetForSelector:(SEL)selector
{
if ([NSStringFromSelector(selector) isEqualToString:@"reloadData"]) { return [[Computer alloc] init];
}
return nil;
} @end
运行后,[person reloadData]真正执行的是Computer里的reloadData方法。
假如我们在第6步和第7步都不做处理,那么消息分发函数将会进入到第8步,这是我们处理这条消息的最后机会,如果我们不处理,程序将会抛出unrecognized selector sent to instance错误。接下来我们来看看如何处理。
因为person类里没有reloadData这个方法,所以返回了一个空的方法签名,最终程序报错崩溃,所以我们要在报错之前返回一个方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
获得方法签名后,会生成一个NSInvocation传给person类的以下方法,在这个方法中,我们就可以对该条消息处理了,我们可以改变消息的target,也可以改变selector,就看我们的需要了,以下我们将target改为Computer,将消息转发到Computer类去,最终执行的是Computer里的reloadData方法。
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
Computer *test = [[Computer alloc] init];
[anInvocation setTarget:test];
[anInvocation invoke];
}
利用运行时的这种动态特性,我们可以改变那些没有源代码的对象(包括系统对象)的行为。比如我们要对UINavigationController的pushViewController:animated:进行改写,每当我们调用pushViewController: animated:时就打印一下日志。我们用分类的方式给UINavigationController添加一个方法,然后再我们需要这个功能的地方运行一下我们的分类方法,这之后的所有pushViewController: animated:将会执行我们指定的行为。
@interface UINavigationController (Mypush) + (void)tellmePushVC; @end @implementation UINavigationController (Mypush) typedef void (*voidIMP)(id, SEL, ...); //用来保存系统原有的实现
static voidIMP sOrigPushVCIMP = NULL; //我们自定义的实现
static void tellmePush(id self, SEL _cmd, id viewcontroller,bool flag) { //打印日志
NSLog(@"push push vc"); // 调用系统原有的实现
if (sOrigPushVCIMP) {
sOrigPushVCIMP(self, _cmd, viewcontroller,flag);
}
} + (void)tellmePushVC
{
//确保以下代码只运行一次,否则将会引发递归循环
static dispatch_once_t onceToken; dispatch_once(&onceToken,^{ Class class = [self class]; SEL sel = @selector(pushViewController:animated:); Method origMethod = class_getInstanceMethod(class,sel); sOrigPushVCIMP = (void *)method_getImplementation(origMethod);//把系统原有的实现存起来 method_setImplementation(origMethod, (IMP)tellmePush);//把新实现替换旧实现
});
}
@end
在我们需要的地方写上[UINavigationController tellmePushVC];这之后凡是我们调用了系统的pushViewController: animated:方法,就会先打印日志,再执行系统原有的pushViewController: animated:方法。通过以上手段,就达到了给系统方法添加行为的目的。
以上都是对方法的操作,我们还可以利用运行时的公开的c函数接口修改类的实例变量的值。还是以Person类举例,假如Person类里有个_age实例变量,下面我们就在运行时修改_age的值。
@interface Person : NSObject
- (void)printAge;
@end @implementation Person {
NSString *_age;
} - (id)init
{
if ((self = [super init])) {
_age = @"";
}
return self; } - (void)printAge
{
Ivar ivar = class_getInstanceVariable([self class], "_age"); object_setIvar(self, ivar, @""); NSLog(@"age %@",_age);
} @end
这样我们便在运行时把实例变量的值修改了。
我们都知道分类只能给类添加方法,不能添加实例变量,但是利用运行时,我们可以给分类动态添加实例变量,这样即使我们不创建子类,也能够对类动态扩展。这个功能称之为关联引用。需要注意的是通过分类添加的实例变量并不是真正的实例变量,所以在对象复制和归档的时候要特别注意。我们给Person类创建一个分类,添加属性address。
#import "Person.h" @interface Person (AddProperty) @property (strong, nonatomic) NSString *address; @end #import "Person+AddProperty.h"
#import <objc/runtime.h> @implementation Person (AddProperty) static char key;//由于一个对象可以有很多个关联引用,所以需要一个key来区分,一般我们用一个静态变量的地址作为key。 - (void)setAddress:(NSString *)address
{
objc_setAssociatedObject(self, &key, address, OBJC_ASSOCIATION_COPY);
} - (NSString *)address
{
return objc_getAssociatedObject(self, &key);
}
@end
这样就完成了在分类中给类添加实例变量。
对Objective-C中runtime的理解的更多相关文章
- 理解Objective C 中id
什么是id,与void *的区别 id在Objective C中是一个类型,一个complier所认可的Objective C类型,跟void *是不一样的,比如一个 id userName, 和vo ...
- 刨根问底Objective-C Runtime
http://chun.tips/blog/2014/11/05/bao-gen-wen-di-objective%5Bnil%5Dc-runtime-(2)%5Bnil%5D-object-and- ...
- 浅谈Objective—C中的面向对象特性
Objective-C世界中的面向对象程序设计 面向对象称程序设计可能是现在最常用的程序设计模式.如何开发实际的程序是存在两个派系的-- 面向对象语言--在过去的几十年中,很多的面向对象语言被发明出来 ...
- Objective C内存管理之理解autorelease------面试题
Objective C内存管理之理解autorelease Autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当前的A ...
- Java 泛型 <? super T> 中 super 怎么 理解?与 < ? extends T>有何不同?
Java 泛型 <? super T> 中 super 怎么 理解?与 extends 有何不同? 简介 前两篇文章介绍了泛型的基本用法.类型擦除以及泛型数组.在泛型的使用中,还有个重要的 ...
- 刨根问底Objective-C Runtime(4)- 成员变量与属性
http://chun.tips/blog/2014/11/08/bao-gen-wen-di-objective[nil]c-runtime(4)[nil]-cheng-yuan-bian-lian ...
- Fouandation(NSString ,NSArray,NSDictionary,NSSet) 中常见的理解错误区
Fouandation 中常见的理解错误区 1.NSString //快速创建(实例和类方法) 存放的地址是 常量区 NSString * string1 = [NSString alloc]init ...
- linux中socket的理解
对linux中socket的理解 一.socket 一般来说socket有一个别名也叫做套接字. socket起源于Unix,都可以用“打开open –> 读写write/read –> ...
- IOS 中runtime 不可变数组__NSArray0 和__NSArrayI
IOS 中runtime 不可变数组__NSArray0 和__NSArrayI 大家可能都遇到过项目中不可变数组避免数组越界的处理:runtime,然而有时候并不能解决所有的问题,因为类簇不一样 # ...
随机推荐
- vue-知乎日志
1.项目API来源 2.项目地址 3.截图 4.功能 首页 轮播图 动态消息 下拉刷新 动态 ...
- 树莓派如何连接WIFI
hello,大家好,我是叶秋! 上一期呢,给大家介绍了如何给树莓派安装系统,有细心的朋友就会发现上一期安装系统的文章漏了一点点知识,不知道机智的你是否有发现呢~~(尴尬
- python教程(三)·函数与模块
函数,这和数学中的函数有点关联,但又不是完全等价 概念 不说的这么官方,我就已自己的理解来表达 ^_^ 在数学中,把一个或多个值(输入x)进行一定的计算或者映射,得到一个值(输出y),这个计算或者映射 ...
- C语言复合数据类型
C语言数据类型非常丰富,其中结构体的使用非常广泛,也有一点复杂,这一讲我们主要学习结构体的使用方法,同时也会学习到联合.枚举以及typedef的使用,因为结构体最为复杂,使用最广,所以我 ...
- 20145209 2016-2017-2 《Java程序设计》第9周学习总结
20145209 2016-2017-2 <Java程序设计>第9周学习总结 教材学习内容总结 JDBC简介 撰写应用程序是利用通信协议对数据库进行指令交换,以进行数据的增删查找 JDBC ...
- 20145209刘一阳《JAVA程序设计》第1周学习总结
20145209刘一阳<JAVA程序设计>第1周学习总结 本周任务 了解Java基础知识 了解JVM.JRE与JDK,并下载.安装.测试JDK 了解PATH.CLASSPATH.SOURC ...
- springboot与activemq的使用
1.springboot和activemq的使用相对来说比较方便了,我在网上看了很多其他的资料,但是自己写出来总是有点问题所以,这里重点描述一下遇到的一些问题. 2.至于activemq的搭建和spr ...
- 100万套PPT模板,包含全宇宙所有主题类型PPT,绕宇宙100圈,持续更新
100万套PPT模板,包含全宇宙所有主题类型PPT(全部免费,都是精品,没有一张垃圾不好看的PPT,任何一张PPT拿来套入自己的信息就可以立马使用),绕宇宙100圈,任意一个模板在某文库上都价不菲.强 ...
- Android Studio怎样创建App项目
然后等待大约N分钟: 默认的是Android模式, 改为Project模式更符合我们的习惯:
- Python零基础入门必知
Python自学知识点总结 //2018.10.09 1. Python(英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/), 是一种面向对象的解释型计算机程序设计语言,由荷兰人Guido ...