前言:

         Objective C的runtime技术功能非常强大,能够在运行时获取并修改类的各种信息,包括获取方法列表、属性列表、变量列表,修改方法、属性,增加方法,属性等等,本文对相关的几个要点做了一个小结。

目录:

(1)使用class_replaceMethod/class_addMethod函数在运行时对函数进行动态替换或增加新函数

(2)重载forwardingTargetForSelector,将无法处理的selector转发给其他对象

(3)重载resolveInstanceMethod,从而在无法处理某个selector时,动态添加一个selector

(4)使用class_copyPropertyList及property_getName获取类的属性列表及每个属性的名称

(5) 使用class_copyMethodList获取类的所有方法列表

(6) 总结

(1)在运行时对函数进行动态替换 : class_replaceMethod

使用该函数可以在运行时动态替换某个类的函数实现,这样做有什么用呢?最起码,可以实现类似windows上hook效果,即截获系统类的某个实例函数,然后塞一些自己的东西进去,比如打个log什么的。

示例代码:

IMP orginIMP;
NSString * MyUppercaseString(id SELF, SEL _cmd)
{
NSLog(@"begin uppercaseString");
NSString *str = orginIMP (SELF, _cmd);()
NSLog(@"end uppercaseString");
return str;
}
-(void)testReplaceMethod
{
Class strcls = [NSString class];
SEL oriUppercaseString = @selector(uppercaseString);
orginIMP = [NSStringinstanceMethodForSelector:oriUppercaseString]; ()
IMP imp2 = class_replaceMethod(strcls,oriUppercaseString,(IMP)MyUppercaseString,NULL);()
NSString *s = "hello world";
NSLog(@"%@",[s uppercaseString]];
}

执行结果为:

begin uppercaseString

end uppercaseString

HELLO WORLD

这段代码的作用就是

(1)得到uppercaseString这个函数的函数指针存到变量orginIMP中

(2)将NSString类中的uppercaseString函数的实现替换为自己定义的MyUppercaseString

(3) 在MyUppercaseString中,先执行了自己的log代码,然后再调用之前保存的uppercaseString的系统实现,这样就在系统函数 执行之前加入自己的东西,后面每次对NSString调用uppercaseString的时候,都会打印出log来

与class_replaceMethod相仿,class_addMethod可以在运行时为类增加一个函数。

(2)当某个对象不能接受某个selector时,将对该selector的调用转发给另一个对象:- (id)forwardingTargetForSelector:(SEL)aSelector

forwardingTargetForSelector是NSObject的函数,用户可以在派生类中对其重载,从而将无法处理的selector转 发给另一个对象。还是以上面的uppercaseString为例,如果用户自己定义的CA类的对象a,没有uppercaseString这样一个实例 函数,那么在不调用respondSelector的情况下,直接执行[a performSelector:@selector"uppercaseString"],那么执行时一定会crash,此时,如果CA实现了 forwardingTargetForSelector函数,并返回一个NSString对象,那么就相对于对该NSString对象执行了 uppercaseString函数,此时就不会crash了。当然实现这个函数的目的并不仅仅是为了程序不crash那么简单,在实现装饰者模式时,也 可以使用该函数进行消息转发。

示例代码:

 @interface CA : NSObject
-(void)f; @end @implementation CA - (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(uppercaseString))
{
return@"hello world";
}
}

测试代码:

CA *a = [CA new];

 NSString * s = [a performSelector:@selector(uppercaseString)];

NSLog(@"%@",s);

测试代码的输出为:HELLO WORLD

ps: 这里有个问题,CA类的对象不能接收@selector(uppercaseString),那么如果我在 forwardingTargetForSelector函数中用class_addMethod给CA类增加一个uppercaseString函数, 然后返回self,可行吗?经过试验,这样会crash,此时CA类其实已经有了uppercaseString函数,但是不知道为什么不能调用,如果此 时new一个CA类的对象,并返回,是可以成功的。

(3)当某个对象不能接受某个selector时,向对象所属的类动态添加所需的selector

+ (BOOL) resolveInstanceMethod:(SEL)aSEL

这个函数与forwardingTargetForSelector类似,都会在对象不能接受某个selector时触发,执行起来略有差别。前者的目 的主要在于给客户一个机会来向该对象添加所需的selector,后者的目的在于允许用户将selector转发给另一个对象。另外触发时机也不完全一 样,该函数是个类函数,在程序刚启动,界面尚未显示出时,就会被调用。

在类不能处理某个selector的情况下,如果类重载了该函数,并使用class_addMethod添加了相应的selector,并返回YES,那么后面forwardingTargetForSelector 就不会被调用,如果在该函数中没有添加相应的selector,那么不管返回什么,后面都会继续调用 forwardingTargetForSelector,如果在forwardingTargetForSelector并未返回能接受该 selector的对象,那么resolveInstanceMethod会再次被触发,这一次,如果仍然不添加selector,程序就会报异常

