目录
相关函数介绍
-
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
)的颜色和字号 -
一般的实现方式
① 通过
UITextField
的attributedPlaceholder
属性-(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:
方法,步骤如下:- 自定义一个
HcgTextField
继承自UITextField
- 重写
HcgTextField
的-drawPlaceholderInRect:
方法 - 在
-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 的私有属性
步骤如下:
- 通过 RunTime 获取类的属性列表和成员变量列表的函数,打印
UITextField
的所有属性和成员变量 - 找到
UITextField
私有的成员变量_placeholderLabel
- 利用 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]; }
- 通过 RunTime 获取类的属性列表和成员变量列表的函数,打印
应用场景:万能控制器跳转
-
需求
① 点击某个页面的不同 banner 图,可以跳转到不同的页面
② 点击推送通知,跳转到指定的页面
③ 扫描二维码,根据二维码不同的内容,跳转到不同的页面
④ 点击 WebView 页面的不同 URL,跳转到不同的原生页面 -
一般的实现方式
① 在每个需要跳转的地方写一堆的判断语句以及跳转语句
② 将判断语句和跳转语句抽取出来,写成工具类 -
利用 RunTime 的 API:定制一个万能跳转控制器工具
步骤如下:
- 事先和服务器端商量好,定义跳转到不同控制器的规则,让服务器端传回对应规则的相关参数。比如:跳转到 A 控制器,需要服务器端传回 A 控制器的类名,A 控制器需要传入的属性参数
- 根据服务器端传回的类名,创建对应的控制器对象
- 遍历服务器端传回的参数,利用 RunTime 的 API 遍历控制器对象的属性列表
- 如果控制器对象存在该属性,则利用 KVC 进行赋值
- 跳转到对应的控制器
首先,定义跳转规则,如下所示:
// 万能控制器跳转 -(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 数据中可以看出:字典中的取值除了基本数据类型之外,还有数组和字典。那么在将字典转换成数据模型的时候,就要考虑
模型嵌套模型
、模型嵌套模型数组
的情况了② 创建数据模型
经过分析,总共需要三个数据模型:
StudentModel
、AddressModel
、CourseModel
// 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 个细节:
- 上面的
StudentModel.h
中导入了NSObject+HcgModel.h
,并遵守了HcgModelProtocol
协议 - 上面的
StudentModel.m
中实现了HcgModelProtocol
协议
NSObject+HcgModel.h
和NSObject+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