iOS中的Runtime学习小记

本博客主要分以下几个方面来介绍iOS中的runtime

  • Runtime的概念介绍
  • iOS中的消息机制
  • Runtime的作用

Objective-C语言中的Runtime概念

  1. 动态编程语言和静态编程语言的区别
  • 动态编程语言:在程序运行过程中可以改变数据类型的结构,对象的函数,变量可以被修改删除。例如OC
  • 静态编程语言:在程序编译阶段检查数据的类型,数据类型的结构不可以在运行时被改变。例如C
  1. Runtime---运行时系统是一个有公开接口的动态库,由一些数据结构和函数的集合组成,这些数据结构和函数的声明头文件,在/usr/include/objc,这些函数支持用纯C的函数来实现和Objective-C同样的功能
  2. Runtime---是开源的,目前苹果公司和GNU各自维护一个开源的runtime版本,apple open runtime;
  3. Runtime---在OC中的使用方式
  • 通过Objective-C源代码
  • 通过类NSObject的方法
class 返回对象的类
isKindeOfClass:和isMemberOfClass: 检查对象是否在指定的类继承体系中
respondsToSelector: 检查对象能否响应指定的消息
conformsToProtocol: 检查对象是否实现了指定协议类的方法
methodForSelector: 返回指定方法实现的地址
  • 通过运行时系统的函数
    通过导入头文件"#import<objc/message.h>"调用相关函数

iOS中的消息机制

  1. Objective-C中消息机制的相关概念
  • message(消息) --包括了函数名+参数列表的一种抽象
  • method(方法)-- 是真正的存在的代码。如:- (int)meaning { return 42; }
  • selector(方法选择器)--通过SEL类型存在,描述一个特定的method 或者说 message。在实际编程中,可以通过selector进行检索方法等操作
  • SEL(方法选择器) -- 是一个char*指针,仅仅表示它所代表的方法名字,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度
 //SEL
 SEL selector = @selector(message); //@selector不是函数调用,只是给这个坑爹的编译器的一个提示   
  NSLog (@"%s", (char *)selector); //print message   
  //以下函数命名方式这被认为是一种编译错误
 -(void)setWidth:(int)width;   
 -(void)setWidth:(double)width;
  • IMP --函数指针
    • 我们可以通过SEL方便、快速、准确的获得它所对应的IMP(也就是函数指针),而在取得了函数指针之后,也就意味着我们取得了执行的时候的这段方法的代码的入口,这样我们就可以像普通的C语言函数调用一样使用这个函数指针。当然我们可以把函数指针作为参数传递到其他的方法,或者实例变量里面,从而获得极大的动态性

