• 对runtime的总结:让你会用Runtime


    导语
    Runtime,简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制,是一套底层的纯C语言的API,我们平时所编写的OC代码,在程序的运行过程中都转成了runtime的代码,平时调方法都是转成了objc_msgSend函数。大家应该都听过或者用过一个叼库IQKeyboard,它只需导入进工程,不需要写一行代码就能实现功能,其实就是用的runtime机制实现的,俗称黑魔法。

    runtime的作用:

    Objective-C 的 Runtime 铸就了它动态语言的特性,这些深层次的知识虽然平时写代码用的少一些,但是却是每个 Objc 程序员需要了解的。

    • 在程序运行过程中,动态的创建一个类(KVO实现原理)以及动态的为每个类添加或修改属性、方法
    • 遍历一个类中所有的成员变量以及方法(NSCoding的自动解档和归档、MJExtension的实现)
    • 交换两个方法的实现(Swizzle黑魔法)
    • ...
    KVO实现原理:

    当你第一次观察某个对象时,runtime会创建一个新的类NSKVONOtifying_class,该类继承自原先的class。在这个新的派生类中,它重写了所有被观察属性的setter方法,然后将对象的isa指针指向新创建的NSKVONOtifying_class(这个指针告诉Objective-C的runtime某个对象到底是哪种类型的对象)。所以该对象神奇地变成了新的子类的实例。当对象的属性发生改变时,会触发setter方法,但这个方法已经被重写了,并且在方法的内部添加了发送通知机制,实现了自动触发通知机制,这就是KVO实现的原理。

    交换两个方法的实现

    先看下面例子:
    创建一个Student类,实现两个类方法:studyJava和studyC
    使用runtime交换两个方法的实现:

        #import<objc/runtime.h>//别忘加运行时头文件
        //获取class中的类方法
        Method studyJava = class_getClassMethod([Student class], @selector(studyJava));
        Method studyC = class_getClassMethod([Student class], @selector(studyC));
        //交换方法的实现
        method_exchangeImplementations(studyJava, studyC);
    
        [Student studyC];
        [Student studyJava];

    运行后会发现:让他先学C再学Java就是不听,结果是先学Java。

    看到这你会觉得并没有什么卵用,是的,这样写意义不大,但我们换个角度想,如果交换的是系统的方法呢?假如我们要统一监听控制器何时被销毁,用runtime可以让我们只写一次代码,看例子:
    首先新建一个UIViewController的分类,然后在里面写上代码:

    #import "UIViewController+Extension.h"
    #import <objc/runtime.h>
    
    @implementation UIViewController (Extension)
    //将交换方法写在load方法中,使得在该类被加载如内存中就调用该方法以交换方法
    + (void)load {
        //class_getInstanceMethod :获取实例方法
        Method dealloc = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
        Method jyh_dealloc = class_getInstanceMethod(self, @selector(jyh_dealloc));
        method_exchangeImplementations(dealloc, jyh_dealloc);
    }
    
    //自己实现的dealloc方法,监听销毁
    - (void)jyh_dealloc {
        NSLog(@"%@ is dealloc", self);
        [self jyh_dealloc];//调用原来的dealloc方法
    }
    
    @end

    这样便实现了所用控制器销毁的监听。以上的例子只是为了说明runtime的作用,它可以有很多扩展,比如:将[UIImage imageNamed:]换成自己的方法,在方法中加入对图片名称的操作,就可以实现换肤或适配等功能;可以替换viewDidLoad方法实现用户进入页面的统计,不用每个控制器都复制粘贴同样的代码(或许你会想我们可以使用 OOP 的特性之一,继承的方式来解决这个问题。创建一个基类,在这个基类中添加统计方法,其他类都继承自这个基类。然而,这种方式修改还是很大,而且定制性很差。以后有新人加入之后,都要嘱咐其继承自这个基类,所以这种方式并不可取。)IQKeyboard库不需要写一行代码就能实现功能,就是通过这一原理实现的。

    遍历类中的所有成员变量

    有时候我们要对一些信息进行归档,如用户信息类UserInfo,这将需要重写initWithCoder和encodeWithCoder方法,并对每个属性进行encode和decode操作。那么问题来了:当属性只有几个的时候可以轻松写完,如果有几十个属性呢?我们可以用runtime提供的函数遍历Model自身所有属性,并对属性进行encode和decode操作。

    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
        if(self = [super init]) {
            unsigned int outCount;
            //获得所传入类的成员变量
            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)];
                [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
            }
            free(ivars);//别忘记释放
        }
        return self;
    }
    
    - (void)encodeWithCoder:(NSCoder *)aCoder {
        unsigned int outCount;
        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)];
            [aCoder encodeObject:[self valueForKey:key] forKey:key];
        }
        free(ivars);
    }
    访问苹果私有属性

    利用runtime获取属性名

    objc_property_t *properties = class_copyPropertyList([UITextField class], &count);

    这样就可以获取到UITextField中的所有属性,包括在头文件中苹果不想让你看到的。
    这有什么用呢?假设我们有一个需求,想改变输入框中占位文字的颜色,用runtime的这个作用可以让我们很方便的实现:
    先将输入框所包含的属性打印出来:

    + (void)getProperties
    {
        unsigned int count = 0;
    
        objc_property_t *properties = class_copyPropertyList([UITextField class], &count);
    
        for (int i = 0; i<count; i++) {
            // 取出属性
            objc_property_t property = properties[i];
    
            // 打印属性名字
            JYHLog(@"%s   <---->   %s", property_getName(property), property_getAttributes(property));
        }
    
        free(properties);
    }

    我们可以看到这样一个属性:


    查看私有属性


    顾名思义,这就是占位的Label,我们只要利用KVC就可以将其改成我们想要的样子:

    [self setValue:[UIColor whiteColor] forKeyPath:@"_placeholderLabel.textColor"];

    字典转模型和动态添加属性用NSObject+runtime的扩展类实现了

    NSObject+runtime.h

    #import <Foundation/Foundation.h>

    @interface NSObject (Runtime)

    ///  给定一个字典,创建 self 类对应的对象

    ///  @param dict 字典

    ///  @return 对象

    + (instancetype)cz_objWithDict:(NSDictionary *)dict;

     ///  获取类的属性列表数组

    ///  @return 类的属性列表数组

    + (NSArray *)cz_objProperties;

    @end

    NSObject+runtime.m

    #import "NSObject+Runtime.h"

    #import <objc/runtime.h>

    @implementation NSObject (Runtime)

    // 所有字典转模型框架,核心算法!

    + (instancetype)cz_objWithDict:(NSDictionary *)dict {

        // 实例化对象

        id object = [[self alloc] init];

        // 使用字典,设置对象信息

        // 1> 获得 self 的属性列表

        NSArray *proList = [self cz_objProperties];   

        // 2> 遍历字典

        [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {

            

            NSLog(@"key %@ --- value %@", key, obj);

            // 3> 判断 key 是否在 proList

            if ([proList containsObject:key]) {

                //  说明属性存在,可以使用 `KVC` 设置数值

                [object setValue:obj forKey:key];

            }

        }];

        

        return object;

    }

     

    const char * kPropertiesListKey = "CZPropertiesListKey";

     

    + (NSArray *)cz_objProperties {

        

        // --- 1. `关联对象`中获取对象属性,如果有,直接返回!

        /**

         获取关联对象 - 动态添加的属性

         

         参数:

         1. 对象 self

         2. 动态属性的 key

         

         返回值

         动态添加的`属性值`

         */

        NSArray *ptyList = objc_getAssociatedObject(self, kPropertiesListKey);

        if (ptyList != nil) {

            return ptyList;

        }

        

        // 调用运行时方法,取得类的属性列表

        // Ivar 成员变量

        // Method 方法

        // Property 属性

        // Protocol 协议

        /**

         参数

         1. 要获取的类

         2. 类属性的个数指针

         

         返回值

         所有属性的`数组`C 语言中,数组的名字,就是指向第一个元素的地址

         

         retain/create/copy 需要 release,最好 option + click

         */

        unsigned int count = 0;

        objc_property_t *proList = class_copyPropertyList([self class], &count);

        

        NSLog(@"属性的数量 %d", count);

        // 创建数组

        NSMutableArray *arrayM = [NSMutableArray array];

        

        // 遍历所有的属性

        for (unsigned int i = 0; i < count; i++) {

            

            // 1. 从数组中取得属性

            /**

             C 语言的结构体指针,通常不需要 `*`

             */

            objc_property_t pty = proList[i];

            

            // 2. pty 中获得属性的名称

            const char *cName = property_getName(pty);

            

            NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];

            

    //        NSLog(@"%@", name);

            // 3. 属性名称添加到数组

            [arrayM addObject:name];

        }

        

        // 释放数组

        free(proList);

        

        // --- 2. 到此为止,对象的属性数组已经获取完毕,利用关联对象,动态添加属性

        /**

         参数

         

         1. 对象 self [OC class 也是一个特殊的对象]

         2. 动态添加属性的 key,获取值的时候使用

         3. 动态添加的属性值

         4. 对象的引用关系

         */

        objc_setAssociatedObject(self, kPropertiesListKey, arrayM.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

        

        return arrayM.copy;

    }

     

    @end

  • 相关阅读:
    The Country List
    hdoj1215--七夕节(数学)
    Poj 1654--Area(叉积)
    Poj2229--Sumsets(递推)
    数据预处理 center&scale&box-cox
    caret 分类回归树 用法
    ensemble 的2篇入门 文章
    数组 array 矩阵 list 数据框 dataframe
    R list frame, matrix
    R 如何 隐藏坐标轴
  • 原文地址:https://www.cnblogs.com/ansyxpf/p/5678318.html
Copyright © 2020-2023  润新知