runtime的几种技术

         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);(3)
    NSLog(@"end uppercaseString");
    return str;
}
-(void)testReplaceMethod
{
      Class strcls = [NSString class];
      SEL  oriUppercaseString = @selector(uppercaseString);
      orginIMP = [NSStringinstanceMethodForSelector:oriUppercaseString];  (1)
      IMP imp2 = class_replaceMethod(strcls,oriUppercaseString,(IMP)MyUppercaseString,NULL);(2)
      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那么简单,在实现装饰者模式时,也可以使用该函数进行消息转发。

示例代码:

 1 @interface CA : NSObject
 3 -(void)f;
 4
 5 @end
 6
 7 @implementation CA
 8
 9 - (id)forwardingTargetForSelector:(SEL)aSelector
11 {
13     if (aSelector == @selector(uppercaseString))
15     {
17         [email protected]"hello world";
19     }
21 }

测试代码:

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,程序就会报异常


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

代码示例一:

 @implementation CA

 void dynamicMethodIMP(id selfSEL _cmd)

 5 {   

 7      printf("SEL %s did not exist\n",sel_getName(_cmd));

 9 }

10

11 + (BOOL) resolveInstanceMethod:(SEL)aSEL

13 {

15     if (aSEL == @selector(t))

17     {

19         class_addMethod([selfclass], aSEL, (IMP) dynamicMethodIMP, "[email protected]:");

21         return YES;

23     }

25     return [superresolveInstanceMethod:aSEL];

27 }

28

29 @end

 

测试代码:

  CA * ca = [CA new]

  [ca performSelector:@selector(t)]; 

  

执行结果

SEL t did not exist


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

示例代码二:

@implementation CA

void dynamicMethodIMP(id selfSEL _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。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

示例代码三:

@implementation CA

+ (BOOL) resolveInstanceMethod:(SEL)aSEL

{

    return  YES;

}

- (id)forwardingTargetForSelector:(SEL)aSelector

{

    return nil;

  }

 测试代码:

1  a = [[CA alloc]init];

2  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 = 0; 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 = 0; i < count ; i++)

{

    SEL name = method_getName(methods[i]);

    NSString *strName = [NSString  stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];

    NSLog(@"%@",strName);

}

  代码执行后将输出UIView所有函数的名称,具体结果略。

其他一些相关函数:

1.SEL method_getName(Method m) 由Method得到SEL
2.IMP method_getImplementation(Method m)  由Method得到IMP函数指针
3.const char *method_getTypeEncoding(Method m)  由Method得到类型编码信息
4.unsigned int method_getNumberOfArguments(Method m)获取参数个数
5.char *method_copyReturnType(Method m)  得到返回值类型名称
6.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 *格式的名称来修改变量的值,而不是通过变量

 

时间: 2024-08-10 19:49:13

runtime的几种技术的相关文章

HTML5和HLS协议两种技术完美结合解决移动端网页播放问题

什么是HTML5 我们需要先了解一下HTML是什么.HTML的英文全称为Hyper Text Markup Language,即超文本标记语言.HTML5是HTML的一个新版本.HTML 不是一种编程语言,而是一种标记语言 (markup language).HTML5是对 HTML 标准的第五次修订. 其主要的目标是将互联网语义化,以便更好地被人类和机器阅读,并同时提供更好地支持各种媒体的嵌入.HTML5的设计目的是为了在移动设备上支持多媒体.新的语法特征被引进以支持这一点,如video.au

SSO是一种技术?

SSO不是一种技术,只是一种思想 下面是在一个论坛里看到的提问: 在这里打个比方,一个大型网站,它有自己的blog系统,也有自己的bbs,也有自己的书签功能等等.在开发的时候,肯定不是建立一个Web项目然后完成后部署的.我想应该是先做blog,又做bbs等,然后这些作为独立的程序跑在不同服务器上.这里,就有个问题我想不明白,难道一定要做单点登录才能对这些应用程序进行统一的身份认证?没有其他办法?如果登录后,每个系统都需要session里面的数据,又怎么进行传递?还有,这个程序之间如果想通讯,又是

ASP、JSP、PHP 三种技术比较

ASP.JSP.PHP 三种技术比较 目前,最常用的三种动态网页语言有ASP(Active Server Pages),JSP(JavaServer Pages),PHP (Hypertext Preprocessor). 简 介 : ASP全名Active Server Pages,是一个WEB服务器端的开发环境,利用它可以产生和执行动态的.互动的.高性能的WEB服务应用程序.ASP采用脚本语言VBScript(Java script)作为自己的开发语言. PHP是一种跨平台的服务器端的嵌入式

J2EE的十三种技术

? ? 引言 ? ? J2EE的十三种技术在网上一搜多的是,比如附录中的那篇文档就已经涵盖的很全了,但是作为应用级开发者并不一定要对这十三种技术都掌握,而且这十三种技术中哪些重要哪些不重要也存在一定的区别,所以我对一些重要的技术提炼出来放在概要里,方便读者快速掌握,而且学习也好有偏重 ? ? 概要 ? ? 这个也可以作为学习Java的路线,其实Java主要的技术包括Java基础部分,也就是面向对象,集合,界面,线程,文件以及网络:以及往网页方面走的JSP,Servlet,JS,其实如果读者接触过

关于PHP、JAVA、.NET这三种技术的区别

关于PHP.JAVA..NET这三种技术的区别 一.语言: PHP:PHP产生与1994年,其语法混合了C.Java.Perl 和他自创的一些编程语法:PHP是嵌入在HTML中执行的:它也是一种解释性语言.早期的PHP并非完全的面向对象编程语言,到了PHP4以后的版本才开始有了面向对象的概念. JAVA:JAVA产生与1995年,JAVA语言和JAVA平台统称为JAVA:它语法与C语言和C++语言很接近,并且JAVA是面向对象编程语言,JAVA是编译性语言,可以先将JAVA源码编译成.class

J2EE的十三种技术(规范)

J2EE的十三种技术(规范)  Java数据库连接(JDBC) JDBC API以一个统一的方式访问各种数据库.与ODBC类似,JDBC将开发者和私有数据库之间的问题隔离开来.由于它建立在Java上,因此JDBC可以提供平台无关的数据库访问. JDBC定义了4种不同的驱动,具体来说,包括有: 类型1:JDBC-ODBC桥 在JDBC刚产生时,JDBC-ODBC桥是非常有用的.通过它,开发者可以使用JDBC来访问一个ODBC数据源.缺点是,它需要在客户机器上安装有一个ODBC驱动,该机器通常是应该

我们都忽略了Html5的力量,如果只看成一种技术就大错特错了!

第一部分:Html5市场的力量 我们太忽略Html5的市场力量了.如果你把Html5当作一种技术,就大错特错了!忘掉你的产品,忘掉你的技术,想想移动时代的信息传播和消费场景.作为2B,我们实在是没有重视Html5的市场力量!app开发中Html5的力量,你想到了吗? 我先抛出两个问题: 第一.现在每个月你下载几个新app?相比之下,你每天要看多少网页和微信圈的信息和文章? 第二.你多久才去app商城找软件?就算的软件上了app商城,能上靠前的排名,还是被淹没? 关注Html5,首先是要看到Htm

源代码加密软件的三种技术 你了解多少?

所谓源代码加密软件是指以源代码文件为对象,采用数据泄露防护技术,有效防止源代码文件泄露和扩散,它主要起源于传统数据防泄密市场.到目前为止很多人对软件源代码加密技术的了解还不够深,随着技术不断升级软件源代码加密技术经历了传统透明加密技术.边缘沙盒防泄密技术以及革新DSA数据安全隔离数据等,接下来几维安全将为大家详细介绍这三种技术,一起去看看. 传统透明加密技术 透明加密技术典型代表即文档加密软件,其核心在于文档创建时即加密,并与用户.权限相结合.加密后,授权用户正常双击打开使用,非授权用户则显示为

30天学30种技术

30天学30种技术 http://www.cnblogs.com/endless-on/tag/30%E5%A4%A9%E5%AD%A630%E7%A7%8D%E6%8A%80%E6%9C%AF/ https://blog.openshift.com/day-21-docker-the-missing-tutorial/