Runtime的作用

  1. 关联对象:主要为分类增加属性和实例变量。

    能用扩展一般不用继承,因为随着继承深度的增加,代码可维护性变差

    static char myKey;  //定义一个静态字符
    /*!
     *  给关联对象赋值
     *
     *  @param self 需要添加关联的分类
     *  @param key  const void *,key仅仅是一个地址,不是字符串内容。具体指向内容不用关心
     *  @param value 关联对象的值
     *  @param policy 关联策略,类似于对象的属性修饰符,OBJC_ASSOCIATION_RETAIN_NONATOMIC等
     *
     */
     objc_setAssociatedObject(self, &myKey, model, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //给关联对象赋值
    /*!
     *  根据关联的key,获取关联的值
     *
     *  @param self  需要添加关联的分类
     *  @param myKey onst void *,key仅仅是一个地址,不是字符串内容。具体指向内容不用关心
     *
     *  @return 关联的对象的值
     */
     objc_getAssociatedObject(self, &myKey);
    
  2. 获取对象的私有变量,私有方法,动态添加属性、方法等。

  • 获取类名
```objectivec
Class class = [objc class];
class_getName(class);
class_getSuperclass(class);
```
  • 获取成员变量(可以获取私有成员变量)
CaculatorMaker *make = [[CaculatorMaker alloc] init];
Class objcClass = [make class];
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(objcClass, &outCount);
for (int i = 0; i<outCount; i++) {
   Ivar ivar = ivars[i];
   const char *ivarString = ivar_getName(ivar);
   NSLog(@"the variable of make is %s",ivarString);
 }
 free(ivars);
  • 获取属性
/*!
 *  获取对象的所有属性和属性值
 *
 *  @param object 对象
 *
 *  @return 属性和属性值数组
*/
- (NSMutableArray *)getObjectPropertyAndValues:(id)object
{
     NSMutableArray *array = [NSMutableArray new];
     Class objcClass = [object class];
     unsigned int outCount = 0 ;
     objc_property_t *properties = class_copyPropertyList(objcClass, &outCount);
     for (int i = 0; i < outCount; i++) {
       objc_property_t property = properties[i];
       const char *propertyName = property_getName(property);
       NSString *propertyKey = [NSString stringWithUTF8String:propertyName];
       NSString *propertyValue = [object valueForKey:propertyKey];
       RACTuple *tuple = RACTuplePack(propertyKey,propertyValue);
       [array addObject:tuple];
     }
     return array;
   }
  • 动态添加方法
 CaculatorMaker *make = [[CaculatorMaker alloc] init];
 /*!
  *  runtime添加方法
  *
  *  @param class     被添加方法的类
  *  @param addMethod: 方法的名称
  *  @param imp:  实现这个方法的函数
  *  @param type: 定义函数返回值类型和参数类型的字符串[Type Encodings](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)
  *  @return 添加成功或者失败
  */
 class_addMethod([make class], @selector(addMethod:), (IMP)addMethod, "i@:@");
 [make performSelector:@selector(addMethod:) withObject:@"new Method"];

 //添加的方法的具体实现
 int addMethod(id self, SEL _cmd, NSString *str)
 {
 NSLog(@"new method is %@",str);
 return 100;
 }
  • 动态添加属性和成员变量均未成功,属性添加完成之后的setter方法和getter方法不会实现。
  1. 消息转发(message forwarding)-当一个对象无法接收某一消息时,就会启动消息转发机制。
    通过这一机制,我们可以告诉对象如何处理未知的消息。默认情况下,对象接收到未知的消息,会导致程序崩溃。
  • 消息转发的步骤:1.动态方法解析 2.备用接收者 3. 完整转发
  • 动态方法解析:当对象接收到未实现的方法时,为自动调用resolveInstanceMethod:和resolveClassMethod:
//当对象接收到未知方法时,动态添加以下方法。作为默认执行,防止程序崩溃
 void functionForMethod1(id self, SEL _cmd)
 {
   NSLog(@"当未实现某个方法时默认执行此函数");
 }
 + (BOOL)resolveInstanceMethod:(SEL)sel
 {
   class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
   return [super resolveInstanceMethod:sel];
 }

 + (BOOL)resolveClassMethod:(SEL)sel
 {
   class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
   return [super resolveClassMethod:sel];
 }
 ```
+ 备用接收者:如果没有用动态方法解析处理消息,则Runtime会继续调以下方法:

```objectivec
//如果对象实现了这个方法,并返回一个非nil的值,则这个对象会作为消息的接收者。且消息会被分发到这个对象
- (id)forwardingTargetForSelector:(SEL)aSelector {
 id  standByObj;
 //....standByObj 备用接收对象的初始化等操作
 return standByObj;
return [super forwardingTargetForSelector:aSelector];
 }

  • 完整转发:如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了
/*!
 *  消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象。因此我们必须重写这个方法,为给定的selector提供一个合适的方法签名
 *
 *  @param aSelector 需要转发的方法
 *
 *  @return 新的方法签名
 */

 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
 {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];

      if (!signature) {
      if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {

          signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
      }
    }
    return signature;
  }

  /*!
  *  将消息转发给其它对象
  *
  *  @param anInvocation 需要转发的消息的selector,目标(target)和参数
  */
  - (void)forwardInvocation:(NSInvocation *)anInvocation
  {   
    if ([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {

      [anInvocation invokeWithTarget:_helper];
    }
  }
  1. 方法交换(Method Swizzling)
  • 给view controller的viewWillAppear:中添加跟踪代码,将viewWillAppear:与自定义的dg_viewWillAppear:交换,代码如下:
#import "UIViewController+Tracking.h"
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
/*!
*  在load中实现方法交换,
*/
+ (void)load
{
   //
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken,^{
       Class class = [self class];
       SEL originalSelector = @selector(viewWillAppear:);
       SEL swizzledSelector = @selector(dg_viewWillAppear:);
       Method originalMethod = class_getInstanceMethod(class, originalSelector);
       Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
       //本类是否已添加swizzledMethod,未添加则进行添加
       BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

       if (didAddMethod) {
           class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
           } else{
             method_exchangeImplementations(originalMethod, swizzledMethod);
           }
           });
         }
 - (void)dg_viewWillAppear:(BOOL)animated
 {
   [self dg_viewWillAppear:animated];
   NSLog(@"viewWilleAppear");
 }
 @end

参考

  1. Objective-C Runtime 运行时
  2. 让你快速上手Runtime
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,540评论 33 466
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 2,129评论 0 9
  • objc_getAssociatedObject返回与给定键的特定对象关联的值。ID objc_getAssoci...
    有一种再见叫青春阅读 1,558评论 0 7
  • 画了一幅画,寄给远方的朋友。 收到张同学的信件,是在一个阴雨的下午,南方的冬天总是伴随着这样的细雨绵绵。 他是个文...
    盛晴雨阅读 429评论 0 1