ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Objective-C 的 RunTime(四):获取类的详细信息

2021-05-05 15:32:22  阅读:228  来源: 互联网

标签:RunTime Nonnull Nullable 方法 param Objective property 详细信息 属性


目录

相关函数介绍

  • ivar 相关函数

    /*
    获取成员变量的名称
    
    @param v 要检视的成员变量
    @return 一个包含成员变量名称的 C 字符串
    */
    const char * _Nullable ivar_getName(Ivar _Nonnull v);
    
    /*
    获取成员变量的偏移量(相对于实例对象结构体的首地址)
    
    @param v 要检视的成员变量
    @return 成员变量的偏移量(相对于实例对象结构体的首地址)
    @note 如果要访问类型为(id 或其他对象类型的)成员变量,请调用 object_getIvar() 和 object_setIvar(),而不是使用 offset 来直接访问成员变量的数据
    */
    ptrdiff_t ivar_getOffset(Ivar _Nonnull v);
    
    /*
    获取成员变量的类型编码
    
    @param v 要检视的成员变量
    @return 一个包含成员变量类型编码的 C 字符串
    @note 关于类型编码更详细的介绍,请参考苹果开发者文档(https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)
    */
    const char * _Nullable ivar_getTypeEncoding(Ivar _Nonnull v);
    
    /*
    获取实例对象中成员变量的值
    
    @param obj 要检视的实例对象
    @param ivar 描述要读取其值的成员变量的 Ivar 结构体
    @return 由参数 ivar 指定的成员变量的值。如果参数 obj 为 nil,则返回 nil
    @note 如果描述成员变量的 Ivar 结构体已知的话,object_getIvar() 会比 object_getInstanceVariable() 快
    */
    id _Nullable object_getIvar(id _Nullable obj, Ivar _Nonnull ivar);
    
    /*
    设置实例对象中成员变量的值
    
    @param obj 要修改的实例对象
    @param ivar 描述要设置其值的成员变量的 Ivar 结构体
    @param value 成员变量的新值
    @note 具有已知内存管理策略的成员变量(如:ARC 的 strong 和 weak),使用该内存管理策略
      	  具有未知内存管理策略的成员变量,默认使用 unsafe_unretained
    @note 如果描述成员变量的 Ivar 结构体已知的话,object_setIvar() 会比 object_setInstanceVariable() 快
    */
    void object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value);
    
    /*
    设置实例对象中成员变量的值
    
    @param obj 要修改的实例对象
    @param ivar 描述要设置其值的成员变量的 Ivar 结构体
    @param value 成员变量的新值
    @note 具有已知内存管理策略的成员变量(如:ARC 的 strong 和 weak),使用该内存管理策略
     	  具有未知内存管理策略的成员变量,默认使用 strong
    @note 如果描述成员变量的 Ivar 结构体已知的话,object_setIvar() 会比 object_setInstanceVariable() 快
     */
    void object_setIvarWithStrongDefault(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value);
    
    /*
    获取一个指向(为给定实例对象分配的任何额外字节)的指针
    
    @param obj 要检视的实例对象(Objective-C)
    @return 一个指向(为 obj 分配的额外字节)的指针。如果 obj 没有分配任何额外的字节,则返回的指针将未被定义
    @note 此函数返回一个指向(随实例对象分配的任何额外字节)的指针,如由 class_createInstance() 指定的大于 0 的外部字节。此内存跟随在实例对象的普通 Ivar 之后,但可能不紧邻实例对象的最后一个普通 Ivar 
    @note 返回的指针保证是 pointer-size 的对齐,即使实例对象的最后一个 Ivar 后面的区域小于这个对齐。但是比 pointer-size 大的对齐不能被保证,即使实例对象的最后一个 Ivar 后面的区域大于这个对齐
    @note 在垃圾回收机制的环境中,内存会被保守地输入
    */
    void * _Nullable object_getIndexedIvars(id _Nullable obj);
    
    /*
    向类中添加一个新的成员变量
    
    @param cls 要修改的类(必须是一个动态创建的类,不能是一个已存在的现有类)
    @param name 成员变量的名称
    @param size 成员变量的大小
    @param alignment 成员变量的内存对齐
    @param types 成员变量的类型编码
    @return 如果成功添加成员变量则返回 YES。否则返回 NO(例如:该类已经包含了一个具有该名称的成员变量)
    @note 此函数只能在 objc_allocateClassPair() 之后和 objc_registerClassPair() 之前调用
    	  不支持向现有的类中添加成员变量(此函数只能向动态生成的类中添加成员变量)
    @note 该类一定不能是一个元类,不支持将成员变量添加到元类中
    @note 成员变量的最小字节对齐值为 1<<align。成员变量的最小字节对齐取决于成员变量的类型和 CPU 的架构。对于任何指针类型的成员变量,请传递 log2(sizeof(pointer_type))
    */
    BOOL class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types);
    
    /*
    获取给定的类的成员变量的内存布局
    
    @param cls 要检视的类
    @return 成员变量的内存布局
    */
    const uint8_t * _Nullable class_getIvarLayout(Class _Nullable cls);
    
    /*
    设置给定的类的成员变量的内存布局
    
    @param cls 要修改的类
    @param layout 成员变量的内存布局
    */
    void class_setIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout);
    
    /*
    获取给定的类的弱成员变量的内存布局
    
    @param cls 要检视的类
    @return 弱成员变量的内存布局
    */
    const uint8_t * _Nullable class_getWeakIvarLayout(Class _Nullable cls);
    
    /*
    设置给定的类的弱成员变量的内存布局
    
    @param cls 要修改的类
    @param layout 弱成员变量的内存布局
    */
    void class_setWeakIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout);
    
    /*
    获取给定类的指定名称的成员变量
    
    @param cls 要检视的类
    @param name 要获取的成员变量的名称
    @return 一个指向 Ivar 数据结构的指针,其中包含了 cls 类中名字为 name 的成员变量的信息
    */
    Ivar _Nullable class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name);
     
    /*
    获取类的成员变量列表(获取类的 Ivar 列表)
    
    @param 要检视的类
    @param outCount 在函数返回时,用于标识返回的 Ivar 数组的长度。如果传 NULL,则不返回 Ivar 数组的长度
    @return 返回一个 Ivar 类型的指针数组(数组中的每一个 Ivar 指针元素,都用于描述一个由类声明的成员变量)
    		不包含任何由父类声明的成员变量
    		返回的数组包含 *outCount 个指针,后面跟着一个 NULL 终止符
    		必须手动调用 free() 函数释放返回的数组
    		如果该类未声明任何成员变量,或者参数 cls 为 Nil,则该函数返回 NULL 并且 *outCount == 0
     */
    Ivar _Nonnull * _Nullable class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount);
    
  • property 相关函数

    /*
    获取属性的名称
    
    @param property 要检视的属性
    @return 一个包含属性名称的 C 字符串
    */
    const char * _Nonnull property_getName(objc_property_t _Nonnull property);
    
    /*
    获取属性的特性字符串
    
    @param property 要检视的属性
    @return 一个包含属性特性的 C 字符串
    @note 特性字符串的格式在 Objective-C Runtime 编程指南的属性声明中有介绍
    */
    const char * _Nullable property_getAttributes(objc_property_t _Nonnull property);
    
    /*
    获取属性的特性数组(数组中的元素为代表属性某一方面特性的键值对 name-value)
    
    @param property 要检视的属性
    @param outCount 在函数返回时,用于标识返回的 objc_property_attribute_t 数组的长度。如果传 NULL,则不返回 objc_property_attribute_t 数组的长度
    @return 属性的特性数组,必须手动调用 free() 函数释放返回的数组
    */
    objc_property_attribute_t * _Nullable property_copyAttributeList(objc_property_t _Nonnull property, unsigned int * _Nullable outCount);
    
    /*
    获取给定属性给定特性名称的特性值
    
    @param property 要检视的属性
    @param attributeName 表示特性名称的 C 字符串
    @return 如果属性中存在该特性名称所对应的特性值(C 字符串),则返回该特性值。否则返回 nil
    */
    char * _Nullable property_copyAttributeValue(objc_property_t _Nonnull property, const char * _Nonnull attributeName);
    
    /*
    为指定的类添加一个属性
    
    @param cls 要修改的类
    @param name 属性的名称
    @param attributes 属性的特性数组
    @param attributeCount 属性的特性数组的元素个数
    @return 如果属性添加成功,则返回 YES。否则返回 NO(例如:类中已经存在该属性)
    */
    BOOL class_addProperty(Class _Nullable cls, const char * _Nonnull name,
                      const objc_property_attribute_t * _Nullable attributes,
                      unsigned int attributeCount);
    
    /*
    获取指定类中给定名称的属性
    
    @param cls 要检视的类
    @param name 要检视的属性的名称
    @return 用于描述该属性的 objc_property_t 类型的指针。如果类中没有声明该名称的属性,或者参数 cls 传 Nil,则返回 NULL
    */
    objc_property_t _Nullable class_getProperty(Class _Nullable cls, const char * _Nonnull name);
    
    /*
    替换指定类中给定名称的属性(注意:修改的是属性的特性,而不是属性的名称)
    
    @param cls 要修改的类
    @param name 属性的名称
    @param attributes 属性的特性数组
    @param attributeCount 属性的特性数组的元素个数 
    */
    void class_replaceProperty(Class _Nullable cls, const char * _Nonnull name,
                          const objc_property_attribute_t * _Nullable attributes,
                          unsigned int attributeCount);
    
    /*
    获取类的属性列表(获取类的 objc_property_t 列表)
    
    @param cls 要检视的类
    @param outCount 在函数返回时,用于标识返回的 objc_property_t 数组的长度。如果传 NULL,则不返回 objc_property_t 数组的长度
    @return 返回一个 objc_property_t 类型的指针数组(数组中的每一个 objc_property_t 指针元素,都用于描述一个由类声明的属性)
    		不包含任何由父类声明的属性
    		返回的数组包含 *outCount 个指针,后面跟着一个 NULL 终止符
    		必须手动调用 free() 函数释放返回的数组
    		如果该类未声明任何属性,或者参数 cls 为 Nil,则该函数返回 NULL 并且 *outCount == 0
    */
    objc_property_t _Nonnull * _Nullable class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount);
    
  • method 相关函数

    /*
    设置一个方法的实现
    
    @param m 要修改的方法
    @param imp 新的方法实现
    @return 该方法先前的实现
    */
    IMP _Nonnull method_setImplementation(Method _Nonnull m, IMP _Nonnull imp);
    
    /* 
    获取一个方法的实现
    
    @param m 要检视的方法
    @return 方法的实现(一个 IMP 类型的函数指针)
    */
    IMP _Nonnull method_getImplementation(Method _Nonnull m);
    
    /* 
    交换两个方法的实现
    
    @param m1 要与 m2 交换实现的方法
    @param m2 要与 m1 交换实现的方法
    
    本函数是以下内容的原子版本:
    	IMP imp1 = method_getImplementation(m1);
    	IMP imp2 = method_getImplementation(m2);
    	method_setImplementation(m1, imp2);
    	method_setImplementation(m2, imp1);
    */
    void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);
    
    /*
    获取一个方法的名称
    
    @param m 要检视的方法
    @return 方法的名称(一个 SEL 类型的指针)
    @note 如果要获取方法名称的 C 字符串,调用 sel_getName(method_getName(method))
    */
    SEL _Nonnull method_getName(Method _Nonnull m);
    
    /*
    获取一个描述方法的参数和返回值类型的字符串编码
    
    @param m 要检视的方法
    @return 方法的类型编码(一个 C 字符串,该字符串可能为空)
    */
    const char * _Nullable method_getTypeEncoding(Method _Nonnull m);
    
    struct objc_method_description * _Nonnull method_getDescription(Method _Nonnull m);
    
    /*
    获取一个描述方法的返回值类型的字符串编码
    
    @param m 要检视的方法
    @return 一个用于描述方法返回值类型的 C 字符串。必须手动调用 free() 函数释放该字符串
    */
    char * _Nonnull method_copyReturnType(Method _Nonnull m);
    
    /*
    通过引用的方式来返回描述方法的返回值类型的字符串编码
    
    @param m 要检视的方法
    @param dst 一个指向方法返回值类型的字符指针
    @param dst_len 参数 dst 中可以存储的最大的字符个数
    @note 方法返回值类型的字符串编码,将被复制到 dst 中。dst 通过 strncpy(dst, parameter_type, dst_len) 函数填充
    */
    void method_getReturnType(Method _Nonnull m, char * _Nonnull dst, size_t dst_len);
        
    /*
    通过引用的方式来返回方法中单个参数类型的字符串编码
    
    @param m 要检视的方法
    @param index 要获取类型编码的参数的索引
    @param dst 一个指向参数类型编码的字符指针
    @param dst_len 参数 dst 中可以存储的最大的字符个数
    @note 参数类型的字符串编码将被复制到 dst 中。dst 通过 strncpy(dst, parameter_type, dst_len) 函数填充
    	  如果方法 m 中不包含具有 index 索引的参数,则 dst 通过 strncpy(dst, "", dst_len) 函数填充
    */
    void method_getArgumentType(Method _Nonnull m, unsigned int index, char * _Nullable dst, size_t dst_len);
    
    /*
    返回一个方法所接受的参数的个数
    
    @param m 要检视的方法
    @return 参数的个数
    */
    unsigned int method_getNumberOfArguments(Method _Nonnull m);
    
    /*
    获取一个用于描述方法中单个参数类型的字符串编码
    
    @param m 要检视的方法
    @param index 要获取类型的参数的索引
    @return 一个用于描述 index 处参数类型的 C 字符串,如果方法在 index 处没有参数则为 NULL
    		必须手动调用 free() 函数释放该字符串
    */
    char * _Nullable method_copyArgumentType(Method _Nonnull m, unsigned int index);
    
    /*
    为给定类添加一个具有给定名称和给定实现的新方法
    
    @param cls 要修改的类
    @param name 方法选择器,用于标识要添加的方法的名称
    @param imp 方法实现,用于实现方法的一个函数。该函数至少有两个参数:self 和 _cmd
    @param types 方法类型,用于描述该方法的参数和返回值的类型的字符串编码
    @return 如果方法添加成功,则返回 YES。否则返回 NO(例如:该类已经包含了一个具有该方法名称的方法实现)
    @note 通过 class_addMethod() 添加的方法将会覆盖父类的方法实现(因为父类的方法实现没有存储在本类的方法列表中)
    	  通过 class_addMethod() 添加的方法不会覆盖本类的方法实现。如果要修改本类已经存在的方法的实现,请调用 method_setImplementation()
    */
    BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types);
    
    /*
    替换给定类给定方法名称的方法实现
    
    @param cls 要修改的类
    @param name 方法选择器,用于标识要替换的方法的名称
    @param imp 新的方法实现
    @param types 方法类型,用于描述该方法的参数和返回值的类型的字符串编码
    			 因为用于实现方法的函数至少有两个参数:self 和 _cmd,所以第二个和第三个类型编码肯定是 "@:"(第一个类型编码是函数的返回值类型)
    @return 该方法先前的实现
    @note 此函数有 2 种不同的操作方式:
    	  ① 如果方法选择器 name 标识的方法不存在,则添加方法(就像是调用了 class_addMethod() 函数一样)。此时按照给定的方法类型 types 作为方法的类型编码
    	  ② 如果方法选择器 name 标识的方法已经存在,则将会使用新的方法实现 imp 替换原有的方法实现(就像是调用了 method_setImplementation() 函数一样)。此时由参数 types 指定的方法类型将被忽略
    */
    IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types);
    
    /*
    获取给定类给定方法名称的类方法
    
    @param cls 要检视的类
    @param name 方法选择器,用于标识要获取的方法的名称
    @return 一个指向 Method 结构体的指针
     		如果给定的类或其父类不包含方法选择器 name 所指定的类方法,则返回 NULL
    @note 请注意:此函数会搜索父类的方法列表,而 class_copyMethodList() 不会
    */
    Method _Nullable class_getClassMethod(Class _Nullable cls, SEL _Nonnull name);
    
    /*
    获取给定类给定方法名称的对象方法
    
    @param cls 要检视的类
    @param name 方法选择器,用于标识要获取的方法的名称
    @return 一个指向 Method 结构体的指针
    		如果给定的类或其父类不包含方法选择器 name 所指定的对象方法,则返回 NULL
    @note 请注意:此函数会搜索父类的方法列表,而 class_copyMethodList() 不会
    */
    Method _Nullable class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
    
    /*
    获取给定类给定方法名称所对应的方法实现
    
    @param cls 要检视的类
    @param name 方法选择器,用于标识要获取方法实现的方法的名称
    @return 给定类 cls 给定方法名称 name 所对应的方法实现。如果参数 cls 传 Nil,则返回 NULL
    @note class_getMethodImplementation() 的执行速度会比 method_getImplementation(class_getInstanceMethod(cls, name)) 快
    @note 返回的函数指针可能是运行时内部的函数,而不是实际的方法实现
    	  例如:如果类的实例对象没有响应方法选择器,则返回的函数指针将是运行时的消息转发机制的一部分
    */
    IMP _Nullable class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name);
    
    /*
    获取类的方法列表(获取类的 Method 列表)
    
    @param cls 要检视的类
    @param outCount 在函数返回时,用于标识返回的 Method 数组的长度。如果传 NULL,则不返回 Method 数组的长度
    @return 返回一个 Method 类型的指针数组(数组中的每一个 Method 指针元素,都用于描述一个由类实现的方法)
    		不包含任何由父类实现的方法
    		返回的数组包含 *outCount 个指针,后面跟着一个 NULL 终止符
    		必须手动调用 free() 函数释放返回的数组
    		如果该类未实现任何实例方法,或者参数 cls 为 Nil,则该函数返回 NULL 并且 *outCount == 0
    @note 如果要获取一个类的类方法列表,请使用 class_copyMethodList(object_getClass(cls), &count)
    @note 如果在当前类中要获取的方法可能由父类实现,请使用 class_getInstanceMethod() 或者 class_getClassMethod()
    */
    Method _Nonnull * _Nullable class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount);
    
  • protocol 相关函数

    /*
    获取协议的名称
    
    @param proto 要检视的协议
    @return 一个包含协议名称的 C 字符串
    */
    const char * _Nonnull protocol_getName(Protocol * _Nonnull proto);
    
    /*
    返回一个布尔值,用于标识两个协议是否相等
    
    @param proto 第一个协议
    @param other 第二个协议
    @return 如果第一个协议 proto 和第二个协议 other 相等,则返回 YES。否则返回 NO
    */
    BOOL protocol_isEqual(Protocol * _Nullable proto, Protocol * _Nullable other);
    
    /*
    将一个构建完成的协议 addition 添加到另一个正在构建的协议 proto 中
    
    @param proto 要接收 addition 的协议,该协议必须处于正在构建中
    @param addition 要添加到 proto 中的协议,该协议必须已经构建完成
    */
    void protocol_addProtocol(Protocol * _Nonnull proto, Protocol * _Nonnull addition);
    
    /*
    获取一个协议所遵守的协议的数组
    
    @param proto 要检视的协议
    @param outCount 在函数返回时,用于标识返回的 Protocol 数组的长度。如果传 NULL,则不返回 Protocol 数组的长度
    @return 协议 proto 所遵守的协议的数组
    		返回的数组包含 *outCount 个指针,后面跟着一个 NULL 终止符
    		必须手动调用 free() 函数释放返回的数组
    		如果 proto 没有遵守其他协议,则该函数返回 NULL 并且 *outCount == 0
    */
    Protocol * __unsafe_unretained _Nonnull * _Nullable protocol_copyProtocolList(Protocol * _Nonnull proto, unsigned int * _Nullable outCount);
    
    /*
    返回一个布尔值,用于标识一个协议 proto 是否遵守另一个协议 other
    
    @param proto 一个协议
    @param other 另一个协议
    @return 如果 proto 协议遵守 other 协议,则返回 YES。否则返回 NO
    @note 一个协议可以采用如下语法来遵守其他的协议:
    			@protocol ProtocolName <protocol-list>
    	  在尖括号之间的列出的所有协议,都将被认为是 ProtocolName 协议的一部分
    */
    BOOL protocol_conformsToProtocol(Protocol * _Nullable proto, Protocol * _Nullable other);
    
    /*
    向协议中添加一个属性,该协议必须处于正在构建中
    
    @param proto 要修改的协议
    @param name 属性的名称
    @param attributes 属性的特性数组
    @param attributeCount 属性的特性数组的元素个数
    @param isRequiredProperty 如果设置为 YES,则属性(和它的访问器)是必选的。如果设置为 NO,则属性(和它的访问器)是可选的
    @param isInstanceProperty 如果设置为 YES,则属性(和它的访问器)为对象方法。如果设置为 NO,则该属性不会添加到协议中
    */
    void protocol_addProperty(Protocol * _Nonnull proto, const char * _Nonnull name,
                         const objc_property_attribute_t * _Nullable attributes,
                         unsigned int attributeCount,
                         BOOL isRequiredProperty, BOOL isInstanceProperty);
    
    /*
    获取给定协议的给定属性
    
    @param proto 要检视的协议
    @param name 属性的名称
    @param isRequiredProperty 如果设置为 YES,则搜索协议的必选属性。如果设置为 NO,则搜索协议的可选属性
    @param isInstanceProperty 如果设置为 YES,则搜索协议的对象属性。如果设置为 NO,则搜索协议的类属性
    @return 协议 proto 中,由属性名称 name,是否必选属性 isRequiredProperty,是否对象属性 isInstanceProperty 指定的属性。如果没有符合要求的属性,则返回 NULL
    */
    objc_property_t _Nullable protocol_getProperty(Protocol * _Nonnull proto, const char * _Nonnull name, 
    											   BOOL isRequiredProperty, BOOL isInstanceProperty);
    
    /*
    获取协议中声明的必选属性的列表
    
    @note 该方法等效于 protocol_copyPropertyList2(proto, outCount, YES, YES);
    */
    objc_property_t _Nonnull * _Nullable protocol_copyPropertyList(Protocol * _Nonnull proto, unsigned int * _Nullable outCount);
    
    /*
    获取协议中声明的属性的列表
    
    @param proto 要检视的协议
    @param outCount 在函数返回时,用于标识返回的 Property 数组的长度。如果传 NULL,则不返回 Property 数组的长度
    @param isRequiredProperty 如果设置为 YES,则获取协议的必选属性。如果设置为 NO,则获取协议的可选属性
    @param isInstanceProperty 如果设置为 YES,则获取协议的对象属性。如果设置为 NO,则获取协议的类属性
    @return 返回一个 objc_property_t 类型的指针数组(数组中的每一个 objc_property_t 指针元素,都用于描述一个由协议声明的属性)
    		不包含任何由该协议所遵守的协议所声明的属性
    		返回的数组包含 *outCount 个指针,后面跟着一个 NULL 终止符
    		必须手动调用 free() 函数释放返回的数组
    		如果该协议未声明任何匹配的属性,则该函数返回 NULL 并且 *outCount == 0			
    */
    objc_property_t _Nonnull * _Nullable protocol_copyPropertyList2(Protocol * _Nonnull proto,
                               unsigned int * _Nullable outCount,
                               BOOL isRequiredProperty, BOOL isInstanceProperty);
    
    /*
    向协议中添加一个方法描述,该协议必须处于正在构建中
    
    @param proto 要修改的协议
    @param name 方法选择器,用于标识要添加的方法的名称
    @param types 一个用于标识方法类型的 C 字符串
    @param isRequiredMethod 如果设置为 YES,则该方法是必选的。如果设置为 NO,则该方法是可选的
    @param isInstanceMethod 如果设置为 YES,则该方法是对象方法。如果设置为 NO,则该方法是类方法
    */
    void protocol_addMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull name,
                                  const char * _Nullable types,
                                  BOOL isRequiredMethod, BOOL isInstanceMethod);
    
    /*
    获取给定协议中给定方法的方法描述(struct objc_method_description)
    
    @param proto 要检视的协议
    @param aSel 方法选择器,用于标识要获取描述的方法的名称
    @param isRequiredMethod 用于标识由 aSel 指定的方法是必选的还是可选的。如果设置为 YES,则该方法是必选的。如果设置为 NO,则该方法是可选的
    @param isInstanceMethod 用于标识由 aSel 指定的方法是对象方法还是类方法。如果设置为 YES,则该方法是对象方法。如果设置为 NO,则该方法是类方法
    @return 协议 proto 中,由方法名称 aSel,是否必选方法 isRequiredMethod,是否对象方法 isInstanceMethod 指定的方法的方法描述
    		如果没有符合要求的方法,则返回的方法描述结构体为 {NULL, NULL}
    @note 此函数会递归地搜索此协议所遵守的任何协议
    */
    struct objc_method_description protocol_getMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull aSel,
                                  BOOL isRequiredMethod, BOOL isInstanceMethod);
    
    /* 
    获取协议中声明的方法的方法描述列表(一个方法描述包含一个方法的:名称和类型)
    
    @param proto 要检视的协议
    @param isRequiredMethod 用于标识要获取描述的方法是必选的还是可选的。如果设置为 YES,则获取必选方法的描述。如果设置为 NO,则获取可选方法的描述
    @param isInstanceMethod 用于标识要获取描述的方法是对象方法还是类方法。如果设置为 YES,则获取对象方法的描述。如果设置为 NO,则获取类方法的描述
    @param outCount 在函数返回时,用于标识返回的 struct objc_method_description 数组的长度。如果传 NULL,则不返回 struct objc_method_description 数组的长度
    @return 返回一个 struct objc_method_description 类型的指针数组(数组中的每一个 struct objc_method_description 指针元素,都用于描述一个由协议声明的方法)
    		返回的数组包含 *outCount 个指针,后面跟着一个 NULL 终止符
    		必须手动调用 free() 函数释放返回的数组
    		如果该协议未声明任何匹配的方法,则该函数返回 NULL 并且 *outCount == 0
    @note 不包含任何由该协议所遵守的协议所声明的方法的方法描述
    */
    struct objc_method_description * _Nullable protocol_copyMethodDescriptionList(Protocol * _Nonnull proto,
                                       BOOL isRequiredMethod, BOOL isInstanceMethod,
                                       unsigned int * _Nullable outCount);
    
    /*
    向给定的类中添加一个给定的协议
    
    @param cls 要修改的类
    @param protocol 要添加到类中的协议
    @return 如果添加成功,则返回 YES。否则返回 NO(例如:例如该类已经遵守了此协议)
    */
    BOOL class_addProtocol(Class _Nullable cls, Protocol * _Nonnull protocol);
    
    /*
    返回一个布尔值,用于标识给定的类是否遵守给定的协议
    
    @param cls 要检视的类
    @param protocol 要验证的协议
    @return 如果类 cls 遵守 protocol 协议,则返回 YES。否则返回 NO
    @note 通常应该使用 NSObject 的 conformsToProtocol: 方法代替此函数
    */
    BOOL class_conformsToProtocol(Class _Nullable cls, Protocol * _Nullable protocol);
    
    /*
    获取类的协议列表(获取类的 Protocol 列表)
    
    @param cls 要检视的类
    @param outCount 在函数返回时,用于标识返回的 Protocol 数组的长度。如果传 NULL,则不返回 Protocol 数组的长度
    @return 返回一个 Protocol* 类型的指针数组(数组中的每一个 Protocol* 指针元素,都用于描述一个由类遵守的协议)
    		不包含任何由父类遵守的协议,也不包含本类协议所遵守的协议
    		返回的数组包含 *outCount 个指针,后面跟着一个 NULL 终止符
    		必须手动调用 free() 函数释放返回的数组
    		如果该类未遵守任何协议,或者参数 cls 为 Nil,则该函数返回 NULL 并且 *outCount == 0
    */
    Protocol * __unsafe_unretained _Nonnull * _Nullable class_copyProtocolList(Class _Nullable cls, unsigned int * _Nullable outCount);
    

