ITPub博客

首页 > > > 事岀无常必有妖-iOS捉妖记之(Runtime)

事岀无常必有妖-iOS捉妖记之(Runtime)

作者:u_453e1a12781dea 时间:2016-12-28 15:13:47 0 删除 编辑

点击上方“蓝字”可以关注我们哦



|转载自:简书

|原文链接:http://www.jianshu.com/p/bba1ac264873



写在前面


看完本篇之后你将获得:


1了解什么是runtime

2知道可以利用runtime做到哪些事情

3掌握用runtime开发的常用方法


Runtime是开源的,任何时候你都可以从http://opensource.apple.com获取。事实上查看 Objective-C 源码是我理解它是如何工作的第一种方式,在某些问题上要比读苹果的文档要好。你也可以下载笔者在写这篇文章时最新objc4-680.tar.gz。


引言


相信很多从事iOS开发的小伙伴们都听过这样一句形容runtime的话:


runtime就像是iOS开发中的妖怪,谁都听说过,但少有人见(用)到过!


这句话是某知名培训机构内某老师对学生们说的一句话(原谅我每年都会去down培训视频大致的过一遍),相信不少人尤其是初学的萌新们还没了解过runtime,听了这句话就被吓到了!直接在心里给runtime打个一打标签[危险,慎用,底层,难,用不到,不用掌握]。以至于很多人做了有一段时间的iOS开发却依然对其一知半解……


定义


Objective-C 的 Runtime 是一个运行时库(Runtime Library),它是一个主要使用 C 和汇编写的库,为 C 添加了面相对象的能力并创造了 Objective-C。这就是说它在类信息(Class information) 中被加载,完成所有的方法分发,方法转发,等等。Objective-C runtime 创建了所有需要的结构体。


其实就在下个人的理解:runtime就是丫Objective-C 的灵魂!Objective-C之所以叫Objective-C是因为他比C语言不同,是面向对象的。但是Objective-C为什么有面相对象的能力?就是因为有runtime这个鬼东西!


进阶


我们为什么要学习runtime?


1、runtime可以遍历对象的属性


2、runtime可以动态添加/修改属性,动态添加/修改/替换方法,动态添加/修改/替换协议


3、runtime可以动态创建类/对象/协议等等


4、runtime可以方法拦截调用


其实runtime所能做的还不止这些,你甚至可以利用它来把一个Class A的实例对象a在程序中当作Class B的实例对象来用。所以很多iOS开发者把runtime叫做obj-C的黑魔法!


常用方法


先来个最简单最基本的也是几乎所有runtime文必备的例子:


