zoukankan      html  css  js  c++  java
  • 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);
         }
    }
    
     

     







  • 相关阅读:
    Nginx源码编译安装
    nginx版本对比
    k8s中subpath挂载单个文件报错处理
    C++ array 数组函数
    洛谷 P2141
    c++ set容器
    字符串中输出每一个元素的方法
    string中的pop_back()函数
    如何去掉前导0 在字符串中 算法
    pat 乙级1074
  • 原文地址:https://www.cnblogs.com/peteremperor/p/6020800.html
Copyright © 2011-2022 走看看