获取类的:成员变量列表 && 属性列表 && 方法列表 && 所遵守的协议列表

  • 获取类的成员变量列表

    #import <objc/runtime.h>
    
    // 打印成员变量列表
    -(void)printIvarList {
        unsigned int count = 0;
        Ivar* ivarList = class_copyIvarList([self class], &count);
        for (unsigned int i = 0; i < count; i++) {
            Ivar anIvar = ivarList[i];
            // const char * -> NString *
            NSString* ivarName = @(ivar_getName(anIvar));
            NSLog(@"ivar(%d) = %@", i, ivarName);
        }
        free(ivarList);
    }
    
  • 获取类的属性列表

    #import <objc/runtime.h>
    
    // 打印属性列表
    -(void)printPropertyList {
        unsigned int count = 0;
        objc_property_t* propertyList = class_copyPropertyList([self class], &count);
        for (unsigned int i = 0; i < count; i++) {
            objc_property_t aProperty = propertyList[i];
            // const char * -> NString *
            NSString* propertyName = @(property_getName(aProperty));
            NSLog(@"property(%d) = %@", i, propertyName);
        }
        free(propertyList);
    }
    
  • 获取类的方法列表

    #import <objc/runtime.h>
    
    // 打印方法列表
    -(void)printMethodList {
        unsigned int count = 0;
        Method* methodList = class_copyMethodList([self class], &count);
        for (unsigned int i = 0; i < count; i++) {
            Method aMethod = methodList[i];
            // SEL -> NString *
            NSString* methodName = NSStringFromSelector(method_getName(aMethod));
            NSLog(@"method(%d) = %@", i, methodName);
        }
        free(methodList);
    }
    
  • 获取类所遵循的协议列表

    #import <objc/runtime.h>
    
    // 打印协议列表
    -(void)printProtocolList {
        unsigned int count = 0;
        __unsafe_unretained Protocol** protocolList = class_copyProtocolList([self class], &count);
        for (unsigned int i = 0; i < count; i++) {
            Protocol* aProtocol = protocolList[i];
            // const char * -> NString *
            NSString* protocolName = @(protocol_getName(aProtocol));
            NSLog(@"protocol(%d) = %@", i, protocolName);
        }
        free(protocolList);
    }
    