代码示例一:

 @implementation CA
void dynamicMethodIMP(id self, SEL _cmd)
{
printf("SEL %s did not exist\n",sel_getName(_cmd));
} + (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(t))
{
class_addMethod([selfclass], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [superresolveInstanceMethod:aSEL];
} @end 测试代码: CA * ca = [CA new]
[ca performSelector:@selector(t)];

执行结果

SEL t did not exist

示例代码二:

@implementation CA

void dynamicMethodIMP(id self, SEL _cmd)
{
printf("SEL %s did not exist\n",sel_getName(_cmd));
} + (BOOL) resolveInstanceMethod:(SEL)aSEL
{
return YES;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(uppercaseString))
{
return @"hello world";
}
}
测试代码 : a = [[CA alloc]init];
NSLog(@"%@",[a performSelector:@selector(uppercaseString)];

该测试代码的输出为:HELLO WORLD

对于该测试代码,由于a没有uppercaseString函数,因此会触发resolveInstanceMethod,但是由于该函数并没有添加selector,因此运行时发现找不到该函数,会触发

forwardingTargetForSelector 函数,在forwardingTargetForSelector函数中,返回了一个NSString "hello world",因此会由该string来执行uppercaseString函数,最终返回大写的hello world。

示例代码三:
@implementation CA + (BOOL) resolveInstanceMethod:(SEL)aSEL
{
return YES;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return nil;
}
测试代码:
a = [[CA alloc]init];
NSLog(@"%@",[a performSelector:@selector(uppercaseString)];

这段代码的执行顺序为:

(1):首先在程序刚执行,AppDelegate都还没有出来时,resolveInstanceMethod就被触发,

(2):等测试代码执行时,forwardingTargetForSelector被调用

(3):由于forwardingTargetForSelector返回了nil,因此运行时还是找不到uppercaseString selector,这时又会触发resolveInstanceMethod,由于还是没有加入selector,于是会crash。

(4) 使用class_copyPropertyList及property_getName获取类的属性列表及每个属性的名称

u_int               count;
objc_property_t* properties= class_copyPropertyList([UIView class], &count);
for (int i = ; i < count ; i++)
{
const char* propertyName = property_getName(properties[i]);
NSString *strName = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding];
NSLog(@"%@",strName);
} 以上代码获取了UIView的所有属性并打印属性名称, 输出结果为:
skipsSubviewEnumeration
viewTraversalMark
viewDelegate
monitorsSubtree
backgroundColorSystemColorName
gesturesEnabled
deliversTouchesForGesturesToSuperview
userInteractionEnabled
tag
layer
_boundsWidthVariable
_boundsHeightVariable
_minXVariable
_minYVariable
_internalConstraints
_dependentConstraints
_constraintsExceptingSubviewAutoresizingConstraints
_shouldArchiveUIAppearanceTags

(5) 使用class_copyMethodList获取类的所有方法列表

获取到的数据是一个Method数组,Method数据结构中包含了函数的名称、参数、返回值等信息,以下代码以获取名称为例:

u_int               count;
Method* methods= class_copyMethodList([UIView class], &count);
for (int i = ; i < count ; i++)
{
SEL name = method_getName(methods[i]);
NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
NSLog(@"%@",strName);
} 代码执行后将输出UIView所有函数的名称,具体结果略。
其他一些相关函数: .SEL method_getName(Method m) 由Method得到SEL
.IMP method_getImplementation(Method m) 由Method得到IMP函数指针
.const char *method_getTypeEncoding(Method m) 由Method得到类型编码信息
.unsigned int method_getNumberOfArguments(Method m)获取参数个数
.char *method_copyReturnType(Method m) 得到返回值类型名称
.IMP method_setImplementation(Method m, IMP imp) 为该方法设置一个新的实现

(6)总结

总而言之,使用runtime技术能做些什么事情呢?

可以在运行时,在不继承也不category的情况下,为各种类(包括系统的类)做很多操作,具体包括:

.   增加

增加函数:class_addMethod
增加实例变量:class_addIvar
增加属性:@dynamic标签,或者class_addMethod,因为属性其实就是由getter和setter函数组成
增加Protocol:class_addProtocol (说实话我真不知道动态增加一个protocol有什么用,-_-!!) 。 获取 获取函数列表及每个函数的信息(函数指针、函数名等等):class_getClassMethod method_getName ...
获取属性列表及每个属性的信息:class_copyPropertyList property_getName
获取类本身的信息,如类名等:class_getName class_getInstanceSize
获取变量列表及变量信息:class_copyIvarList
获取变量的值 . 替换 将实例替换成另一个类:object_setClass
将函数替换成一个函数实现:class_replaceMethod
直接通过char *格式的名称来修改变量的值,而不是通过变量

Objective C运行时(runtime)技术总结,好强大的runtime的更多相关文章

  1. 运行时设计(Design at Run-time)

    1.定义 传统软件开发必须经历“设计时”和“运行时”两个阶段,运行时设计,顾名思义,就是在软件运行过程中,对软件进行实时设计修改,而无需再次进行编译,用户即可使用. “运行时设计(Design at ...

  2. iOS - OC 使用运行时来获取并修改类

    前言:          Objective C的runtime技术功能非常强大,能够在运行时获取并修改类的各种信息,包括获取方法列表.属性列表.变量列表,修改方法.属性,增加方法,属性等等,本文对相 ...

  3. 重温CLR(十八) 运行时序列化

    序列化是将对象或对象图转换成字节流的过程,反序列化是将字节流转换回对象图的过程.在对象和字节流之间转换是很有用的机制. 1 应用程序的状态(对象图)可轻松保存到磁盘文件或数据库中,并在应用程序下次运行 ...

  4. Java运行时环境---内存划分

    背景:听说Java运行时环境的内存划分是挺进BAT的必经之路. 内存划分: Java程序内存的划分是交由JVM执行的,而不像C语言那样需要程序员自己买单(C语言需要程序员为每一个new操作去配对del ...

  5. JVM 专题六:运行时数据区(一)概述

    1. 运行时数据区架构图 2. 内存 内存是非常重要的系统资源,是硬盘和cpu的中间仓库及桥梁,承载着操作系统和应用程序的实时运行.JVM内存布局规定了JAVA在运行过程中内存申请.分配.管理的策略, ...

  6. 第28 章 : 理解容器运行时接口 CRI

    理解容器运行时接口 CRI CRI 是 Kubernetes 体系中跟容器打交道的一个非常重要的部分.本文将主要分享以下三方面的内容: CRI 介绍 CRI 实现 相关工具 CRI 介绍 在 CRI ...

  7. Objective-O Runtime 运行时初体验

    Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理.这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一 ...

  8. iOS开发——高级特性&Runtime运行时特性详解

    Runtime运行时特性详解 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的动态特性,使这门古老的语言焕发生机.主要内容如下: 引言 ...

  9. Objective-C Runtime 运行时之四:Method Swizzling

    理解Method Swizzling是学习runtime机制的一个很好的机会.在此不多做整理,仅翻译由Mattt Thompson发表于nshipster的Method Swizzling一文. Me ...

随机推荐

  1. Python3 中 configparser 模块解析配置的用法详解

    configparser 简介 configparser 是 Pyhton 标准库中用来解析配置文件的模块,并且内置方法和字典非常接近.Python2.x 中名为 ConfigParser,3.x 已 ...

  2. django的rest framework框架——安装及基本使用

    一.django的FBV 和 CBV 1.FBV(基于函数的视图): urlpatterns = [ url(r'^users/', views.users), ] def users(request ...

  3. Python基础数据类型之集合

    Python基础数据类型之集合 集合(set)是Python基本数据类型之一,它具有天生的去重能力,即集合中的元素不能重复.集合也是无序的,且集合中的元素必须是不可变类型. 一.如何创建一个集合 #1 ...

  4. Zuma (区间DP)

    Genos recently installed the game Zuma on his phone. In Zuma there exists a line of n gemstones, the ...

  5. 九度oj 题目1447:最短路

    题目描述: 在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt.但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线 ...

  6. 九度oj 题目1019:简单计算器

    题目描述:     读入一个只包含 +, -, *, / 的非负整数计算表达式,计算该表达式的值. 输入:     测试输入包含若干测试用例,每个测试用例占一行,每行不超过200个字符,整数和运算符之 ...

  7. gevent 使用踩坑

    简单介绍 gevent 基本概念:   调度器: hub          上下文切换管理: switch          主循环: loop   协程: greenlet gevent 特性:  ...

  8. Spoj-VISIBLEBOX Decreasing Number of Visible Box

    Shadowman loves to collect box but his roommates woogieman and itman don't like box and so shadowman ...

  9. hdu 4965 矩阵快速幂 矩阵相乘性质

    Fast Matrix Calculation Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Jav ...

  10. java多线程总结一:线程的两种创建方式及比较

    1.线程的概念:线程(thread)是指一个任务从头至尾的执行流,线程提供一个运行任务的机制,对于java而言,一个程序中可以并发的执行多个线程,这些线程可以在多处理器系统上同时运行.当程序作为一个应 ...