Objective C运行时(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 return@”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

代码示例一:

1 @implementation CA
3 void dynamicMethodIMP(id self, SEL _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, “v@:”);
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 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。

复制代码
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 *格式的名称来修改变量的值,而不是通过变量

参考资料:(1)Objective-C Runtime Reference

           (2)深入浅出Cocoa之消息   

           (3)objective-c 编程总结(第六篇)运行时操作 - 方法交换

            (4)Runtime of Objective-C

转自:http://justsee.iteye.com/blog/2019541

附例:【获取某个类的方法和属性】
- (void)methodsOfTheClass{
u_int count;
Method* methods= class_copyMethodList(NSClassFromString(@”UIView”), &count);
for (int i = 0; i < count ; i++)
{
SEL name = method_getName(methods[i]);
NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
NSLog(@”METHOD: %@”,strName);
}

u_int ccccc;
objc_property_t*    properties= class_copyPropertyList(NSClassFromString(@"LSApplicationProxy"), &ccccc);
for (int i = 0; i < ccccc ; i++)
{
    const char* propertyName = property_getName(properties[i]);
    NSString *strName = [NSString  stringWithCString:propertyName encoding:NSUTF8StringEncoding];
    NSLog(@"property = %@",strName);
}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值