应用场景:修改私有属性

  • 需求

    更改 UITextField 占位文字(placeholder)的颜色和字号

  • 一般的实现方式

    ① 通过 UITextFieldattributedPlaceholder 属性

    -(void)setupPlaceholder {
        NSMutableDictionary* attrsDict = [NSMutableDictionary dictionary];
        attrsDict[NSForegroundColorAttributeName] = [UIColor orangeColor];
        attrsDict[NSFontAttributeName] = [UIFont systemFontOfSize:15];
        NSAttributedString* attrString = [[NSAttributedString alloc] initWithString:@"用户名/邮箱" attributes:attrsDict];
        self.loginTextField.attributedPlaceholder = attrString;
    }
    

    ② 通过重写 UITextField-drawPlaceholderInRect: 方法,步骤如下:

    1. 自定义一个 HcgTextField 继承自 UITextField
    2. 重写 HcgTextField-drawPlaceholderInRect: 方法
    3. -drawPlaceholderInRect: 方法中设置 placeholder 的属性
    // HcgTextField .h
    #import <UIKit/UIKit.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface HcgTextField : UITextField
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    // HcgTextField.m
    #import "HcgTextField.h"
    
    @implementation HcgTextField
    
    -(void)drawPlaceholderInRect:(CGRect)rect {
        NSMutableDictionary* attrsDict = [NSMutableDictionary dictionary];
        attrsDict[NSForegroundColorAttributeName] = [UIColor orangeColor];
        attrsDict[NSFontAttributeName] = [UIFont systemFontOfSize:15];
        CGSize placeholderSize = [self.placeholder sizeWithAttributes:attrsDict];
        CGFloat placeholderX = 0;
        CGFloat placeholderY = (rect.size.height - placeholderSize.height) / 2;
        CGFloat placeholderW = rect.size.width;
        CGFloat placeholderH = rect.size.height;
        CGRect placeholderFrame = CGRectMake(placeholderX, placeholderY, placeholderW, placeholderH);
        [self.placeholder drawInRect:placeholderFrame withAttributes:attrsDict];
    }
    
    @end
    
  • 利用 RunTime 的 API:找到并修改 UITextField 的私有属性

    步骤如下:

    1. 通过 RunTime 获取类的属性列表和成员变量列表的函数,打印 UITextField 的所有属性和成员变量
    2. 找到 UITextField 私有的成员变量 _placeholderLabel
    3. 利用 KVC 对 _placeholderLabel 进行修改
    // 打印 UITextField 的成员变量列表和属性列表
    -(void)printUITextFieldIvarListAndPropertyList {
        // 打印 UITextField 的成员变量列表
        unsigned int ivarCount = 0;
        Ivar* ivarList = class_copyIvarList([UITextField class], &ivarCount);
        for (unsigned int i = 0; i < ivarCount; i++) {
            Ivar anIvar = ivarList[i];
            NSString* ivarName = @(ivar_getName(anIvar));
            NSLog(@"ivar(%d) = %@", i, ivarName);
        }
        free(ivarList);
        // 打印 UITextField 的属性列表
        unsigned int propertyCount = 0;
        objc_property_t* propertyList = class_copyPropertyList([UITextField class], &propertyCount);
        for (unsigned int i = 0; i < propertyCount; i++) {
            objc_property_t aProperty = propertyList[i];
            NSString* propertyName = @(property_getName(aProperty));
            NSLog(@"property(%d) = %@", i, propertyName);
        }
        free(propertyList);
    }
    
    // 通过 KVC 设置 UITextField 的私有成员变量
    -(void)setupPrivateIvarForUITextField {
    /*
        // 在 iOS13 及以上的版本,通过此种方式访问 UITextField 的 _placeholderLabel 会导致程序奔溃
        [self.loginTextField setValue:[UIColor orangeColor] forKeyPath:@"_placeholderLabel.textColor"];
        [self.loginTextField setValue:[UIFont systemFontOfSize:15] forKeyPath:@"_placeholderLabel.font"];
        */
        // 解决方式:去掉 _placeholderLabel 的下划线,原因如下所示:
        [self.loginTextField setValue:[UIColor orangeColor] forKeyPath:@"placeholderLabel.textColor"];
        [self.loginTextField setValue:[UIFont systemFontOfSize:15] forKeyPath:@"placeholderLabel.font"];
    }
    
    // iOS13 及以上的版本,系统的 UITextField 重写了 KVC 的 valueForKey: 拦截了外部对 "_placeholderLabel" 的取值,实现如下:
    -(id)valueForKey:(NSString *)key {
        if ([key isEqualToString:@"_placeholderLabel"]) {
            [NSException raise:NSGenericException format:@"Access to UITextField's _placeholderLabel ivar is prohibited. This is an application bug"];
        }
        return [super valueForKey:key];
    }
    

