iOS Runtime 运行时

Objc Runtime使得C具有了面向对象能力,在程序运行时创建,检查,修改类、对象和它们的方法。(Runtime是C和汇编编写的)

Runtime系统是由一系列的函数和数据结构组成的公共接口动态共享库,在/usr/include/objc目录下可以看到头文件,可以用其中一些函数通过C语言实现Objective-C中一样的功能。

Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一,我们平时编写的 OC 代码,底层都是基于它来实现的。

Objective-C 是一门动态语言,它会将一些工作放在代码运行时才处理而并非编译时。也就是说,有很多类和成员变量在我们编译的时是不知道的,而在运行时,我们所编写的代码会转换成完整的确定的代码运行。

因此,编译器是不够的,我们还需要一个运行时系统(Runtime system)来处理编译后的代码。

 objc/runtime.h中objc_class结构体的定义如下:

struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //isa指针指向Meta Class,因为Objc的类的本身也是一个Object,为了处理这个关系,runtime就创造了Meta Class,当给类发送[NSObject alloc]这样消息时,实际上是把这个消息发给了Class Object

#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存,对象接到一个消息会根据isa指针查找消息对象,这时会在methodLists中遍历,如果cache了,常用的方法调用时就能够提高调用的效率。
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif

} OBJC2_UNAVAILABLE;

objc_object是一个类的实例结构体,objc/objc.h中objc_object是一个类的实例结构体定义如下:
struct objc_object { Class isa OBJC_ISA_AVAILABILITY; }; typedefstruct objc_object *id;

向object发送消息时,Runtime库会根据object的isa指针找到这个实例object所属于的类,然后在类的方法列表以及父类方法列表寻找对应的方法运行。id是一个objc_object结构类型的指针,这个类型的对象能够转换成任何一种对象。

 Meta Class :一个类对象的类,当向对象发消息,runtime会在这个对象所属类方法列表中查找发送消息对应的方法,但当向类发送消息时,runtime就会在这个类的meta class方法列表里查找。所有的meta class,包括Root class,Superclass,Subclass的isa都指向Root class的meta class,这样能够形成一个闭环。

关联对象使用

//动态的将一个Tap手势操作连接到任何UIView中。
- (void)setTapActionWithBlock:(void (^)(void))block
{
     UITapGestureRecognizer *gesture = objc_getAssociatedObject(self, &kDTActionHandlerTapGestureKey);

     if (!gesture)
     {
          gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(__handleActionForTapGesture:)];
          [self addGestureRecognizer:gesture];
          //将创建的手势对象和block作为关联对象
          objc_setAssociatedObject(self, &kDTActionHandlerTapGestureKey, gesture, OBJC_ASSOCIATION_RETAIN);
     }

     objc_setAssociatedObject(self, &kDTActionHandlerTapBlockKey, block, OBJC_ASSOCIATION_COPY);
}

//手势识别对象的target和action
- (void)__handleActionForTapGesture:(UITapGestureRecognizer *)gesture
{
     if (gesture.state == UIGestureRecognizerStateRecognized)
     {
          void(^action)(void) = objc_getAssociatedObject(self, &kDTActionHandlerTapBlockKey);

          if (action)
          {
               action();
          }
     }
}

Method调用流程

消息函数,Objc中发送消息是用中括号把接收者和消息括起来,只到运行时才会把消息和方法实现绑定。 

Method Swizzling

是改变一个selector实际实现的技术,可以在运行时修改selector对应的函数来修改Method的实现。但是对于有些类无法修改其源码时又要更改其方法实现时可以使用Method Swizzling,通过重新映射方法来达到目的,但是跟消息转发比起来调试会困难。

  • Swizzling应该总在+load中执行:Objective-C在运行时会自动调用类的两个方法+load和+initialize。+load会在类初始加载时调用,和+initialize比较+load能保证在类的初始化过程中被加载
  • Swizzling应该总是在dispatch_once中执行:swizzling会改变全局状态,所以在运行时采取一些预防措施,使用dispatch_once就能够确保代码不管有多少线程都只被执行一次。这将成为method swizzling的最佳实践。
  • Selector,Method和Implementation:这几个之间关系可以这样理解,一个类维护一个运行时可接收的消息分发表,分发表中每个入口是一个Method,其中key是一个特定的名称,及SEL,与其对应的实现是IMP即指向底层C函数的指针。
#import <objc/runtime.h>

@implementation UIViewController (Tracking)

