前言:

         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. 【01】markdown特殊说明

    [01]说明 Markdown 的目标是实现「易读易写」. 可读性,无论如何,都是最重要的.一份使用 Markdown 格式撰写的文件应该可以直接以纯文本发布,并且看起来不会像是由许多标签或是格式指令 ...

  2. SQL server将查询到的多行结果,拼接成字符串(列转行)

    select stuff(( ,,'') as UserNamestr 注释:查询出tabname表中的UserName列的所有内容,并将内容拼接成UserNamestr

  3. BNUOJ4359 无爱编号

    无爱编号 Time Limit: 2000ms Memory Limit: 65536KB   64-bit integer IO format: %lld      Java class name: ...

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

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

  5. 更新yum源导致yum不可用

    当安装和yum配置相关的包后报yum模块找不到 yum install -y yum-utils device-mapper-persistent-data lvm2 yum list|grep yu ...

  6. 01-offsetWidth和offsetHeight

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  7. RHEL7网卡命名规则

    systemd 和 udev 引入了一种新的网络设备命名方式:一致网络设备命名(CONSISTENT NETWORK DEVICE NAMING).根据固件.拓扑.位置信息来设置固定名字,带来的好处是 ...

  8. BZOJ 4503 两个串 ——FFT

    [题目分析] 定义两个字符之间的距离为 (ai-bi)^2*ai*bi 如果能够匹配,从i到i+m的位置的和一定为0 但这和暴力没有什么区别. 发现把b字符串反过来就可以卷积用FFT了. 听说KMP+ ...

  9. BZOJ2246 [SDOI2011]迷宫探险 【记忆化搜索dp + 概率】

    题目 输入格式 输出格式 仅包含一个数字,表示在执行最优策略时,人物活着走出迷宫的概率.四舍五入保留3位小数. 输入样例 4 3 3 2 .$. A#B A#C @@@ 143 37 335 85 9 ...

  10. LinkedList的构造函数有哪些

    LinkedList构造函数有(两种): public LinkedList() public LinkedList(Collection<? extends E> c) /** * Co ...