应用场景:万能控制器跳转

  • 需求

    ① 点击某个页面的不同 banner 图,可以跳转到不同的页面
    ② 点击推送通知,跳转到指定的页面
    ③ 扫描二维码,根据二维码不同的内容,跳转到不同的页面
    ④ 点击 WebView 页面的不同 URL,跳转到不同的原生页面

  • 一般的实现方式

    ① 在每个需要跳转的地方写一堆的判断语句以及跳转语句
    ② 将判断语句和跳转语句抽取出来,写成工具类

  • 利用 RunTime 的 API:定制一个万能跳转控制器工具

    步骤如下:

    1. 事先和服务器端商量好,定义跳转到不同控制器的规则,让服务器端传回对应规则的相关参数。比如:跳转到 A 控制器,需要服务器端传回 A 控制器的类名,A 控制器需要传入的属性参数
    2. 根据服务器端传回的类名,创建对应的控制器对象
    3. 遍历服务器端传回的参数,利用 RunTime 的 API 遍历控制器对象的属性列表
    4. 如果控制器对象存在该属性,则利用 KVC 进行赋值
    5. 跳转到对应的控制器

    首先,定义跳转规则,如下所示:

    // 万能控制器跳转
    -(void)JumpViewControllerDemo {
        // 键(targetVCName)中保存着:将要跳转的控制器的类名
        // 键(params)中保存着:控制器所需要的属性参数
        NSDictionary* jumpInfoDict = @{
            @"targetVCName":@"XXViewController",
            @"params":@{
                    @"age":@"20",
                    @"height":@"170.0",
                    @"sex":@"m",
                    @"name":@"hcg"
            }
        };
        // 调用 万能跳转控制器工具 进行跳转
        [HcgJumpViewControllerTool jumpViewControllerWihtJumpInfoDict:jumpInfoDict];
    }
    

    然后,添加一个工具类 HcgJumpViewControllerTool ,在其中添加跳转相关的方法

    // HcgJumpViewControllerTool.h
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface HcgJumpViewControllerTool : NSObject
    
    // 根据跳转信息字典,跳转到指定的控制器
    +(void)jumpViewControllerWihtJumpInfoDict:(NSDictionary *)jumpInfoDict;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    // HcgJumpViewControllerTool.m
    #import "HcgJumpViewControllerTool.h"
    #import <UIKit/UIKit.h>
    #import <objc/runtime.h>
    
    @implementation HcgJumpViewControllerTool
    
    // 根据跳转信息字典,跳转到指定的控制器
    +(void)jumpViewControllerWihtJumpInfoDict:(NSDictionary *)jumpInfoDict {
        // 获取控制器类名
        const char * targetVCName = [jumpInfoDict[@"targetVCName"] cStringUsingEncoding:NSASCIIStringEncoding];
        // 获取控制器所属的类
        Class targetVCClass = objc_getClass(targetVCName);
        if (!targetVCClass) {
            Class superClass = [NSObject class];
            targetVCClass = objc_allocateClassPair(superClass, targetVCName, 0);
            objc_registerClassPair(targetVCClass);
        }
        // 创建控制器对象
        id targetVC = [[targetVCClass alloc] init];
        // 遍历参数字典 Params,使用 KVC 对控制器的属性进行赋值
        NSDictionary* paramsDict = jumpInfoDict[@"params"];
        [paramsDict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
            if ([self checkIsExistPropertyWithInstance:targetVC name:key]) {
                [targetVC setValue:obj forKey:key];
            }
        }];
        // 跳转到对应的控制器
        // [[self getFrontestViewController].navigationController pushViewController:targetVC animated:YES];
        [[self getFrontestViewController] presentViewController:targetVC animated:YES completion:nil];
    }
    
    // 检测给定的对象是否存在给定名称的属性
    +(BOOL)checkIsExistPropertyWithInstance:(id)instance name:(NSString *)name {
        unsigned int count = 0;
        objc_property_t* propertyList = class_copyPropertyList([instance class], &count);
        for (unsigned int i = 0; i < count; i++) {
            objc_property_t aProperty = propertyList[i];
            // const char * -> NSString *
            NSString* propertyName = @(property_getName(aProperty));
            if ([propertyName isEqualToString:name]) {
            	free(propertyList);
                return YES;
            }
        }
        free(propertyList);
        return NO;
    }
    
    // 获取当前显示在屏幕最前面的 ViewController
    +(UIViewController *)getFrontestViewController {
        UIViewController* rootVC = [UIApplication sharedApplication].keyWindow.rootViewController;
        UIViewController* frontestVC = [self getViewControllerNoNivigationNoTabBar:rootVC];
        while (frontestVC.presentedViewController) {
            frontestVC = [self getViewControllerNoNivigationNoTabBar:frontestVC.presentedViewController];
        }
        return frontestVC;
    }
    
    // 过滤容器类型的控制器:NavigationController 和 TabBarController
    +(UIViewController *)getViewControllerNoNivigationNoTabBar:(UIViewController *)vc {
        if ([vc isKindOfClass:[UINavigationController class]]) {
            return [self getViewControllerNoNivigationNoTabBar:[(UINavigationController *)vc topViewController]];
        } else if ([vc isKindOfClass:[UITabBarController class]]) {
            return [self getViewControllerNoNivigationNoTabBar:[(UITabBarController *)vc selectedViewController]];
        } else {
            return vc;
        }
    }
    
    @end
    

