美文网首页iOS技术点ios开发iOS开发精选集
runtime知识点及项目中的应用

runtime知识点及项目中的应用

作者: lilei5 | 来源:发表于2017-06-27 15:58 被阅读559次

    概论:Runtime是Objective-C中底层的一套C语言API,是一个将C语言转化为面向对象语言的拓展。OC是一种面向对象的动态语言,动态语言就是在运行时执行静态语言的编译连接的工作。OC编写的程序不能直接编译为及其读懂的机器语言,在程序运行时,须通过Runtime来转换。

    而在某些在特定的场景下,运用runtime的知识,往往可以大大减少我们的工作量。并能使代码更加统一、简洁。

    本文将以一下几个方面逐一讲解runtime:

    目录:
    一、系统方法中的应用
      1、消息机制
      2、KVO的底层实现
    二、runtime常见作用
      1、动态获取成员属性
      2、动态获得成员变量
      3、动态获得实例方法
      4、动态添加方法实现
      5、方法交换
    三、实际开发中的应用
      1、给控件添加block事件回调方法
      2、自定义类型自动归档、解归档
      3、数组越界crash处理

    首先定义一个Dog类,本篇文章的讲解将围绕这个类展开。

    @interface Dog : NSObject
    {
        float _weight;
    }
    @property(nonatomic,copy) NSString *name;
    - (void)run:(NSInteger)runLong street:(NSString *)streetName;
    @end
    
    @interface Dog ()
    {
        NSInteger  _age;
    }
    @property(nonatomic,copy) NSString *address;
    @end
    
    @implementation Dog
    - (void)run:(NSInteger)runLong street:(NSString *)streetName{
         NSLog(@"在%@跑了%li米",streetName,runLong);
    }
    - (void)_sleep{
         NSLog(@"%s",__func__);
    }
    @end
    

    一、系统方法运用runtime

    1、发送消息

    在OC中,调用一个对象的方法,实际上是给对象发了一条消息,在编译Objective-C函数调用的语法时,会被翻译成一个C的函数调用:objc_msgSend()
    当我们dog,run的方法时:
    [dog run:100 street:@"华尔街"];
    实际上会转换为:
    objc_msgSend(dog, @selector(run:street:),100,@"华尔街");
    注:由于xcode5.0开始苹果不建议我们使用底层的代码,所以我们需要在target->build setting->搜索msg->将YES改为NO之后import <objc/message.h>才能使用objc_msgSend方法。

    2、KVO的底层实现

    概述:KVO是通过"isa-swizzling"技术来实现的,当一个对象注册观察者时,这个对象的isa指针被修改指向一个中间类。
    当观察A类型的对象时,在运行时会创建了一个继成自A类的NSKVONotifying_A类,且为NSKVONotifying_A重写观察属性的setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察者属性值的更改情况。
    因为本文主要讲解runtime的作用以及项目中的应用,所以这里就不做过多的陈述,关于KVO和isa指针不了解的同学可以移步我的另一篇Objective-C isa指针及KVO实现原理进行学习。

    二、runtime常见作用

    1、动态获取成员属性
    通过class_copyPropertyList获取属性列表,此方法会获得所有通过@property展开的成员属性,包括私有属性。

    unsigned int count = 0;
    NSMutableArray *nameArray = [NSMutableArray array];
    /**
         @param class 你想要检测的类  Class类型
         @param count 数组的个数
         @return 返回c语言数组  数组里元素是  objc_property_t 类型
         */
    objc_property_t *properList =  class_copyPropertyList([Dog class],&count);
    for (int i = 0; i<count; i++) {
        const char *name =  property_getName(properList[i]);
        NSString *ocName = [NSString stringWithUTF8String:name];
        [nameArray addObject:ocName];
    }
        NSLog(@"属性名:%@",nameArray);
    

    2、动态获得成员变量
    通过class_copyIvarList获取成员变量列表,此方法会获得所有通过@property展开的成员属性以及声明的成员变量,包括私有成员变量。

     unsigned int count = 0;
        NSMutableArray *nameArray = [NSMutableArray array];
        NSMutableArray *typeArray = [NSMutableArray array];
         /**
         @param class 你想要检测的类  Class类型
         @param count 数组的个数
         @return 返回c语言数组  数组里元素是  Ivar 类型
         */
        Ivar *vars = class_copyIvarList([Dog class], &count);
        for (int i = 0; i<count; i++) {
            
            const char *name = ivar_getName(vars[i]);
            const char *type = ivar_getTypeEncoding(vars[i]);
            NSString *ocName = [NSString stringWithUTF8String:name];
            NSString *ocType = [NSString stringWithUTF8String:type];
            [nameArray addObject:ocName];
            [typeArray addObject:ocType];
            
            /*
             类型说明:基本类型 q:NSInteger   d:double   f:float  i:int  c:BOOL
             引用类型:以字符串显示  如:NSString:"NSString"  NSArray:"NSArray"
             不同类型编码请参考:
             https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
             */
            
        }
        NSLog(@"变量名:%@-----变量类型:%@",nameArray,typeArray);
    

    3、动态获得实例方法
    通过class_copyMethodList获得实例方法列表,通过method_copyReturnType获得返回类型,通过method_copyArgumentType获得各个参数类型,代码如下

     unsigned int count = 0;
        NSMutableArray *methodArray = [NSMutableArray array];
        Method *methodList = class_copyMethodList([Dog class],&count);
        for (int i = 0 ; i<count; i++) {
            NSMutableDictionary *methodDic = [NSMutableDictionary dictionary];
            // 方法名
            SEL method = method_getName(methodList[i]);
            // 返回类型
            const char *returnTypeName = method_copyReturnType(methodList[i]);
            // 参数个数  默认会多两个  第一个接受消息的对象  第二个selector  原因:objc_msgSend(id, SEL)前两个参数
            int argumentCount = method_getNumberOfArguments(methodList[i]);
            
            methodDic[@"methodName"] = NSStringFromSelector(method);
            methodDic[@"returnType"] = [NSString stringWithUTF8String:returnTypeName];
            for (int j = 0; j<argumentCount; j++) {
                NSString *key = [NSString stringWithFormat:@"param%i",j+1];
                methodDic[key] = [NSString stringWithUTF8String:method_copyArgumentType(methodList[i], j)];
            }
            [methodArray addObject:methodDic];
            
        }
        //  @对象类型  v为void   :为selector
        NSLog(@"%@",methodArray);
    

    4、动态添加方法实现
    通过class_addMethod动态添加方法:通过[dog performSelector:@selector(haha)];执行haha的方法,因为Dog类中并没有haha方法,所以执行时会crash,我们可以通过如下代码,动态添加方法实现:

    // class: 给哪个类添加方法
    // SEL: 方法选择器,即调用时候的名称(只是一个编号)
    // IMP: 方法的实现(函数指针)
    // type: 方法类型,(返回值+隐式参数) v:void @对象->self :表示SEL->_cmd
    class_addMethod([Dog class], @selector(haha),(IMP)abc, "v@:");
    // 任何方法默认都有两个隐式参数,self,_cmd
    void abc(id self, SEL _cmd) {
        NSLog(@"哈哈大笑");
    }
    

    5、方法交换
    通过method_exchangeImplementations实现方法的交换:在Dog类的load方法里添加如下方法,实现方法的交换。(写在load方法中的原因:当类被加载到内存中是会调用,执行比较早,并且不会多次调用)

    +(void)load{
         Method orginMethod = class_getInstanceMethod([Dog class], @selector(run:street:));
         Method myMethod = class_getInstanceMethod([Dog class], @selector(myRun:street:));
        method_exchangeImplementations(orginMethod, myMethod);
    }
    
    - (NSString *)myRun:(NSInteger)runLong street:(NSString *)streetName{
        NSLog(@"交换方法了");
        return nil;
    }
    

    三、实际开发中的应用

    说明:runtime在开发中的应用有很多,但知识点都是大致相同,为了控制篇幅这里只给出了某个知识点的一种应用,其他的应用可自己尝试实现,如果想看其他实现的代码可以给我留言。

    1、给控件添加block回调方法
    知识点: 动态关联两个对象
    需求:给UIButton添加点击点击事件回调的block方法
    思路:给UIButton添加一个扩展方法,传入一个block,再在button的点击事件里执行block;
    还可以做:给类的catagory添加属性
    实现代码:

    @interface UIButton (Blocks)
    - (void)handleControlEvent:(UIControlEvents)event withBlock:(void (^)(UIButton *btn))block;
    @end
    @implementation UIButton (Blocks)
    static char overviewKey;
    - (void)handleControlEvent:(UIControlEvents)event withBlock:(void (^)(UIButton *btn))block {
    // 关联对象方法
    // object:给哪个对象添加关联
    // key:被关联者的索引key
    // value:被关联者
    // policy:关联时采用的协议,有assign,retain,copy等协议,一般使用OBJC_ASSOCIATION_RETAIN_NONATOMIC
        objc_setAssociatedObject(self, &overviewKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
        [self addTarget:self action:@selector(callActionBlock:) forControlEvents:event];
    }
    - (void)callActionBlock:(id)sender {
    // 通过key 获取关联对象
        void (^block)(UIButton *btn) = objc_getAssociatedObject(self, &overviewKey);
        if (block) {
            block(sender);
        }
    }
    @end
    

    2、自定义类型自动归档、解归档
    概述: 归档,持久化保存数据的一种方法。系统的类如果遵循了NSCoding协议都可以直接进行归档(如:NSString,NSArray等),但是自定义对象如果要进行归档,需要:
    1、遵循NSCoding协议
    2、实现解档方法:-(id)initWithCoder:(NSCoder *)aDecoder和归档方法:-(void)encodeWithCoder:(NSCoder *)aCoder

    知识点: 获取一个类的成员变量
    需求:一劳永逸的给自定义类型实现自动归档
    思路:获取自定义类的成员变量,获取成员变量字符串,再通过KVC取值,实现归/解档。
    还可以做:字典转模型、获取系统类的私有属性,进行操作。如 UITextField占位文字的颜色 [self setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
    实现代码:

    // 归档
    - (void)encodeWithCoder:(NSCoder *)aCoder{
        // 遍历,对父类的属性执行归档方法
        Class c = self.class;
        while (c &&c != [NSObject class]) {
            unsigned int outCount = 0;
            Ivar *ivars = class_copyIvarList([self class], &outCount);
            for (int i = 0; i < outCount; i++) {
                Ivar ivar = ivars[i];
                NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
                // 通过KVC取值
                id value = [self valueForKeyPath:key];
                [aCoder encodeObject:value forKey:key];
            }
            free(ivars);
            c = [c superclass];
        }
    
    
    }
    
    // 解档
    - (instancetype)initWithCoder:(NSCoder *)aDecoder{
        if (self =  [super init]) {
            // 遍历,对父类的属性执行解档方法
            Class c = self.class;
            while (c &&c != [NSObject class]) {
                unsigned int outCount = 0;
                Ivar *ivars = class_copyIvarList(c, &outCount);
                for (int i = 0; i < outCount; i++) {
                    Ivar ivar = ivars[i];
                    NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
                    id value = [aDecoder decodeObjectForKey:key];
                    [self setValue:value forKey:key];
                }
                free(ivars);
                c = [c superclass];
            }
    
        }
        return self;
    }
    

    3、数组越界crash处理
    知识点:获取某个类的方法、方法交换
    需求:如果数组越界,避免程序闪退
    思路:首先看一下数组越界后所报的原因

    数组越界
    报错原因提到了"__NSArrayI"而不是"NSArray"这是因为:NSArray这个类在设计的时候采用了“抽象工厂”模式,内部是个类簇,真正起作用的是内部的:"__NSArray0"(空数组)、"__NSSingleObjectArrayI"(个数为1的数组),"__NSArrayI"(不可变数组)以及"__NSArrayM"(可变数组)。所以我们只需要给NSArray添加个category替换这几个类的objectAtIndex:方法即可。
    还可以做:截获系统方法,替换成自己的方法。如:字典添加nil的crash,按钮暴力点击重复响应,字体大小适配等
    代码:
    + (void)load{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            @autoreleasepool {
                [self exchangeMethodWithClass:objc_getClass("__NSArray0") orginSelector:@selector(objectAtIndex:) customSelector:@selector(emptyObjectIndex:)];
                [self exchangeMethodWithClass:objc_getClass("__NSSingleObjectArrayI") orginSelector:@selector(objectAtIndex:) customSelector:@selector(singleArrObjectIndex:)];
                [self exchangeMethodWithClass:objc_getClass("__NSArrayI") orginSelector:@selector(objectAtIndex:) customSelector:@selector(arrObjectIndex:)];
                [self exchangeMethodWithClass:objc_getClass("__NSArrayM") orginSelector:@selector(objectAtIndex:) customSelector:@selector(mutableObjectIndex:)];
                [self exchangeMethodWithClass:objc_getClass("__NSArrayM") orginSelector:@selector(insertObject:atIndex:) customSelector:@selector(mutableInsertObject:atIndex:)];
            }
        });
    }
    
    + (void)exchangeMethodWithClass:(Class)class orginSelector:(SEL)orginS customSelector:(SEL)customS{
        Method orginMethod = class_getInstanceMethod(class, orginS);
        Method customMethod = class_getInstanceMethod(class, customS);
        method_exchangeImplementations(orginMethod, customMethod);
    }
    
    - (id)emptyObjectIndex:(NSInteger)index{
        return nil;
    }
    
    - (id)singleArrObjectIndex:(NSInteger)index{
        @autoreleasepool {
            if (index >= self.count || index < 0) {
                return nil;
            }
            return [self singleArrObjectIndex:index];
        }
    }
    
    - (id)arrObjectIndex:(NSInteger)index{
        @autoreleasepool {
            if (index >= self.count || index < 0) {
                return nil;
            }
            return [self arrObjectIndex:index];
        }
    }
    
    - (id)mutableObjectIndex:(NSInteger)index{
        @autoreleasepool {
            if (index >= self.count || index < 0) {
                return nil;
            }
            return [self mutableObjectIndex:index];
        }
    }
    
    - (void)mutableInsertObject:(id)object atIndex:(NSUInteger)index{
        @autoreleasepool {
            if (object) {
                [self mutableInsertObject:object atIndex:index];
            }
        }
    }
    - (id)myObjectAtIndex:(NSUInteger)index
    {
        @autoreleasepool {
            if (index < self.count) {
                return [self myObjectAtIndex:index];
            } else {
                return nil;
            }
        }
    }
    

    到这里本篇文章基本结束,文中所涉及代码都在这里
    最后:喜欢我文章的可以多多点赞和关注,您的鼓励是我写作的动力。O(∩_∩)O~

    相关文章

      网友评论

        本文标题:runtime知识点及项目中的应用

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