obj-C: [obj func];
runtime:objc_msgSend(obj, @selector(func);


很多初学者除了知道runtime把对象的方法调用转化成消息发送的代码之后就不知道其他的了,但是显然仅仅知道上述的转化并没有什么“吡-”用,我们来看runtime中比较常用(实用)的几种基本用法:


遍历对象的属性


首先定义一个简单的类Person


  @interface Person : NSObject

 

  @property (nonatomic, copy) NSString *name;

  @property (nonatomic, assign) NSInteger age;

  @end

   

然后在需要遍历对象的属性时


id personClass = objc_getClass("Person");

  unsigned int outCount;

 

  objc_property_t *properties = class_copyPropertyList(personClass, &outCount);

  for (int i = 0; i

   

这时就会打印出这个类对象的属性相关信息:


name:T@”NSString”,C,N,V_name
age:Tq,N,V_age


消息转发


其实讲道理的话,消息转发不是三言两语就可以讲清的,我只能在这里粗浅的介绍一下,让大家会用而已。周末可能会写一篇详解runtime的文章来细致的介绍runtime中的术语以及runtime消息发送机制,动态方法解析,重定向以及消息转发。包括我们熟用但是可能不知道其源码是什么样的id,SEL,objc_object,objc_class以及其结构也会详细的讲解到,感兴趣的小伙伴可以关注我,这样我的新文章会第一时间推送给你。


我们这里的[消息转发]指的就是我上面提到的动态方法解析,重定向以及消息转发,我们先来看一张图:


消息转发流程.png


动态方法解析


从上图可以知道,当对一个实例对象obj发送一条消息func时[obj func],当前obj如果没有对func实现对应的方法,那么就runtime会调用+ (BOOL)resolveInstanceMethod:(SEL)sel方法允许开发者对当前受到的消息func做出响应,这就是动态方法解析。


继续拿上面的Person举例子,给Person类加一个体重weight属性


@property (nonatomic, assign) NSInteger weight;

   

然后在.m文件中加入一下代码


@implementation Person

<a href='http://www.jobbole.com/members/Dynamic2016'>@dynamic</a> weight;  //避免自动生成getter/setter方法

 

//重写resolveInstanceMethod方法,动态方法解析

+ (BOOL)resolveInstanceMethod:(SEL)sel

{

    if (sel == @selector(setWeight:)) {

        class_addMethod([self class], sel, (IMP)setPropertyDynamic, "v@:");

 

        return YES;

    }

    return [super resolveInstanceMethod:sel];

}

 

//用来响应setWeight的c语言方法

void setPropertyDynamic(id self, SEL _cmd) {

    NSLog(@"Dynamic setWeight");

}

@end

   

然后可以在代码就调用Person的setWeight方法


Person *lision = [[Person alloc] init];

lision.weight = 75;

   

这时候如果不重写+ (BOOL)resolveInstanceMethod:(SEL)sel方法本应该异常的,但是你可以发现程序会打印出信息:


Dynamic setWeight


重定向


那么还是看图说话,如果没有重写+ (BOOL)resolveInstanceMethod:(SEL)sel方法,那就就会调用- (id)forwardingTargetForSelector:(SEL)aSelector方法,把这个消息让另一个对象来处理,这次叫做重定向。


跟着上面的例子走,先另一个类People用来等待重定向:


@interface People : NSObject

@end

   

给新写的People类加一个weight方法,但是注意:People没有weight属性!


- (NSInteger)weight

{

    return 70;

}

   

接下来我们重写- (id)forwardingTargetForSelector:(SEL)aSelector方法:


- (id)forwardingTargetForSelector:(SEL)aSelector

{

    if (aSelector == @selector(weight)) {

        People *people = [[People alloc] init];

        return people;

    }

    return [super forwardingTargetForSelector:aSelector];

}

   

然后我们在刚才的执行代码中:


NSLog(@"weight = %ld", lision.weight);

   

然后运行,经历过上面的例子你肯定知道不会异常啦,而且你会发现虽然你给weight属性赋值明明是75,可是打印结果是:weight = 70。这就是Person类- (id)forwardingTargetForSelector:(SEL)aSelector方法中把这条信息抛给了people对象,调用了People类的weight方法!


消息转发


那么如果上面的两个方法都没有重写,并且消息依然是当前对象没有实现的方法,runtime才会启用消息转发调用– (void)forwardInvocation:(NSInvocation *)anInvocation,需要注意的是很多文章没有提到这个方法花费代价较大,如果要实现把消息转发类似的功能建议最好使用重定向,而且再调用这个方法前runtime会先调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法。


我们跟着上面的例子,继续给Person类加入属性:


@property (nonatomic, copy) NSString *ID;

   

以及上面提到的两个方法:


- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

{

    if (aSelector == @selector(setID:)) {

        NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v@:"];

//"v@:"代表的意思参见Objective-C Type Encodings,这里的意思是返回值为空

        return sig;

    }

    return nil;

}

 

- (void)forwardInvocation:(NSInvocation *)anInvocation

{

    People *people = [[People alloc] init];

    if ([people respondsToSelector:anInvocation.selector]) {

        [anInvocation invokeWithTarget:people];

    }

}

   

别忘记了在People类中添加对应的方法:


- (void)setID:(NSString *)ID

{

    NSLog(@"People setID: %@", ID);

}

   

最后,我门只需要在执行代码块中加入代码:


lision.ID = @"xxxx";

   

结果显而易见,相信各位都知道将会打印信息:


People setID: xxxx


写在最后


其实runtime就是我们无时无刻不在用的东西,只是人们习惯对看不到的东西怀有恐惧心理而已。我们平时的obj-C代码都是被runtime转译为c和汇编语言运行的。我个人认为大公司为什么喜欢在面试时问runtime相关的东西是因为大公司往往不仅仅要会干活的人,它还会要求这些会干活的人知道其中的原理!我们自己也应该要求自己或多或少的理解这些原理,知道我们为什么写出的obj-C代码经历了哪些过程run到我们的设备上,不要敲了很多年的代码还是一只只会干活的码农。




 我知道一种学习

○

于坚



下载我们APP

Android和IOS均可使用

这里有最新鲜的干货

ITPUB|ChinaUnix|IT168



原文作者:;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2019-07-17

  • 博文量
    1982
  • 访问量
    17873