美文网首页
Runtime 学习笔记

Runtime 学习笔记

作者: 47号同学 | 来源:发表于2016-09-17 20:34 被阅读16次

    Runtime

    1.对象和类

    Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下:

    typedef struct objc_class *Class;

    在objc/runtime.h中,struct objc_class 的定义如下:

    struct objc_class {
        Class isa;//元类
        #if !__OBJC2__
        Class super_class//父类                    OBJC2_UNAVAILABLE;
        long version//版本信息                      OBJC2_UNAVAILABLE;
        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;
        struct objc_protocol_list *protocols//协议  OBJC2_UNAVAILABLE;
        #endif
    } OBJC2_UNAVAILABLE;
    

    虽然这个结构体已经过时了,但是还是有参考的意义,比起最新的版本更好理解。

    由于 objc_class 也有 isa 指针,所以类本身也是一个对象。

    下面就根据runtime提供的一些办法,使用简单的例子来探窥其究竟:

    //首先创建一个测试类
    #import <Foundation/Foundation.h>
    
    @interface MyRuntimeTestClass : NSObject
    
    @property (nonatomic, strong) NSArray *array;
    @property (nonatomic, copy) NSString *string;
    
    - (void)method1;
    - (void)method2;
    + (void)classMethod1;
    @end
    
    #import "MyRuntimeTestClass.h"
    
    @interface MyRuntimeTestClass ()
    {
        NSInteger _instance1;
        NSString *_instance2;
    }
    
    @property (nonatomic, assign) NSInteger *integer;
    
    - (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2;
    
    @end
    
    @implementation MyRuntimeTestClass
    
    +(void)classMethod1
    {
        
    }
    
    -(void)method1
    {
        NSLog(@"call the method1");
    }
    
    -(void)method2
    {
        NSLog(@"call the method2");
    }
    
    - (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2 {
        NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2);
    }
    
    @end
    
    //测试类和对象
    -(void)testMyRuntimeTestClass
    {
        MyRuntimeTestClass *obj = [[MyRuntimeTestClass alloc]init];
        self.myRuntimeTestClass = obj;
        unsigned int outCount = 0;
        Class cls = obj.class;
        
        NSLog(@"class name :%s",class_getName(cls));
        NSLog(@"==========================================================");
    
        NSLog(@"super class name :%s",class_getName(class_getSuperclass(cls)));
        NSLog(@"==========================================================");
        
        Class metaClass = objc_getMetaClass(class_getName(cls));
        NSLog(@"%s metaClass name:%s",class_getName(cls),class_getName(metaClass));
        NSLog(@"==========================================================");
        
        NSLog(@"%s is %@ a metaClass",class_getName(cls),class_isMetaClass(cls) ? @"" : @"not");
        NSLog(@"==========================================================");
        
        NSLog(@"class size:%zu",class_getInstanceSize(cls));
        NSLog(@"==========================================================");
        
        // 成员变量
        Ivar *ivars = class_copyIvarList(cls, &outCount);//获取整个成员变量列表
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSLog(@"instance variable's name:%s at index:%d",ivar_getName(ivar),i);
        }
        free(ivars);
        
        Ivar array = class_getInstanceVariable(cls, "_array"); //获取类中指定名称实例成员变量的信息
        if (array != NULL) {
            NSLog(@"instance variable's %s",ivar_getName(array));
        }
        NSLog(@"==========================================================");
        
        // 属性操作
        objc_property_t *properties = class_copyPropertyList(cls, &outCount);
        for (int i = 0; i < outCount; i++) {
            objc_property_t property = properties[i];
            NSLog(@"property's name: %s",property_getName(property));
        }
        free(properties);
        
        objc_property_t string = class_getProperty(cls, "_string");
        if (string != NULL) {
            NSLog(@"property %s",property_getName(string));
        }
        NSLog(@"==========================================================");
        
        // 方法操作
        Method *methods = class_copyMethodList(cls, &outCount);
        for (int i = 0; i < outCount; i++) {
            Method method = methods[i];
            NSLog(@"method's signature:%s",sel_getName(method_getName(method)));
        }
        free(methods);
        
        Method method1 = class_getInstanceMethod(cls, @selector(method1));
        if (method1 != NULL) {
            NSLog(@"instand method %s",sel_getName(method_getName(method1)));
        }
        
        Method classMethod = class_getClassMethod(cls, @selector(classMethod1));
        if (classMethod != NULL) {
            NSLog(@"class method %s",sel_getName(method_getName(method1)));
        }
        
        IMP imp = class_getMethodImplementation(cls, @selector(method1));
        IMP imp2 = method_getImplementation(class_getInstanceMethod(cls, @selector(method1)));
        
        imp();
        imp2();
        NSLog(@"==========================================================");
    
        /**
         *  1.成员变量和属性: (1) 成员变量内部使用,属性外部使用,属性是为了让类外能够访问到成员变量,即是属性是外部访问成员变量的接口。
         (2)类的变量包含成员变量和属性,成员变量就好似一个人的自己固有的属性(如大脑,眼睛等等),属性是一个人的外部特征(如他的名字,职业等等)。
         (3)只要@property 声明了成员变量,SDK自动生成成员变量,不要需要手动对应,是专用于从类外部对其进行调用或赋值的.
         (4)成员变量命名方式:_变量名
         2.@property:自动创建setter and getter
         3.类对象和类的实例(即对象):
         */
    }
    

    根据上面的一些例子,我们来看看元类:

    messaging1.gif

    获取办法的地址

    该方式就类似 C 语言的函数调用,直接找到函数的地址。一般情况下不会使用该方式,只有在比较频繁调用某个函数的时候才会使用该方式。即使 OC 中使用了办法缓存机制,如果一个办法频繁调用,根据缓存列表,快速找到相应的办法和办法实现,也是有一定的时间的。

    /**
     *  直接获取办法的地址:有cocoa框架提供,并非 Objective-C 语言的特性 (一个办法比较频繁调用才会使用该办法,相当于直接调用函数)
     */
    -(void)testGetMethodAdress
    {
        void (*setter)(id, SEL, BOOL);
        setter = (void (*)(id, SEL, BOOL))[MyRuntimeTestClass methodForSelector:@selector(method1)];
        for (int i = 0 ; i < 1000 ; i++)
            setter(self, @selector(method1), YES);
    }
    

    消息转发

    当对象接收到无法解读的消息后,就会启动”消息转发“。消息转发分三个阶段:动态解析,备用接收者,完整的消息转发。

    动态解析:

    该方式是当对象接收到无法解读的消息后,会调用 +(BOOL)resolveInstanceMethod:(SEL)sel,

    +(BOOL)resolveClassMethod:(SEL)sel

    这两个办法,这时候我们可以在该办法处理对象无法解读的消息。

    /**
     *  动态解析:当对象接收到无法识别的消息,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法)
     */
    +(BOOL)resolveInstanceMethod:(SEL)sel
    {
        NSLog(@"resolveInstanceMethod");
        NSLog(@"can not performSelector called: %@",NSStringFromSelector(sel));
        
        NSString *selectorString = NSStringFromSelector(sel);
        if ([selectorString isEqualToString:@"testMySendMsgOfDymanicMehod"]) {
            class_addMethod(self.class, @selector(testMySendMsgOfDymanicMehod), (IMP)functionForTestMySendMsgOfDymanicMehod, "@:");
        }
        return [super resolveInstanceMethod:sel];;
    }
    +(BOOL)resolveClassMethod:(SEL)sel
    {
        NSLog(@"resolveClassMethod");
        NSLog(@"can not performSelector called: %@",NSStringFromSelector(sel));
        return [super resolveClassMethod:sel];
    }
    void functionForTestMySendMsgOfDymanicMehod(id self, SEL _cmd)
    {
        NSLog(@"the IMP of functionForTestMySendMsgOfDymanicMehod");
    }
    

    备用接收者:

    当第一步的动态解析也无法解读未知的消息的时候,就会转向备用接收者:-(id)forwardingTargetForSelector:(SEL)aSelector

    其实质就是寻找有没有其他对象能解读该对象,即是所谓的备用接收者。

    /**
     *  备用接收者
     */
    -(id)forwardingTargetForSelector:(SEL)aSelector
    {
        NSLog(@"forwardingTargetForSelector");
        NSLog(@"can not performSelector called: %@",NSStringFromSelector(aSelector));
        NSString *selectorString = NSStringFromSelector(aSelector);
        if ([selectorString isEqualToString:@"method1"]) {
            
            return self.myRuntimeTestClass;
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    

    完整消息转发:

    当前面两种方式都不能解读未知的消息,那就启用最后一中方式,即是将尚未处理的那条消息的有关的全部细节都封装在 NSInvocation 中,此对象包含选择子,目标以及参数。在触发 NSInvocation 对象时,“消息派发系统”将亲自出马,把消息指派给目标对象。

    /**
     *  由于完整的消息转发需要将尚未处理的消息有关的全部细节封装在NSInvocation中,故应重写该办法。
     */
    -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        NSLog(@"methodSignatureForSelector:");
        NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
        if (!signature) {
            if ([MyRuntimeTestClass instancesRespondToSelector:aSelector]) {
                
                signature = [MyRuntimeTestClass instanceMethodSignatureForSelector:aSelector];
            }
        }
        return signature;
    }
    /**
     *  完整的消息转发
     */
    -(void)forwardInvocation:(NSInvocation *)anInvocation
    {
        NSLog(@"forwardInvocation");
        if ([MyRuntimeTestClass resolveClassMethod:anInvocation.selector]) {
            
            anInvocation.target = self.myRuntimeTestClass;
        }
    }
    

    3.办法交换

    有时候我们需要替换系统的办法时,比如我想在每次调用

    -(void)viewWillAppear:(BOOL)animated

    都打印一段话。这样每个 VC 都码上打印语句,未免效率有些不高,这时候我们就可以使用办法交换,将我们自己的办法和系统的办法交换。

    /**
     *  第一次调用类的类方法或实例方法之前被调用
     */
    +(void)initialize
    {
        NSLog(@"initialize");
    }
    
    /**
     *  在类初始加载时调用
     */
    +(void)load
    {
        NSLog(@"load");
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            
            Class class = [self class];
            
            SEL originalSelector = @selector(viewWillAppear:);
            SEL swizzlingSelector = @selector(YYviewWillAppear:);
            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method swizzlingMethod = class_getInstanceMethod(class, swizzlingSelector);
            
            BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzlingMethod), method_getTypeEncoding(swizzlingMethod));
            
            if (didAddMethod) {
                
                //实现两个办法的实现部分交换
                class_replaceMethod(class, swizzlingSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
            }else{
                
                method_exchangeImplementations(originalMethod, swizzlingMethod);
            }
        });
        
        /*
            1.Selector(typedef struct objc_selector *SEL):用于在运行时中表示一个方法的名称。一个方法选择器是一个C字符串,它是在Objective-C运行时被注册的。选择器由编译器生成,并且在类被加载时由运行时自动做映射操作。
         
            2.Method(typedef struct objc_method *Method):在类定义中表示方法的类型
         
            3. Implementation(typedef id (*IMP)(id, SEL, ...)):这是一个指针类型,指向方法实现函数的开始位置。这个函数使用为当前CPU架构实现的标准C调用规范。每一个参数是指向对象自身的指针(self),第二个参数是方法选择器。然后是方法的实际参数。
         */
    }
    
    #pragma mark -method swizzling
    
    -(void)YYviewWillAppear:(BOOL)animated
    {
        [self YYviewWillAppear:animated];
        NSLog(@"method had changed!");
    }
    

    注:办法的交换永远都应该在 load 办法中实现,因为 load 办法在类初始化的时候就会调用,而 initialize 要在类被发送消息的时候才会调用。

    相关文章

      网友评论

          本文标题:Runtime 学习笔记

          本文链接:https://www.haomeiwen.com/subject/colwettx.html