应用场景:实现字典转模型

  • 需求

    将服务器端返回的 JSON 字典转换为数据模型

  • 实现方式分析

    我们在日常开发中,经常需要将从网络请求中获取的 JSON 数据转换为数据模型,我们通常会选用诸如 YYModel、JSONModel、MJExtension 等第三方框架来实现这一过程。这些框架实现原理的核心就是 RunTime 和 KVC,以及 Getter / Setter

    实现的大体思路如下:
    借助 RunTime 可以动态获取属性列表的特性,遍历数据模型中的所有属性,然后以获取到的属性名为 key,在 JSON 字典中寻找对应的值 value。再使用 KVC 或直接调用 Getter / Setter 将每一个对应的 value 赋值给数据模型的属性。这样就完成了字典转模型的目的

  • ① 先准备一份待解析的 JSON 数据

    {
        "id": "994923259",
        "name": "行走少年郎",
        "age": "18",
        "height": 180.0,
        "address": {
            "country": "中国",
            "province": "北京"
        },
        "courses": [
            {
                "name": "Chinese",
                "desc": "语文课"
            },
            {
                "name": "Math",
                "desc": "数学课"
            },
            {
                "name": "English",
                "desc": "英语课"
            }
        ]
    }
    

    假设这就是服务器端返回的 JSON 数据,内容是一个学生的信息。现在我们需要将该 JSON 字典转换为方便开发的数据模型

    从这份 JSON 数据中可以看出:字典中的取值除了基本数据类型之外,还有数组和字典。那么在将字典转换成数据模型的时候,就要考虑 模型嵌套模型模型嵌套模型数组 的情况了

    ② 创建数据模型

    经过分析,总共需要三个数据模型:StudentModelAddressModelCourseModel

    // StudentModel.h
    #import <Foundation/Foundation.h>
    #import "NSObject+HcgModel.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @class AddressModel, CourseModel;
    @interface StudentModel : NSObject <HcgModelProtocol>
    
    @property (nonatomic, strong) NSString* uid;                        // 学号
    @property (nonatomic, strong) NSString* name;                       // 姓名
    @property (nonatomic, assign) int age;                              // 年龄
    @property (nonatomic, assign) float height;                         // 身高
    @property (nonatomic, strong) AddressModel* address;             	// 地址(嵌套模型)
    @property (nonatomic, strong) NSArray<CourseModel *>* courses;   	// 课程(嵌套模型数组)
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    
    // StudentModel.m
    #import "StudentModel.h"
    #import "CourseModel.h"
    
    @implementation StudentModel
    
    +(NSDictionary<NSString*, NSString*> *)modelContainGenericProperty {
        return @{ @"uid":@"id" };
    }
    
    +(NSDictionary<NSString*, Class> *)modelContainGenericClass {
        return @{ @"courses":[CourseModel class] };
    }
    
    @end
    
    // AddressModel.h
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface AddressModel : NSObject
    
    @property (nonatomic, strong) NSString* country;    // 国籍
    @property (nonatomic, strong) NSString* province;   // 省份
    @property (nonatomic, strong) NSString* city;       // 城市
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    
    // AddressModel.m
    #import "AddressModel.h"
    
    @implementation AddressModel
    
    @end
    
    // CourseModel.h
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface CourseModel : NSObject
    
    @property (nonatomic, strong) NSString* name;   // 课程名称
    @property (nonatomic, strong) NSString* desc;   // 课程介绍
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    
    // CourseModel.m
    #import "CourseModel.h"
    
    @implementation CourseModel
    
    @end
    
  • ③ 利用 RunTime 的 API:实现字典转模型

    这里需要注意以下 2 个细节:

    1. 上面的 StudentModel.h 中导入了 NSObject+HcgModel.h,并遵守了 HcgModelProtocol 协议
    2. 上面的 StudentModel.m 中实现了 HcgModelProtocol 协议

    NSObject+HcgModel.hNSObject+HcgModel.m 就是我们用来解决字典转模型问题所创建的分类
    HcgModelProtocol 协议中的 +modelContainGenericProperty 方法用于处理:模型属性名称与 JSON 字典的 key 不一致的情况
    HcgModelProtocol 协议中的 +modelContainGenericClass 方法用于处理:模型嵌套模型数组的情况

    #import <Foundation/Foundation.h>
    
    
    
    @protocol HcgModelProtocol <NSObject>
    
    @optional
    // 特殊字段处理规则:模型属性名称与 JSON 字典的 key 不一致
    +(nullable NSDictionary<NSString*, NSString*> *)modelContainGenericProperty;
    // 特殊字段处理规则:模型嵌套模型数组
    +(nullable NSDictionary<NSString*, Class> *)modelContainGenericClass;
    
    @end
    
    
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface NSObject (HcgModel)
    
    // 字典转模型
    +(instancetype)hcg_modelWithDictionary:(NSDictionary *)dictionary;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "NSObject+HcgModel.h"
    #import <objc/runtime.h>
    
    @implementation NSObject (HcgModel)
    
    +(instancetype)hcg_modelWithDictionary:(NSDictionary *)dictionary {
        // 1.创建当前模型对象
        id model = [[self alloc] init];
        // 2.获取当前模型对象的属性列表
        unsigned int propertyCount = 0;
        objc_property_t* propertyList = class_copyPropertyList([self class], &propertyCount);
        // 3.遍历属性列表中的所有属性,以其属性名称为 key,在 JSON 字典 dictionary 中查找对应的 value
        for (unsigned int i = 0; i < propertyCount; i++) {
            // 3.1 获取属性 + 属性名称
            objc_property_t aProperty = propertyList[i];
            NSString* propertyName = @(property_getName(aProperty));
            // 3.2 获取属性类型(属性所属的类的名称)
            NSString* propertyType = nil;
            unsigned int attrCount = 0;
            objc_property_attribute_t* attrList = property_copyAttributeList(aProperty, &attrCount);
            for (unsigned int j = 0; j < attrCount; j++) {
                switch (attrList[j].name[0]) {
                    // Type Encoding
                    case 'T':
                    {
                        if (attrList[j].value) {
                            propertyType = [NSString stringWithUTF8String:attrList[j].value];
                            // 去除转义字符:@\"NSString\" -> @NSString
                            propertyType = [propertyType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
                            // 去除 @ 符号:@NSString -> NSString
                            propertyType = [propertyType stringByReplacingOccurrencesOfString:@"@"  withString:@""];
                        }
                    }
                    break;
                        
                    default: break;
                }
            }
            // 3.3 获取属性值
            id value = dictionary[propertyName];
            // 3.4 特殊字段处理规则:模型属性名称与 JSON 字典的 key 不一致
            if ([self respondsToSelector:@selector(modelContainGenericProperty)]) {
                NSDictionary* genericPropertyDict = [self performSelector:@selector(modelContainGenericProperty)];
                NSString* anotherName = genericPropertyDict[propertyName];
                if (anotherName) {
                    value = dictionary[anotherName];
                }
            }
            // 3.5 处理模型嵌套模型的情况
            if ([value isKindOfClass:[NSDictionary class]] && ![propertyType hasPrefix:@"NS"]) {
                Class subModelClass = NSClassFromString(propertyType);
                // 将被嵌套的字典也转换为模型
                value = [subModelClass hcg_modelWithDictionary:value];
            }
            // 3.6 特殊字段处理规则:模型嵌套模型数组
            if ([value isKindOfClass:[NSArray class]] &&
                [self respondsToSelector:@selector(modelContainGenericClass)]) {
                NSDictionary* genericClassDict = [self performSelector:@selector(modelContainGenericClass)];
                Class elementModelClass = genericClassDict[propertyName];
                NSMutableArray* elementArray = [NSMutableArray array];
                for (NSDictionary* elementDict in value) {
                    id elementModel = [elementModelClass hcg_modelWithDictionary:elementDict];
                    [elementArray addObject:elementModel];
                }
                value = elementArray;
            }
            // 3.7 使用 KVC 将 value 设置到 model 上
            if (value) {
                [model setValue:value forKey:propertyName];
            }
        }
        // 4.释放属性列表
        free(propertyList);
        // 5.返回模型
        return model;
    }
    
    @end
    
  • ④ 测试代码

    // 将 JSON 字典转换为数据模型
    -(void)convertJsonDictToDataModel {
        // 获取 JSON 字典
        NSString* jsonFilePath = [[NSBundle mainBundle] pathForResource:@"Student" ofType:@"json"];
        NSData* jsonData = [NSData dataWithContentsOfFile:jsonFilePath];
        NSDictionary* jsonDict = [NSJSONSerialization JSONObjectWithData:jsonData
                                                                 options:(NSJSONReadingOptions)NSJSONReadingMutableContainers
                                                                   error:nil];
        NSLog(@"jsonDict = %@", jsonDict);
        // JSON 字典转数据模型
        StudentModel* student = [StudentModel hcg_modelWithDictionary:jsonDict];
        // 打印输出
        NSLog(@"student.uid = %@", student.uid);
        NSLog(@"student.name = %@", student.name);
        for (unsigned int i = 0; i < student.courses.count; i++) {
            CourseModel* courseModel = student.courses[i];
            NSLog(@"courseModel[%d] name = %@, desc = %@", i, courseModel.name, courseModel.desc);
        }
    }
    

    测试效果如下:
    字典转模型测试效果

    当然,如果需要考虑缓存机制、性能问题、对象类型检查等,建议还是使用例如 YYModel 之类的知名第三方框架

应用场景:改进归档和解档

  • 实现方式分析

    『归档』和『解档』是一种常用的轻量型文件存取方式。在项目中,如果需要将数据模型本地化存储,一般就会用到『归档』和『解档』。但是如果数据模型中有很多个属性的话,我们不得不对每个属性进行处理,这个过程既繁琐又单调

    改进归档和解档的大体思路如下:
    借助 RunTime 可以动态获取属性列表的特性,遍历数据模型中的所有属性,然后以获取到的属性名为 key,通过 KVC 获取到属性对应的值,再对属性的值进行归档
    借助 RunTime 可以动态获取属性列表的特性,遍历数据模型中的所有属性,然后以获取到的属性名为 key,对属性的值进行解档,再通过 KVC 设置属性对应的值

  • 利用 RunTime 的 API:改进归档和解档

    『归档』和『解档』的测试代码如下:

    -(void)encodeAndDecodeDemo {
        // 存储路径
        NSString* path = [NSString stringWithFormat:@"%@/person.plist", NSHomeDirectory()];
        // 对 Person 对象进行归档
        Person* person0 = [[Person alloc] init];
        person0.uid = @"994923259";
        person0.name = @"hcg";
        person0.sex = 'M';
        person0.age = 18;
        person0.height = 180.0f;
        [NSKeyedArchiver archiveRootObject:person0 toFile:path];
        // 对 Person 对象进行解档
        Person* person1 = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
        NSLog(@"person1.uid = %@", person1.uid);
        NSLog(@"person1.name = %@", person1.name);
    }
    

    需要本地化存储的数据模型 Person 类如下:

    // Person.h
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface Person : NSObject <NSCoding>
    
    @property (nonatomic, strong) NSString* uid;
    @property (nonatomic, strong) NSString* name;
    @property (nonatomic, assign) int age;
    @property (nonatomic, assign) char sex;
    @property (nonatomic, assign) float height;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    
    // Person.m
    #import "Person.h"
    #import "NSObject+HcgModel.h"
    
    @implementation Person
    
    #pragma mark - NSCoding
    
    -(instancetype)initWithCoder:(NSCoder *)coder {
        if (self = [super init]) {
            [self hcg_modelInitWithCoder:coder];
        }
        return self;
    }
    
    -(void)encodeWithCoder:(NSCoder *)coder {
        [self hcg_modelEncodeWithCoder:coder];
    }
    
    @end
    

    实现自动归档和解档的代码如下:

    // NSObject+HcgModel.h
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface NSObject (HcgModel)
    
    // 归档
    -(void)hcg_modelEncodeWithCoder:(NSCoder *)aCoder;
    // 解档
    -(instancetype)hcg_modelInitWithCoder:(NSCoder *)aDecoder;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    
    // NSObject+HcgModel.m
    #import "NSObject+HcgModel.h"
    #import <objc/runtime.h>
    
    @implementation NSObject (HcgModel)
    
    // 归档
    -(void)hcg_modelEncodeWithCoder:(NSCoder *)aCoder {
        // 判空
        if (!aCoder || !self) {
            return;
        }
        // 遍历模型的属性列表,使用 KVC 获取模型的属性对应的值,然后进行归档
        unsigned int propertyCount = 0;
        objc_property_t* propertyList = class_copyPropertyList([self class], &propertyCount);
        for (unsigned int i = 0; i < propertyCount; i++) {
            objc_property_t aProperty = propertyList[i];
            NSString* propertyName = @(property_getName(aProperty));
            id propertyValue = [self valueForKey:propertyName];
            [aCoder encodeObject:propertyValue forKey:propertyName];
        }
        free(propertyList);
    }
    
    // 解档
    -(instancetype)hcg_modelInitWithCoder:(NSCoder *)aDecoder {
        // 判空
        if (!aDecoder || !self) {
            return self;
        }
        // 遍历模型的属性列表进行解档,并使用 KVC 对模型的属性进行赋值
        unsigned int propertyCount = 0;
        objc_property_t* propertyList = class_copyPropertyList([self class], &propertyCount);
        for (unsigned int i = 0; i < propertyCount; i++) {
            objc_property_t aProperty = propertyList[i];
            NSString* propertyName = @(property_getName(aProperty));
            id propertyValue = [aDecoder decodeObjectForKey:propertyName];
            [self setValue:propertyValue forKey:propertyName];
        }
        free(propertyList);
        // 返回模型
        return self;
    }
    
    @end
    

标签:RunTime,Nonnull,Nullable,方法,param,Objective,property,详细信息,属性
来源: https://blog.csdn.net/Airths/article/details/116106166

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有