Runtime-Category
2021-03-11 本文已影响0人
磊Se
Category的基本使用
如下代码,可以在里面添加方法和遵守协议。当然也可以重写方法(苹果不建议),或添加属性(没成员变量的属性)。
自己实现get、set方法如下使用objc_getAssociatedObject
@interface NSObject (Log)
// Category 中不能添加成员变量(这样写需要自己实现get&set方法)
@property (nonatomic, copy) NSString *name;
- (void)testLog;
@end
@implementation NSObject (Log)
- (void)testLog {
NSLog(@"添加方法");
}
#pragma mark - Getter & Setter
const void *kNameKey; //可用这样关联属性的key
- (NSString *)name { //属性的get方法
//从 AssociationsHashMap 取值,以 @selector(name) 作为 Key
NSString *kname = objc_getAssociatedObject(self, _cmd);
return kname;
}
- (void)setName:(NSString *)name { //属性的set方法
//在 AssociationsHashMap 添加 key-value
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY);
}
@end
Association 关联对象的原理
主要有3个方法
// 用于给对象添加关联对象,传入 nil 则可以移除已有的关联对象;
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
// 用于获取关联对象
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
// 移除所有的关联对象
objc_removeAssociatedObjects(id _Nonnull object)
关联策略
通过objc_AssociationPolicy
参数给对象添加关联策略(即assign、strong、copy、weak、原子属性)
结论:
- 关联对象与被关联对象的存储并没有直接的关系,它是存在AssociationsHashMap单独的哈希表中;
- AssociationsManager来管理AssociationHashMap中的哈希表;
Category 与 Extension的区别
- Extension是Category的特例。
- Extension只有声明,没有实现。
- Extension可以添加属性,并且属性有成员变量。
本质原因:Extension在编译时期就加载到类中,而Category却是在运行时期才加载到Class结构体中。
Category 的本质
Category分为编译时期和运行时期两个阶段。objc-runtime-new.h
文件中Category结构体,如下
// 分类结构体
struct category_t {
// 分类名称
const char *name;
// 类型
classref_t cls;
// 实例方法列表
struct method_list_t *instanceMethods;
// 类方法列表
struct method_list_t *classMethods;
// 遵守的协议列表
struct protocol_list_t *protocols;
// 实例成员属性列表
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
// 类成员属性列表
struct property_list_t *_classProperties;
// 用于返回方法列表
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
// 用于返回属性列表
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
Category的实现原理
编译时期Category并不会把其内容编译进Class中,只有在运行时期Class加载完之后,才会添加到已加载在内存中的Class中。
1、每一个分类编译之后生成一个struct
category_t
结构体。里面存储着分类的对象方法、类方法、属性、协议信息;
2、把所有Category的方法、属性、协议数据,合并到一个大数组中;后面参与编译的Category数据,会在数组的前面;
3、将合并后的分类数据(方法、属性、协议),插入到类原来的数据的前面(对象方法合并到类中,类方法合并到元类中);
分类执行顺序
1、方法名和原有类方法名相同时,分类会覆盖原有类的方法。
2、如果不同的分类中有相同的方法,调用的方法由编译器targets->Build Phases->Complie Source里添加的文件顺序决定,从上到下编译。调用的方法为最后编译的方法。(如顺序为A、B、C 则调用C中的方法)