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"调用相关函数

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
  • 获取属性
/*!
 *  获取对象的所有属性和属性值
 *
 *  @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 
@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

你可能感兴趣的