很多人都喜欢研究底层的东西, 因个人比较low, 只能讲讲runtime在实际工作中的应用.
- 应用1 动态获取类的属性
// 获取成员变量列表, 第三方框架使用此方法居多
// 参数1: 要copy的类
// 参数2: 属性计数指针
class_copyIvarList(__unsafe_unretained Class cls, unsigned int *outCount);
// 获取方法列表
class_copyMethodList(__unsafe_unretained Class cls, unsigned int *outCount);
// 获取属性列表
class_copyPropertyList(__unsafe_unretained Class cls, unsigned int *outCount);
// 获取协议列表
class_copyProtocolList(__unsafe_unretained Class cls, unsigned int *outCount)
举例: 这种方式可以动态获取一个类中的属性; 可以避免字典和模型中属性不匹配时造成崩溃
+ (NSArray *)loadProperties{
unsigned int count = 0;
// 返回所有属性的数组
// C语言中, 指向数组第一个元素的是一个指针
objc_property_t *list = class_copyPropertyList([self class], &count);
NSLog(@"属性数量 %u", count);
// 遍历数组
NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; ++i) {
// C语言中没有对象的概念,一般不需要使用 `*`
objc_property_t pty = list[i];
// 属性名称
const char *cname = property_getName(pty);
// 添加到数组
[arrayM addObject:[NSString stringWithUTF8String:cname]];
}
NSLog(@"%@", arrayM);
// 释放对象
// 用C语言方法创建对象, 用到copy/new/retain/create等时要释放对象, 具体使用什么释放, 看文档
free(list);
return arrayM.copy;
}
- 应用2 关联对象
使用: 在第三方框架使用特别多, 目的是让分类解耦, 动态给某些类增加属性做缓存
上面的例子可以解决动态添加属性的问题, 但是每一次调用loadProperty方法时都会进来一次; 而程序运行起来, 类的属性不会再变, 为了提升性能, 我们可以考虑只提取一次; 因此我们需要使用到关联对象
const char *kPropertiesKey = "kPropertiesKey";
+ (NSArray *)loadProperties{
// 利用`关联`对象,给`类`添加属性,OC中的类,本身就是一个特殊对象
/**
获取关联对象
1. 对象,属性关联到的对象
2. key,属性的 key
*/
NSArray *pList = objc_getAssociatedObject(self, kPropertiesKey);
if (pList != nil) {
return pList;
}
unsigned int count = 0;
// 返回所有属性的数组
// C语言中, 指向数组第一个元素的是一个指针
objc_property_t *list = class_copyPropertyList([self class], &count);
NSLog(@"属性数量 %u", count);
// 遍历数组
NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; ++i) {
// C语言中没有对象的概念,一般不需要使用 `*`
objc_property_t pty = list[i];
// 属性名称
const char *cname = property_getName(pty);
// 添加到数组
[arrayM addObject:[NSString stringWithUTF8String:cname]];
}
NSLog(@"%@", arrayM);
// 释放对象
// 用C语言方法创建对象, 用到copy/new/retain/create等时要释放对象, 具体使用什么释放, 看文档
free(list);
// 设置关联对象对象
/**
设置关联对象属性,运行时机制中,在OC开发的应用,关联对象使用的频率最高!
1. 属性关联的对象
2. key
3. 值
4. 引用关系
*/
objc_setAssociatedObject(self, kPropertiesKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
return objc_getAssociatedObject(self, kPropertiesKey);
}
- 应用3 交换方法–拦截系统或其他框架的方法
原理: 方法在内存中调用是通过函数指针, 系统有一个函数映射表, 里面记录所有函数的地址, 当调用某个函数时, 函数指针指向这个函数的地址;
应用: AFN中有一个方法 resume表示开始网络任务, 我想要在网络任务开启后发一个通知, 这样就可以监听到网络任务开启了;
但是resume是苹果封装好的, 我们够不到苹果底层东西. 怎么办?
我们定义一个方法 如 AF_Resume 开启网络任务同时发送通知; 但是我们开发一个框架时, 不可能告诉每个人都使用AF_Resume;
这时我们就需要用到交换方法; 函数映射表中记录着resume和AF_Resume两个函数的地址, 直接交换两个函数的地址; 交换后再调用resume时, 其实已经去执行 AF_Resume了.
- 给分类添加成员变量
创建一个分类UIImageView+WebImage, 设置一个URLString属性, 在implementation中重写set和get方法.
// MARK: - 运行时关联对象
const void *HMCurrentURLStringKey = "HMCurrentURLStringKey";
- (void)setCurrentURLString:(NSString *)currentURLString {
objc_setAssociatedObject(self, HMCurrentURLStringKey, currentURLString, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)currentURLString {
return objc_getAssociatedObject(self, HMCurrentURLStringKey);
}