runtime运行时

oc 的runtime技术功能非常强大,能够在运行时获取各种信息,例如,获取方法列表,属性列表,变量列表,修改方法,属性,增加方法,属性等等,我们也可以引入库#include<objc/runtime.h>进入头文件进行查看

那OC是怎么实现动态调用的呢?下面我们来看看OC通过发送消息来达到动态调用的秘密。假如在OC中写了这样的一个代码:

[obj makeText];
其中obj是一个对象,makeText是一个函数名称。对于这样一个简单的调用。在编译时RunTime会将上述代码转化成
objc_msgSend(obj,@selector(makeText));
首先我们来看看obj这个对象,iOS中的obj都继承于NSObject。
@interface NSObject <nsobject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}</nsobject>

在NSObjcet中存在一个Class的isa指针。然后我们看看Class:
typedef struct objc_class *Class;
struct objc_class {
  Class isa; // 指向metaclass
  Class super_class ; // 指向其父类
  const char *name ; // 类名
  long version ; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取
  long info; // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
  long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);
  struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址
  struct objc_method_list **methodLists ; // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;
  struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率;
  struct objc_protocol_list *protocols; // 存储该类遵守的协议
    }


Class isa :指向metaclass,也就是静态的Class。一般一个Obj对象中的isa会指向普通的Class,这个Class中存储普通成员变量和对 象方法(“-”开头的方法).

普通Class中的isa指针指向静态Class,静态Class中存储static类型成员变量和类方法(“+”开头的方 法)。

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

使用该函数可以在运行时动态的替换某个类的函数实现,这样做有何作用,可以实现类似windows上的hook效果,即获取系统类的某个实例函数,然后塞进一些自己的东西

IMP orginIMP;
NSString * MyUppercaseString(id SELF, SEL _cmd)
{
    NSLog(@"begin uppercaseString");
    NSString *str = orginIMP (SELF, _cmd);(3NSLog(@"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);(2NSString *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

forwardingTagetForSelector是NSObject的函数,用户可以在派生类中对其重载,从而将无法处理的selector转发给另一个对象。还是以上面的uppercaseString为例,如果用户自己定义的CA类的对象a,没有uppercaseString这样的一个实例函数,那么在不调用respondSelector的情况下,直接执行[a perforSelector:@selector”uppercaseString”]那么执行时一定会crash,此时如果CA实现了forwardingTargetForSelector函数,并且返回一个NSSstring对象,那么就相对于该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
这里有个问题,CA类的对象不能直接接收@selector(uppercaseString),那么如果我在forwardingTargetForSelector函数中调用class_addMethod给CA类添加一个uppercaseString函数,然后返回self,可行么,经过试验,这样程序会崩溃,此时CA类其实已经有了uppercaseString函数,但是不知道为什么不能调用,如果此时new一个CA类的对象,并返回,是可以成功的

(3)当某个对象不能接收某个selector时,向对象所属的类的动态添加所需要的selector:+(BOOL)resolveInstanceMethod:(SEL)aSEL

这个函数与forwardTargetForSelector类似,都会在对象不能接受某个selector转发给另一个对象,另外,触发时机也不完全一样,该函数是个类函数,在程序启动,界面尚未显示出时,就会被调用

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

@implementation CA
void dynamicMethodIMP(id self,SEL _cmd)
{
printf("SEL %s did not\n",sel_getName(_cmd));
}
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(t))
{
class_addMethod([selfclass],aSEL,(IMP) dynamicMethodIMP,"v@:");
return YES;

其中types参数为"i@:@“,按顺序分别表示:
 i :   返回值类型int,若是v则表示void
 @ :   参数id(self)
 : :   SEL(_cmd)
 @ :   id(str)
  }
}

class_addMethod 方法中的参数
 cls:被添加方法的类
 name:可以理解为方法名
 imp:实现这个方法的函数
 types:一个定义该函数返回值类型和参数类型的字符串
@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没有对于该测试代码,由于a没有uppercaseString函数,因此会触发resolveInstanceMethod,但是由于该函数并没有添加selector,因此运行时发现找不到该函数,会触发
forwardingTargetForSelector函数,在forwardingTargetForSelector函数中,返回了一个NSString “hello world”,因此会由该string来执行uppercaseString函数,最终返回大写的hello world。

(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);
}


输出结果:
skipsSubviewEnumeration
viewTraversalMark
viewDelegate
monitorsSubtree
backgroundColorSystemColorName
gesturesEnabled
deliversTouchesForGesturesToSuperview
userInteractionEnabled
tag
layer
_boundsWidthVariable
_boundsHeightVariable
_minXVariable
_minYVariable
_internalConstraints
_dependentConstraints
_constraintsExceptingSubviewAutoresizingConstraints
_shouldArchiveUIAppearanceTags

使用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);
}

其他一些相关方法的用法:
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) 为该方法设置一个新的实现

总结:

总而言之,使用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
获取变量的值

替换
将实例替换成另一个类:object_setClass
将函数替换成一个函数实现:class_replaceMethod
直接通过char *格式的名称来修改变量的值,而不是通过变量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值