关于runtime的使用心得

 最近一直在听朋友用runtime进行装逼,就去学习了一下,顺便整理出来方便以后查看。

1.什么是Runtime?

   runtime是底层C语言的API,从表面理解就是动态运行时。它会在程序运行时把OC转换成C,比如:[self name]会装换成objc_msgSend(self,@selector(name)).

2.runtime的相关定义以及方法

  /// An opaque type that represents a method in a class definition.

  typedefstruct objc_method *Method;


  /// An opaque type that represents an instance variable.

  typedefstruct objc_ivar *Ivar;


  /// An opaque type that represents a category.

  typedefstruct objc_category *Category;


  /// An opaque type that represents an Objective-C declared property.

  typedefstruct objc_property *objc_property_t;

  

  struct objc_class {

          Class isa  OBJC_ISA_AVAILABILITY;

        

  #if !__OBJC2__

          Class super_class                                        OBJC2_UNAVAILABLE;

          const char *name                                         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;

3.runtime的实际应用

1)获取类中相关属性

    1.首先建立一个类,如下:

    #import

    @interface Student : NSObject

    {

        int _age;

    }

    @property (nonatomic, copy) NSString *name;

    @property (nonatomic, copy) NSString *number;

    @property (nonatomic, copy) NSString *sex;

    - (void)setAge:(int)age;

    - (int)age;

    2.在主控制器里调用,记得导入#import

    unsigned int outCount = 0;

    //获取成员变量列表

    Ivar *ivarList = class_copyIvarList([Student class], &outCount);

    for (int i = 0; i < outCount; i ++) {

        Ivar ivar = ivarList[i];

        const char *ivarName = ivar_getName(ivar);

        NSLog(@"ivar = %@",[NSString stringWithUTF8String:ivarName]);

    }

    //获取属性列表

    objc_property_t *propertyList = class_copyPropertyList([Student class], &outCount);

    for (int i = 0; i < outCount; i ++) {

        const char *propertyName = property_getName(propertyList[i]);

        NSLog(@"property = %s",propertyName);

    }

    //获取方法列表

    Method *methodList = class_copyMethodList([Student class], &outCount);

    for (int i = 0; i < outCount; i ++) {

        Method method = methodList[i];

        NSLog(@"method = %@",NSStringFromSelector(method_getName(method)));

    }

    //获取协议列表

    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([Student class], &outCount);

    for (int i = 0; i < outCount; i ++) {

        Protocol *protocol = protocolList[i];

        const char *protocolName = protocol_getName(protocol);

        NSLog(@"protocol = %s",protocolName);

    }

    3.打印结果如下:

    关于runtime的使用心得_第1张图片

    4.应用场景

    可以利用遍历类的属性进行快速的归档

    在没用runtime之前,归档是这样的:

    关于runtime的使用心得_第2张图片

    对于属性少的还好说,如果有几十上百个,那就要写上百个[aCoderencodeObject:self.nameforKey:@"name"];那样就会非常麻烦,然而利用runtime就非常方便了,如图:

    关于runtime的使用心得_第3张图片

    

2)交换方法

    应用场景:比如想要获取当前控制器,就可以用自己的方法来交换系统的方法,要先建立一个UIViewController的分类,代码如下:

    该代码是在网上复制粘贴的。

#import "UIViewController+swizzling.h"
#import @implementation UIViewController (swizzling)
//load方法会在类第一次加载的时候被调用
//调用的时间比较靠前,适合在这个方法里做方法交换
+ (void)load{
    //方法交换应该被保证,在程序中只会执行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //获得viewController的生命周期方法的selector
        SEL systemSel = @selector(viewWillAppear:);
        //自己实现的将要被交换的方法的selector
        SEL swizzSel = @selector(swiz_viewWillAppear:);
        //两个方法的Method
        Method systemMethod = class_getInstanceMethod([self class], systemSel);
        Method swizzMethod = class_getInstanceMethod([self class], swizzSel);
        //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
        BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
        if (isAdd) {
            //如果成功,说明类中不存在这个方法的实现
            //将被交换方法的实现替换到这个并不存在的实现
            class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
        }else{
            //否则,交换两个方法的实现
            method_exchangeImplementations(systemMethod, swizzMethod);
        }
    });
}
- (void)swiz_viewWillAppear:(BOOL)animated{
    //这时候调用自己,看起来像是死循环
    //但是其实自己的实现已经被替换了
    [self swiz_viewWillAppear:animated];
    NSLog(@"swizzle");
}
@end
在一个自己定义的viewController中重写viewWillAppear
    
- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    NSLog(@"viewWillAppear");
}

    3)拦截调用

    //当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES

    + (BOOL)resolveClassMethod:(SEL)sel;

    //和第一个方法相似,只不过处理的是实例方法。

    + (BOOL)resolveInstanceMethod:(SEL)sel;

    //将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target

    - (id)forwardingTargetForSelector:(SEL)aSelector;

    //将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。

    - (void)forwardInvocation:(NSInvocation *)anInvocation;

    

4)动态添加方法

    class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)

    其中class_addMethod的四个参数分别是:

1.Class cls 给哪个类添加方法,比如self

2.SEL name 添加的方法,是重写的拦截调用传进来的selector

3.IMP imp 方法的实现,C方法的方法实现可以直接获得。如果是OC方法,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;获得方法的实现。

4.代表有无参数的方法。


    首先从外部调用一个不存在的方法

//调用不存在的方法
[student performSelector:@selector(resolveAdd:) withObject:@"test"];
   然后再student对象内部重写拦截方法

void runAddMethod(id self, SEL _cmd, NSString *string){
    NSLog(@"add C IMP ", string);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    //给本类动态添加一个方法
    if ([NSStringFromSelector(sel) isEqualToString:@"resolveAdd:"]) {
        class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
    }
    return YES;
}

5)关联对象

    关联对象可以给一个类添加新的属性。

    关于runtime的使用心得_第4张图片

    关于runtime的使用心得_第5张图片

    

    

    总结:runtime在实际开发中用到的很少,用的较多就是交换方法,但是一定要保证只交换一次,否则会弄的很乱,其次用的多的是遍历类的属性进行归档,不过现在都有很多第

    三方库,也很少用到了。不过了解一下其机制还是不错的。





你可能感兴趣的:(runtime的学习心得)