本人录制技术视频地址:https://edu.csdn.net/lecturer/1899 欢迎观看。
上一节,我介绍了runtime的基本概念,如有不清楚的地方,请猛点这里。
这一节,举例说明runtime的常用使用场合。
一. 动态获取一个类的属性列表信息
首先定义一个Person类,代码如下:
@interface Person : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int age;
@property (nonatomic,assign) BOOL gender;
@end
然后就可以使用runtime机制动态的获取属性信息了。代码如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1.获取类信息
id personClass = objc_getClass("Person");
unsigned int outCount,i;
objc_property_t *properties = class_copyPropertyList(personClass, &outCount);
// 2. 获取属性信息
for(i=0;i<outCount;i++){
objc_property_t property = properties[i];
NSLog(@"Name:%s;Attribute:%s",property_getName(property),property_getAttributes(property));
}
}
return 0;
}
需要注意的是,需要导入头文件 #import<objc/runtime.h>, 因为 objc_property_t,property_getName 这些方法都是 #import <objc/runtime.h> 动态库中提供的
二. 用runtime实现归档解档
一般的实现方式:(弊端:1. 必须针对指定的类写类似的代码;2. 如果属性比较多的话,代码量就比较大)
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeBool:self.gender forKey:@"gender"];
[aCoder encodeInteger:self.age forKey:@"age"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if(self = [super init])
{
self.name = [aDecoder decodeObjectForKey:@"name"];
self.gender = [aDecoder decodeBoolForKey:@"gender"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
runtime的实现方式
// 解档
- (id)initWithCoder:(NSCoder *)aDecoder
{
if(self = [super init])
{
unsigned int count;
Ivar *ivars = class_copyIvarList([Person class], &count);
for(int i=0;i<count;i++){
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *strName = [NSString stringWithUTF8String:name];
// 进行解档取值
id value = [aDecoder decodeObjectForKey:strName];
// 利用KVC对属性赋值
[self setValue:value forKey:strName];
}
}
return self;
}
// 归档
-(void)encodeWithCoder:(NSCoder *)aCoder
{
unsigned int count;
Ivar *ivars = class_copyIvarList([Person class], &count);
for(int i=0;i<count;i++){
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *strName = [NSString stringWithUTF8String:name];
// 利用KVC取值
id value = [self valueForKey:strName];
// 进行归档赋值
[aCoder encodeObject:value forKey:strName];
}
}
三. Swizzle
在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。
每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。
我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,
我们可以利用 class_replaceMethod 来修改类,
我们可以利用 method_setImplementation 来直接设置某个方法的IMP,……
归根结底,都是偷换了selector的IMP,如下图所示:
上面的图解释的也许有点抽象了,下面举一个具体来说明。
假如你现在的项目有一个需求,项目中所有的图片全部要跟新为UI部门所设计的新图片(而且图片名称全部以"_os8")结尾,这个时候,也许开发人员就要奔溃了! 全局搜索 "imageNamed:"方法,然后在图片名称后面全部加上"_os8"后缀,可想而知,这纯粹是体力活啊!
这个时候,我们就可以考虑使用Swizzle了,大致实现代码如下:
@implementation UIImage (Extension)
/**
* 分类被装载到内存中,就会调用1次
*/
+(void)load {
Method older = class_getClassMethod(self, @selector(imageNamed:));
Method newer = class_getClassMethod(self, @selector(lFImageNamed:));
// 交换两个方法
method_exchangeImplementations(older, newer);
}
+ (UIImage *)lFImageNamed:(NSString *)name {
BOOL iOS8 = [[UIDevice currentDevice].systemVersion floatValue] >= 8.0;
if(iOS8) {
name = [name stringByAppendingString:@"_os8"];
}
return [self lFImageNamed:name];
}
@end
[self lFImageNamed:name]
这一句的时候,大家也许会认为造成死循环。其实不然,它其实调用的是 imageName: 方法,只不过偷偷的将传进来的参数名称加上了_os8, 之所以可以进行方法的交换操作,是因为
method_exchangeImplementations
这个方法,它完成了 lFImageNamed: 与 imageNamed: 方法的交换工作。
关于runtime的使用就介绍这么多,下一节,我为大家介绍一个基于runtime实现的
字典模型转换插件“MJExtension”。