前言:

         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的情况下,为各种类(包括系统的类)做很多操作,具体包括:

1.   增加

增加函数:class_addMethod

增加实例变量:class_addIvar

增加属性:@dynamic标签,或者class_addMethod,因为属性其实就是由getter和setter函数组成

增加Protocol:class_addProtocol (说实话我真不知道动态增加一个protocol有什么用,-_-!!)

2。  获取

获取函数列表及每个函数的信息(函数指针、函数名等等):class_getClassMethod method_getName ...

获取属性列表及每个属性的信息:class_copyPropertyList property_getName

获取类本身的信息,如类名等:class_getName class_getInstanceSize

获取变量列表及变量信息:class_copyIvarList

获取变量的值

3.    替换

将实例替换成另一个类:object_setClass

将函数替换成一个函数实现:class_replaceMethod

直接通过char *格式的名称来修改变量的值,而不是通过变量

iOS - OC 使用运行时来获取并修改类的更多相关文章

  1. iOS开发——底层OC篇&运行时常用

    运行时常用 什么是Runtime(前面的文章已经说的很清楚了,这里就简单的介绍一下) 我们写的代码在程序运行过程中都会被转化成runtime的C代码执行,例如[target doSomething]; ...

  2. 为什么说OC是运行时语言?什么是动态类型、动态绑定、动态加载?

    转载:https://www.cnblogs.com/dxb123456/p/5525343.html 动态: 主要是将数据类型的确定由编译时,推迟到了运行时. 这个问题其实浅涉及到两个概念,运行时和 ...

  3. oc的运行时系统

    Objective-C is a class-based object system. Each object is an instance of some class; the object's i ...

  4. iOS - OC RunLoop 运行循环/消息循环

    1.RunLoop 1)运行循环: 运行循环在 iOS 开发中几乎不用,但是概念的理解却非常重要. 同一个方法中的代码一般都在同一个运行循环中执行,运行循环监听 UI 界面的修改事件,待本次运行循环结 ...

  5. iOS OC环信实时语音切换听筒免提听不到声音报错:AVAudioSessionErrorCodeBadParam

    出现这个报错:AVAudioSessionErrorCodeBadParam 先看看你的问题是不是在切换听筒免提的时候 听不到声音了, 不是的可以继续搜索去了   问题在这里 把圈住的那个货换成这个就 ...

  6. Java泛型类型擦除与运行时类型获取

    Java的泛型大家都知道是类型擦除的方式实现的,“编译器会进行泛型擦除”是一个常识了(实际擦除的是参数和自变量的类型).“类型擦除” 并非像许多开发者认为的那样,在 <..> 符号内的东西 ...

  7. Android 中运行时权限获取联系人信息 Demo

    代码比较简单... AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <m ...

  8. Eclipse运行时无法加载主类的解决方法

    测试代码: package javastudy; class Person { public static void main(String[] args) { PersonCeshi pp=new ...

  9. eclipse导入新项目后,运行时找不到主类解决办法

    最近在学习多线程,今天下了一套源码,导入到eclipse里后,随便找了个带main()的类试了一下,找不到主类. 首先想到的解决办法是把工程clean一下,并没有用.去网上找了一个遍终于找到了管用的方 ...

随机推荐

  1. IFC构件位置数据与revit模型中对应构件位置数据对比

    IFC构件位置数据与revit模型中对应构件位置数据对比

  2. qt操作excel報錯解決

    如果電腦上沒有office,需要判斷,否则,会报错 onecore\com\combase\catalog\catalog.cxx()\combase.dll!00007FFF1DF823CB: (c ...

  3. Spring cloud微服务安全实战-4-11Zuul网关安全开发(四)

    限流,有个现成的开源项目可以帮助我们来做网关上的限流 用最新的这个版本 在pom.xml加入引用. 在限流的过程中需要存一些信息,可以存在数据库里 也可以存在redis里.这里我们演示存到数据库里 比 ...

  4. jsp、freemarker、velocity、thymeleaf

    1.概述在java领域,表现层技术主要有三种, (1)jsp; (2)freemarker; (3)velocity; (4)thymeleaf; 2.jsp优点: 1.功能强大,可以写java代码 ...

  5. Qt编写自定义控件66-光晕时钟

    一.前言 在上一篇文章写了个高仿WIN10系统的光晕日历,这次来绘制一个光晕的时钟,也是在某些网页上看到的效果,时分秒分别以进度条的形式来绘制,而且这个进度条带有光晕效果,中间的日期时间文字也是光晕效 ...

  6. 【Leetcode_easy】925. Long Pressed Name

    problem 925. Long Pressed Name solution1: class Solution { public: bool isLongPressedName(string nam ...

  7. [BJOI2019] 奥术神杖 [取log+AC自动机+dp]

    题面 传送门 思路 首先,看到这个乘起来开根号的形式,应该能想到用取$\log$的方式做一个转化: $\sqrt[n]{\prod_i a_i}=\frac{1}{n}\sum_i \log_b a_ ...

  8. Django 之上下文处理器和中间件

    一.上下文处理器 上下文处理器是可以返回一些数据,在全局模板中都可以使用.比如登录后的用户信息,在很多页面中都需要使用,那么我们可以放在上下文处理器中,就没有必要在每个视图函数中都返回这个对象. 在s ...

  9. jQuery插件——imgbox(点击图片查看大图)

    需要的资源: 需要对应的js代码和css样式,大家可以通过www.htmldrive.net平台下载,也可以在我文章的底部下载.对应的资源如下,将资源引入页面(别忘了JQuery): 注意:jQuer ...

  10. springboot备份mysql后发送邮件并删除备份文件,支持win和Linux

    首先加入springboot的邮箱依赖 <!--邮箱依赖--> <!-- https://mvnrepository.com/artifact/org.springframework ...