Runtime
runtime是一套比较底层的纯C语言API,属于1个C语言库,包含了很多底层的C语言API。平时编写的OC代码,在程序运行过程中,其实最终都是转成了runtime的C语言代码,runtime算是OC的幕后工作者。它可以帮助我们实现以下功能:
1. 发送消息
- OC方法调用的本质,就是让对象发送消息。
- objc_msgSend,只有对象才能发送消息,因此以objc开头。
- 使用消息机制前提,必须导入
#import <objc/message.h>
- build settings->enable strict checking of objc_msgSend Calls->no
- runtime函数都有前缀,谁的事情谁开头。
// 创建person对象
Person *p = [[Person alloc] init];
// 调用对象方法
[p eat];
// 本质:让对象发送消息
objc_msgSend(p, @selector(eat));
// 调用类方法的方式:两种
// 第一种通过类名调用
[Person eat];
// 第二种通过类对象调用
[[Person class] eat];
// 用类名调用类方法,底层会自动把类名转换成类对象调用
// 本质:让类对象发送消息
objc_msgSend([Person class], @selector(eat));
根据对象的isa找到类中方法编号SEL, 去映射表查找对应的方法名, 根据方法名找到方法区中的方法实现
交换方法
开发使用场景:系统自带的方法功能不够,给系统自带的方法扩展一些功能,并且保持原有的功能。
- 方式一:继承系统的类,重写方法.
- 方式二:使用runtime,交换方法.
需求:给imageNamed方法提供功能,每次加载图片就判断下图片是否加载成功。
- 步骤一:先搞个分类,定义一个能加载图片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
- 步骤二:交换imageNamed和imageWithName的实现,就能调用imageWithName,间接调用imageWithName的实现。
// 加载类的时候调用,肯定只会调用一次
+ (void)load
{
// 交互方法实现ozh_imageNamed,imageNamed
// 获取方法 Method:方法名
// 获取类方法
// class:获取哪个类方法
// SEL:方法编号
Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
Method ozh_imageNameMethod = class_getClassMethod(self, @selector(ozh_imageNamed:));
method_exchangeImplementations(imageNameMethod, ozh_imageNameMethod);
}
------------------------------华丽的分割线---------------------------------
//新增的方法
+ (UIImage *)ozh_imageNamed:(NSString *)name
{
UIImage *image = [UIImage ozh_imageNamed:name];
if (image == nil) {
NSLog(@"加载失败");
}
return image;
}
2. 动态添加方法
为什么动态添加方法?
- 有些方法可能很久不会调用, 我们可以用到时才去加载, 这符合苹果的懒加载机制
- 例如会员机制,只有会员才拥有这些功能, 我们可以在用户成为会员时才去加载这些方法
例子:
viewController调用了Person类中只有声明没有实现的方法,会报错。
- [p performSelector:@selector(eat)];
解决方案:动态添加方法
- 给Person动态添加eat方法
- 使用
resolveInstanceMethod:
的动态添加方法class_addMethod
@implementation Person
// 定义函数
// 没有返回值,没有参数
// 默认OC方法都有两个隐式参数,self,_cmd
void eat(id self, SEL _cmd) {
NSLog(@"吃东西");
}
// `resolveInstanceMethod:`什么时候调用:只要调用了有声明没有实现的方法 就会调用该方法去解决
// 作用:我们可以在该方法内`class_addMethod`, 这样便实现了动态添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
/*
class:给谁添加方法
SEL:添加哪个方法
IMP:方法实现,函数入口,函数名(上面定义的那个东东)
type:方法类型
*/
if (sel == @selector(eat)) {
class_addMethod(self, sel, (IMP)eat, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
3. 动态添加属性
- 需求:给NSObject添加一个name属性,动态添加属性 -> runtime
- 属性的本质:让属性与某个对象产生一段关联
- 使用场景:给系统的类添加属性
步骤
- 分类中重写setter, getter方法
@implementation NSObject (Property)
//static NSString *_name;
// 只要想调用runtime方法,思考:谁的事情
- (void)setName:(NSString *)name
{
// 保存name
// 动态添加属性 = 本质:让对象的某个属性与值产生关联
/*
object:保存到哪个对象中
key:用什么属性保存 属性名
value:保存值
policy:策略,strong,weak
*/
objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// _name = name;
}
- (NSString *)name
{
return objc_getAssociatedObject(self, "name");
// return _name;
}
@end
4. 字典转模型
传统字典转模型如果使用KVC方法, 必须要保证 模型中属性名要跟字典中key一一对应
- 缺点: 不是后台提供的每个数据都有用, 不一定会把每个数据都保存在模型中
- 解决方案1: 重写
setValue:forUndefinedKey
, 不使用系统的实现(因为一旦它实现就会报错)
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
//不实现该方法, 所以不会报错
}
- 解决方案2: 利用runtime运行时判断是否有关联对象,和设置关联对象
提供一个NSDictionary的分类以便自动生成属性代码
- (void)createPropetyCode
{
// 模型中属性根据字典的key
// 有多少个key,生成多少个属性
NSMutableString *codes = [NSMutableString string];
// 遍历字典
[self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull value, BOOL * _Nonnull stop) {
NSString *code = nil;
if ([value isKindOfClass:[NSString class]]) {
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;",key];
} else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",key];
} else if ([value isKindOfClass:[NSNumber class]]) {
code = [NSString stringWithFormat:@"@property (nonatomic, assign) NSInteger %@;",key];
} else if ([value isKindOfClass:[NSArray class]]) {
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",key];
} else if ([value isKindOfClass:[NSDictionary class]]) {
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",key];
}
// 拼接字符串
[codes appendFormat:@"\n%@\n",code];
}];
NSLog(@"%@",codes);
}
提供一个NSObject的分类以便字典转模型
- 使用runtime字典转模型: 遍历模型中属性,去字典中取出对应value,再给模型中属性赋值
ivar
指的是成员变量[实例变量(instance variable)], 就是带下划线的属性, 遍历ivar比较严谨(成员变量包括属性, 属性不包括成员变量)
@property (nonatomic, assign) NSInteger reposts_count;
//reposts_count->属性(property)
//_reposts_count->成员变量(ivar)
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
id objc = [[self alloc] init];
// 1.获取模型中所有属性 -> 保存到类
// ivar:成员变量 和 Property:属性
// 获取成员变量列表
// class:获取哪个类成员变量列表
// count:成员变量总数
int count = 0;
// 成员变量数组 指向数组第0个元素
Ivar *ivarList = class_copyIvarList(self, &count);
// 遍历所有成员变量
for (int i = 0; i < count; i++) {
// 获取成员变量
Ivar ivar = ivarList[i];
// 获取成员变量名称
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 成员变量名称转换key
NSString *key = [ivarName substringFromIndex:1];
// 从字典中取出对应value
id value = dict[key];
// 给模型中属性赋值
[objc setValue:value forKey:key];
}
return objc;
}