zoukankan      html  css  js  c++  java
  • runtime 运行时机制 + 应用场景

     

     

      ——————注:(非海腾原创)    


    一、runtime 运行时机制

    • Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等。
    • 这种特性意味着Objective-C不仅需要一个编译器,还需要一个运行时系统来执行编译的代码。对于Objective-C来说,这个运行时系统就像一个操作系统一样:它让所有的工作可以正常的运行。这个运行时系统即Objc Runtime。Objc Runtime其实是一个Runtime库,它基本上是用C和汇编写的,这个库使得C语言有了面向对象的能力。
    • 对于C语言,函数的调用在编译的时候会决定调用哪个函数。对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
    • 在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。
      在编译阶段,C语言调用未实现的函数就会报错。

    二、运行时的作用

    • 能获得某个类的所有成员变量
    • 能获得某个类的所有属性
    • 能获得某个类的所有方法
    • 交换方法实现
    • 能动态添加一个成员变量
    • 能动态添加一个属性
    • 字典转模型
    • runtime归档/反归档

    常见的函数,头文件

    #import<objc/runtime.h> : //成员变量,类,方法
    class_copyIvarList : 获得某个类内部的所有成员变量
    class_copyMethodList : 获得某个类内部的所有方法
    class_getInstanceMethod : 获得某个具体的实例方法 (对象方法,减号-开头)
    class_getClassMethod : 获得某个具体的类方法 (加号+开头)
    method_exchangeImplementations : 交换两个方法的实现
    #import<objc/message.h> : //消息机制
    objc_msgSend(...)

    三、应用场景

    场景1 ----------------------- runtime 发送消息 ----------------------

    方法的调用本质是,对象发送消息

      objc/msgSend 只有对象才能发送消息,因此以objc开头
      导入 #import <objc/message.h> 或者直接导入 #import <objc/runtime.h>
      注意 Xcode 6  之后代码检查 单独使用<objc/message.h>会报错
      builtSeting 修改 Enable Strict Checking of objc_msgSend Calls -> NO 才能调用 objc_msgSend

    我们创建一个对象Dog 自定义一个实例方法和类方法,并实现方法

    #import <Foundation/Foundation.h>
    
    @interface Dog : NSObject
    
    - (void)run;
    
    + (void)eat;
    
    
    - (void)run
    {
        NSLog(@"一只狗正在奔跑。。。。");
    }
    
    + (void)eat
    {
        NSLog(@"一只狗正在吃。。。。");
    }
    @end

    然后我们在vc里面使用

    // 创建对象 -> 调用方法

    Dog *d = [[Dog alloc] init];
    // 调用方法 -> 实例方法

    // [d run];

    // 系统底层本质 -> 让对象发消息
    objc_msgSend(d, @selector(run));  // 等同于  [d run];
    
    // 调用方法 -> 类方法

    // [Dog eat];

       objc_msgSend([Dog class], @selector(eat));   // 等同于  [Dog eat];

    消息机制原理
    对象根据方法编号SEL去映射表查找对应方法的实现,即我们在调用实例方法的时候,其实是实例对象d,在发送消息,消息的实现,其实是SEL,根据方法编号,去映射表查找对应方法的实现.类方法本质是[Dog class],发什么消息.


    场景2 ------------------- runtime 交换方法 ----------------------

    使用场景,系统自带方法功能不够用,给系统自带的方法扩展一些功能,并保存原有功能.

    • 实现方法 1 -> 继承系统的类, 重写方法.
    • 实现方法 2 -> runtime 交换方法

    案例:这里我们写一个UIImage的类目,来保证UIImage,不会被渲染,同时,如果图片为空,会打印提示.

    #import <UIKit/UIKit.h>
    
    @interface UIImage (Image)
    
    // 创建一个类方法
    
    // 传入 一个字符串 -> 返回 不被 渲染的原始图片
    
    + (id)ImageOriginalWithStrName:(NSString *)name;
    @end

    在.m进行实现, 使用method_exchangeImplementations(method1, method2)方法交换,详情看代码注释.

    #import "UIImage+Image.h"
    #import <objc/runtime.h>
    
    @implementation UIImage (Image)
    
    
    
    // 加载内存时调用
    
    + (void)load
    {
        // 交换方法
    
        // 获取 ImageOriginalWithStrName: 方法
        Method imageWithName = class_getClassMethod(self, @selector(ImageOriginalWithStrName:));
    
        // 获取 imageName 方法
        Method imageName = class_getClassMethod(self, @selector(imageNamed:));
    
        // 交换方法地址, 相当于交换实现
    
        method_exchangeImplementations(imageWithName, imageName);
    
    }
    
    // 注意 // 这里 返回值是一个函数结果类型 使用instancetype 会产生类型不匹配, 所以使用id
    // 不能在改分类UIImage中重写 imageNamed:因为系统会把imageNamed:原来的功能覆盖掉
    // 分类中不能调用super本身
    
    + (id)ImageOriginalWithStrName:(NSString *)name
    {
        UIImage *image = [[self ImageOriginalWithStrName:name] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    
        if (image == nil) {
            NSLog(@"加载图片为空...");
        }
    
        return image;
    
    }
    
    @end

    在vc中使用

        UIImage *image = [UIImage imageNamed:@"123"];
    
        // 这里通过runtime 交换自定义方法 和 UIImage 自身的imageNamed: 来实现图片加载 不被渲染
        // 代码在 #import "UIImage+Image.h" 类目中

    // 传入了一个空123


    log日志

    场景3-------------------- runtime 动态添加方法 ---------------------

    开发场景:
    如果一个类方法非常多,加载类到内存中的时候,会比较耗费资源,需要给给个方法生成映射表,这里可以使用动态添加方法给某个类.
    经典面试题 有没有使用过 performSelector 其实主要是想问你有没有动态添加过方法.

    // 下面是简单使用,继续以Dog 对象为例
    
    [d performSelector:@selector(jump)];
    // 默认的狗 没有jump 这个方法实现, 直接调用会出错,可以使用 performSelector 调用就不会出错.
    // --> 动态添加方法 不会报错

    然后我们可以到Dog对象中进行添加动态方法

    // void(*)()
    // 默认方法 都有两个隐式参数
    
    // 定义添加的方法
    void jump (id self, SEL sel)
    {
        NSLog(@" eat ...... %@ --- %@  ", self, NSStringFromSelector(sel));
    }
    
    
    // 当一个对象调用未实现的方法,会调用(+ (BOOL)resolveInstanceMethod:(SEL)sel
    )这个方法处理,并且会把这个对应方法列表传过来
    // 所以动态添加方法, 我们可以在这里做判断, 为我们未实现的方法动态添加自己的方法.
    
    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        if (sel == @selector(jump)) {
    
            // <#__unsafe_unretained Class cls#>  参数1 给哪个类添加方法
            // <#SEL name#>                       参数2 添加放啊编号
            // <#IMP imp#>                        参数3 添加方法函数实现 (函数地址)
            // <#const char *types#>    参数4 函数的类型 (返回值 + 参数类型) v:void @:对象-> self :表示SEL -> _cmd
    
            class_addMethod(self, @selector(jump), jump, "v@:");
        }
    
        return [super resolveInstanceMethod:sel];
    }

    // 实现动态添加方法后,在vc中,使用不会出错.

    场景4 -------------------- runtime 给分类添加属性 ---------------

    原理给一个类声明属性,其实是本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到内存空间
    案例 : 这里在类目中对NSObject扩展name 属性 可以直接给属性赋值使用

    #import <Foundation/Foundation.h>
    
    @interface NSObject (Property)
    @property (nonatomic, strong) NSString *name; // 添加一个name属性
    @end

    在.m 中使用objc_setAssociatedObject动态添加方法

    import "NSObject+Property.h"
    #import <objc/runtime.h>
    
    static const char *key = "name";
    
    @implementation NSObject (Property)
    
    - (NSString *)name
    {
        // 根据关联的key,获取关联的值
        return objc_getAssociatedObject(self, key);
    }
    
    
    - (void)setName:(NSString *)name
    {
        // 参数1 <#id object#> 给那个对象添加关联
        // 参数2 <#const void *key#> 关联的key 值,通过这个key 值获取
        // 参数3 <#id value#> 关联的value
        // 参数4 <#objc_AssociationPolicy policy#> 关联的策略
    
    
    //    typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    //        OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    //        OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
    //                                                *   The association is not made atomically. */
    //        OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied.
    //                                                *   The association is not made atomically. */
    //        OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
    //                                                *   The association is made atomically. */
    //        OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
    //                                                *   The association is made atomically. */
    //    };
    
        objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    
    @end

    VC 使用.能正常获取该属性,并能操作使用

        NSObject *objc = [[NSObject alloc] init];
        objc.name = @"xxx";
        NSLog(@"%@", objc.name);

    场景5 -------------------- runtime 字典转模型 ---------------

    设计模型 : 字典转模型的第一步
    模型属性, 通常需要和字典中的key一一对应
    通过创建一个分类,专门根据字典生成对应属性的字符串 (高效率的字典转模型)

    我们可以正常通过写类目来实现 NSObject+DictionaryToModel

    // 自动打印属性字符串
    // 写一个类方法
    + (void)transformToModelByDictionary:(NSDictionary *)dict;
    
    // 实现
    + (void)transformToModelByDictionary:(NSDictionary *)dict
    {
    
        // 根据类别拼接属性字符串代码
        NSMutableString *str = [NSMutableString string];
    
        // 遍历字典,把字典中的所有key取出来;生成对应的属性代码
    
        [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
    
            // 对各类新进行分类, 抽取出来
    
            NSString *type;
    
            // 需要 理解 系统底层 数据结构类型
            // 可以自行断点查看 各类型底层类型
    
            if ([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) {
                type = @"NSString";
            } else if ([obj isKindOfClass:NSClassFromString(@"__NSArrayI")])
            {
                type = @"NSArray";
            } else if ([obj isKindOfClass:NSClassFromString(@"__NSArrayM")])
            {
                type = @"NSMutableArray";
            }
            else if ([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")])
            {
                type = @"NSNumber";
            } else if ([obj isKindOfClass:NSClassFromString(@"__NSDictionaryI")])
            {
                type = @"NSDictionary";
            } else if ([obj isKindOfClass:NSClassFromString(@"__NSDictionaryM")])
            {
                type = @"NSMutableDictionary";
            }
    
    
            // 属性字符串
            NSString *property;
            if ([type containsString:@"NS"]) {
                property = [NSString stringWithFormat:@"@property (nonatomic, strong) %@ *%@", type, key];
            }
            else
            {
                 property = [NSString stringWithFormat:@"@property (nonatomic, assign) %@ %@", type, key];
            }
    
            // 每生成一对属性字符串 就自动换行
            [str appendFormat:@"
    %@
    ", property];
    
        }];
    
        // 打印出拼接的字符串
        NSLog(@"对应属性 -> %@", str);
    
    
    }

    vc中测试

        NSArray *array = [NSArray arrayWithObjects:@1, @2, @3, @4, nil];
        NSMutableArray *arr = [NSMutableArray arrayWithArray:array];
    
        NSDictionary *dic = [NSDictionary dictionaryWithObject:@"dfdf" forKey:@"dfsdf"];
    
        [NSObject transformToModelByDictionary:@{@"name" : @"str", @"num" : array, @"count" : @0, @"hah": arr, @"dic" : dic}];

    打印结果

    // 字典转模型KVC方式
    dic setValuesForKeysWithDictionary:<#(nonnull NSDictionary<NSString *,id> *)#>
    必须保证属性和字典中key值一一对应, 如果不一致 会调用 setValue:forUndefinedKey: 重写该方法可以覆盖系统方法 可以继续KVC 字典转模型

    字典转模型 -> runtime
    -> MJEXtension

    #import "NSObject+Model.h"
    #import <objc/runtime.h>
    
    @implementation NSObject (Model)
    
    
    + (instancetype)modelWithDictionary:(NSDictionary *)dictionary
    {
    
    
        // 思路:遍历模型中所有属性-》使用运行时
    
        // 0.创建对应的对象
        id objc = [[self alloc] init];
    
        // 1.利用runtime给对象中的成员属性赋值
    
        // class_copyIvarList:获取类中的所有成员属性
        // Ivar:成员属性的意思
        // 第一个参数:表示获取哪个类中的成员属性
        // 第二个参数:表示这个类有多少成员属性,传入一个Int变量地址,会自动给这个变量赋值
        // 返回值Ivar *:指的是一个ivar数组,会把所有成员属性放在一个数组中,通过返回的数组就能全部获取到。
        /* 类似下面这种写法
    
         Ivar ivar;
         Ivar ivar1;
         Ivar ivar2;
         // 定义一个ivar的数组a
         Ivar a[] = {ivar,ivar1,ivar2};
    
         // 用一个Ivar *指针指向数组第一个元素
         Ivar *ivarList = a;
    
         // 根据指针访问数组第一个元素
         ivarList[0];
    
         */
        unsigned int count;
    
        // 获取类中的所有成员属性
        Ivar *ivarList = class_copyIvarList(self, &count);
    
        for (int i = 0; i < count; i++) {
            // 根据角标,从数组取出对应的成员属性
            Ivar ivar = ivarList[i];
    
            // 获取成员属性名
            NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
    
            // 处理成员属性名->字典中的key
            // 从第一个角标开始截取
            NSString *key = [name substringFromIndex:1];
    
            // 根据成员属性名去字典中查找对应的value
            id value = dictionary[key];
    
            // 二级转换:如果字典中还有字典,也需要把对应的字典转换成模型
            // 判断下value是否是字典
            if ([value isKindOfClass:[NSDictionary class]]) {
                // 字典转模型
                // 获取模型的类对象,调用modelWithDict
                // 模型的类名已知,就是成员属性的类型
    
                // 获取成员属性类型
                NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
                // 生成的是这种@"@"User"" 类型 -》 @"User"  在OC字符串中 " -> ",是转义的意思,不占用字符
                // 裁剪类型字符串
                NSRange range = [type rangeOfString:@"""];
    
                type = [type substringFromIndex:range.location + range.length];
    
                range = [type rangeOfString:@"""];
    
                // 裁剪到哪个角标,不包括当前角标
                type = [type substringToIndex:range.location];
    
    
                // 根据字符串类名生成类对象
                Class modelClass = NSClassFromString(type);
    
    
                if (modelClass) { // 有对应的模型才需要转
    
                    // 把字典转模型
                    value  =  [modelClass modelWithDictionary:value];
                }
    
    
            }
    
            // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
            // 判断值是否是数组
            if ([value isKindOfClass:[NSArray class]]) {
                // 判断对应类有没有实现字典数组转模型数组的协议
                if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
    
                    // 转换成id类型,就能调用任何对象的方法
                    id idSelf = self;
    
                    // 获取数组中字典对应的模型
                    NSString *type =  [idSelf arrayContainModelClass][key];
    
                    // 生成模型
                    Class classModel = NSClassFromString(type);
                    NSMutableArray *arrM = [NSMutableArray array];
                    // 遍历字典数组,生成模型数组
                    for (NSDictionary *dict in value) {
                        // 字典转模型
                        id model =  [classModel modelWithDictionary:dict];
                        [arrM addObject:model];
                    }
    
                    // 把模型数组赋值给value
                    value = arrM;
    
                }
            }  
            if (value) { // 有值,才需要给模型的属性赋值
                // 利用KVC给模型中的属性赋值
                [objc setValue:value forKey:key];
            }
    
        }
    
        return objc;
    
    }
    @end
     //  测试
    
        NSMutableDictionary *di = [NSMutableDictionary dictionary];
        NSMutableArray *a = [NSMutableArray array];
        [a addObject:dic];
        [di setValue:a forKey:@"dic"];
    
        id model =   [NSObject modelWithDictionary:dic];
    
        NSLog(@"%@", model);
    
    
        id dmodel =   [NSObject modelWithDictionary:di];
    
        NSLog(@"%@", dmodel);

    场景6 -------------------- runtime 快速归档 ---------------

    主要还是通过class_copyIvarList,遍历对象属性,来做事情.

    #import <Foundation/Foundation.h>
    
    @interface NSObject (Extension)
    
    - (NSArray *)ignoredNames;
    - (void)encode:(NSCoder *)aCoder;
    - (void)decode:(NSCoder *)aDecoder;
    
    @end
    #import "NSObject+Extension.h"
    #import <objc/runtime.h>
    
    @implementation NSObject (Extension)
    
    - (void)decode:(NSCoder *)aDecoder {
        // 一层层父类往上查找,对父类的属性执行归解档方法
        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)];
    
                // 如果有实现该方法再去调用
                if ([self respondsToSelector:@selector(ignoredNames)]) {
                    if ([[self ignoredNames] containsObject:key]) continue;
                }
    
                id value = [aDecoder decodeObjectForKey:key];
                [self setValue:value forKey:key];
            }
            free(ivars);
            c = [c superclass];
        }
    
    }
    
    - (void)encode:(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)];
    
                // 如果有实现该方法再去调用
                if ([self respondsToSelector:@selector(ignoredNames)]) {
                    if ([[self ignoredNames] containsObject:key]) continue;
                }
    
                id value = [self valueForKeyPath:key];
                [aCoder encodeObject:value forKey:key];
            }
            free(ivars);
            c = [c superclass];
        }
    }
    
    
    // 设置需要忽略的属性
    - (NSArray *)ignoredNames {
        return @[@"bone"];
    }
    
    
    // 在需要归解档的对象中实现下面方法即可:
    
    
    
    
    //// 在系统方法内来调用我们的方法
    //- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    //    if (self = [super init]) {
    //        [self decode:aDecoder];
    //    }
    //    return self;
    //}
    //
    //- (void)encodeWithCoder:(NSCoder *)aCoder {
    //    [self encode:aCoder];
    //}
    
    
    
    @end

    ----- 最后使用runtime 来写了通过 block回调 直接调用手势识别的action ---------------

    #import <UIKit/UIKit.h>
    
    typedef void(^XXWGestureBlock)(id gestureRecognizer);
    
    @interface UIGestureRecognizer (Block)
    
    
    
    /**
     *  使用类方法 初始化 添加手势
     *
     *  @param block 手势回调
     *
     *  @return block 内部 action 
     *
     *
     *  使用 __unsafe_unretained __typeof(self) weakSelf = self;
     *  防止循环引用
     *
     */
    
    
    + (instancetype)xxw_gestureRecognizerWithActionBlock:(XXWGestureBlock)block;
    
    @end
    #import "UIGestureRecognizer+Block.h"
    #import <objc/runtime.h>
    
    static const int target_key;
    @implementation UIGestureRecognizer (Block)
    
    
    + (instancetype)xxw_gestureRecognizerWithActionBlock:(XXWGestureBlock)block {
        return [[self alloc]initWithActionBlock:block];
    }
    
    - (instancetype)initWithActionBlock:(XXWGestureBlock)block {
        self = [self init];
        [self addActionBlock:block];
        [self addTarget:self action:@selector(invoke:)];
        return self;
    }
    
    /**
     * Returns the value associated with a given object for a given key.
     *
     * @param object The source object for the association.
     * @param key The key for the association.
     *
     * @return The value associated with the key e key for e object.
     *
     * @see objc_setAssociatedObject
     */
    
    //OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
    //__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
    
    - (void)addActionBlock:(XXWGestureBlock)block {
        if (block) {
            objc_setAssociatedObject(self, &target_key, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
        }
    }
    
    - (void)invoke:(id)sender {
        XXWGestureBlock block = objc_getAssociatedObject(self, &target_key);
        if (block) {
            block(sender);
        }
    }
    @end

    VC 中使用,我们可以直接调用手势对象,来实现action,是不是很方便.

    [self.view addGestureRecognizer:[UITapGestureRecognizer xxw_gestureRecognizerWithActionBlock:^(id gestureRecognizer) {
    
            NSLog(@"点击-------");
        }]];
    
    [self.view addGestureRecognizer:[UILongPressGestureRecognizer xxw_gestureRecognizerWithActionBlock:^(id gestureRecognizer) {
    
            NSLog(@"长按-------");
        }]];

    总结:通过学习使用 runtime,我们能更好来了解体会oc底层的运行机制,同时,我们可以使用runtime,来获取系统底层私有方法和属性来使用,动态的添加属性和方法,在有些场景下使用非常的高效,同时我们可以使用runtime,开完成很多好的Category,来高效开发.本文章只是对runtime的一些基础知识的归纳,能够让初学者,更好更快的理解runtime,力图起个抛砖引玉的作用。还有许多关于runtime有意思东西还需要读者自己去探索发现。

    runtime 深入学习 推荐 ->
    顾鹏:http://tech.glowing.com/cn/objective-c-runtime/
    杨潇玉:http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
    南峰子:http://southpeak.github.io/blog/2014/10/25/objective-c-runtime-yun-xing-shi-zhi-lei-yu-dui-xiang/
    叶纯俊http://chun.tips/blog/2014/11/05/bao-gen-wen-di-objective[nil]c-runtime(1)[nil]-self-and-super/



    文/Civel_Xu(简书作者)
    原文链接:http://www.jianshu.com/p/f87b220f48ae
    著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
  • 相关阅读:
    第十四周 Leetcode 315. Count of Smaller Numbers After Self(HARD) 主席树
    POJ1050 To the Max 最大子矩阵
    POJ1259 The Picnic 最大空凸包问题 DP
    POJ 3734 Blocks 矩阵递推
    POJ2686 Traveling by Stagecoach 状态压缩DP
    iOS上架ipa上传问题那些事
    深入浅出iOS事件机制
    iOS如何跳到系统设置里的各种设置界面
    坑爹的私有API
    业务层网络请求封装
  • 原文地址:https://www.cnblogs.com/HaiTeng/p/5626531.html
Copyright © 2011-2022 走看看