+ (void)load {
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
          Class class = [self class];
          // When swizzling a class method, use the following:
          // Class class = object_getClass((id)self);

          //通过method swizzling修改了UIViewController的@selector(viewWillAppear:)的指针使其指向了自定义的xxx_viewWillAppear
          SEL originalSelector = @selector(viewWillAppear:);
          SEL swizzledSelector = @selector(xxx_viewWillAppear:);

          Method originalMethod = class_getInstanceMethod(class, originalSelector);
          Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

          BOOL didAddMethod = class_addMethod(class,
               originalSelector,
               method_getImplementation(swizzledMethod),
               method_getTypeEncoding(swizzledMethod));

          //如果类中不存在要替换的方法,就先用class_addMethod和class_replaceMethod函数添加和替换两个方法实现。但如果已经有了要替换的方法,就调用method_exchangeImplementations函数交换两个方法的Implementation。
          if (didAddMethod) {
               class_replaceMethod(class,
                    swizzledSelector,
                    method_getImplementation(originalMethod),
               method_getTypeEncoding(originalMethod));
          } else {
               method_exchangeImplementations(originalMethod, swizzledMethod);
          }
     });
}

#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
     [self xxx_viewWillAppear:animated];
     NSLog(@"viewWillAppear: %@", self);
}

@end

Runtime的应用

获取系统提供的库相关信息

主要函数

// 获取所有加载的Objective-C框架和动态库的名称
const char ** objc_copyImageNames ( unsigned int *outCount );

// 获取指定类所在动态库
const char * class_getImageName ( Class cls );

// 获取指定库或框架中所有类的类名
const char ** objc_copyClassNamesForImage ( const char *image, unsigned int *outCount );

通过这些函数就能够获取某个类所有的库,以及某个库中包含哪些类

NSLog(@"获取指定类所在动态库");

NSLog(@"UIView's Framework: %s", class_getImageName(NSClassFromString(@"UIView")));

NSLog(@"获取指定库或框架中所有类的类名");
const char ** classes = objc_copyClassNamesForImage(class_getImageName(NSClassFromString(@"UIView")), &outCount);
for (int i = 0; i < outCount; i++) {
     NSLog(@"class name: %s", classes[i]);
}

//结果
2014-11-08 12:57:32.689 [747:184013] 获取指定类所在动态库
2014-11-08 12:57:32.690 [747:184013] UIView's Framework: /System/Library/Frameworks/UIKit.framework/UIKit
2014-11-08 12:57:32.690 [747:184013] 获取指定库或框架中所有类的类名
2014-11-08 12:57:32.691 [747:184013] class name: UIKeyboardPredictiveSettings
2014-11-08 12:57:32.691 [747:184013] class name: _UIPickerViewTopFrame
2014-11-08 12:57:32.691 [747:184013] class name: _UIOnePartImageView
2014-11-08 12:57:32.692 [747:184013] class name: _UIPickerViewSelectionBar
2014-11-08 12:57:32.692 [747:184013] class name: _UIPickerWheelView
2014-11-08 12:57:32.692 [747:184013] class name: _UIPickerViewTestParameters
......

对App的用户行为进行追踪

就是用户点击时把事件记录下来。一般比较做法就是在viewDidAppear里记录事件,这样会让这样记录事件的代码遍布整个项目中。继承或类别也会有问题。这时利用Method Swizzling把一个方法的实现和另一个方法的实现进行替换。

//先定义一个类别,添加要Swizzled的方法
@implementation UIViewController (Logging)- (void)swizzled_viewDidAppear:(BOOL)animated
{ // call original implementation
     [self swizzled_viewDidAppear:animated]; // Logging
     [Logging logWithEventName:NSStringFromClass([self class])];
}
//接下来实现swizzle方法
@implementation UIViewController (Logging)void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector) { // the method might not exist in the class, but in its superclass
     Method originalMethod = class_getInstanceMethod(class, originalSelector);
     Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); // class_addMethod will fail if original method already exists
     BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); // the method doesn’t exist and we just added one
     if (didAddMethod) {
          class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
     }
     else {
          method_exchangeImplementations(originalMethod, swizzledMethod);
     }
}

//最后要确保在程序启动的时候调用swizzleMethod方法在之前的UIViewController的Logging类别里添加+load:方法,然后在+load:里把viewDidAppear替换掉
@implementation UIViewController (Logging)+ (void)load
{
     swizzleMethod([self class], @selector(viewDidAppear:), @selector(swizzled_viewDidAppear:));
}

//更简化直接用新的IMP取代原IMP,不是替换,只需要有全局的函数指针指向原IMP即可。
void (gOriginalViewDidAppear)(id, SEL, BOOL);void newViewDidAppear(UIViewController *self, SEL _cmd, BOOL animated)
{ // call original implementation
     gOriginalViewDidAppear(self, _cmd, animated); // Logging
     [Logging logWithEventName:NSStringFromClass([self class])];
}
+ (void)load
{
     Method originalMethod = class_getInstanceMethod(self, @selector(viewDidAppear:));
     gOriginalViewDidAppear = (void *)method_getImplementation(originalMethod); if(!class_addMethod(self, @selector(viewDidAppear:), (IMP) newViewDidAppear, method_getTypeEncoding(originalMethod))) {
          method_setImplementation(originalMethod, (IMP) newViewDidAppear);
     }
}
 

 







原文地址:https://www.cnblogs.com/peteremperor/p/6020800.html