动态的添加对象的成员变量和方法
class_addMethod([self.person class], @selector(guess), (IMP)guessAnswer, "v@:");
参数说明
>(IMP)guessAnswer 意思是guessAnswer的地址指针;
>"v@:" 意思是,v代表无返回值void,如果是i则代表int;@代表 id sel; : 代表 SEL _cmd;
>“v@:@@” 意思是,两个参数的没有返回值。
动态交换两个方法的实现
交换方法之后,以后每次调用这两个方法都会交换方法的实现
Method m1 = class_getInstanceMethod([self.person class], @selector(sayName));
Method m2 = class_getInstanceMethod([self.person class], @selector(saySex));
method_exchangeImplementations(m1, m2);
拦截并替换方法
我们要改变这个方法的实现,但是又不能去动它的源代码
(正如一些开源库出现问题的时候),
这个时候runtime就派上用场了
我们先增加一个tool类,然后写一个我们自己实现的方法-change,
通过runtime把test1替换成change。
Class PersionClass = object_getClass([Person class]);
Class toolClass = object_getClass([tool class]);
////源方法的SEL和Method
SEL oriSEL = @selector(test1);
Method oriMethod = class_getInstanceMethod(PersionClass, oriSEL);
////交换方法的SEL和Method
SEL cusSEL = @selector(change);
Method cusMethod = class_getInstanceMethod(toolClass, cusSEL);
////先尝试給源方法添加实现,这里是为了避免源方法没有实现的情况
BOOL addSucc = class_addMethod(PersionClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
if (addSucc) {
// 添加成功:将源方法的实现替换到交换方法的实现
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else {
//添加失败:说明源方法已经有实现,直接将两个方法的实现交换即
method_exchangeImplementations(oriMethod, cusMethod);
}
在方法上增加额外功能
有这样一个场景,出于某些需求,我们需要跟踪记录APP
中按钮的点击次数和频率等数据,怎么解决?当然通过继承按钮
类或者通过类别实现是一个办法,但是带来其他问题比如别人不
一定会去实例化你写的子类,或者其他类别也实现了点击方法导
致不确定会调用哪一个,runtime可以这样解决,
实现场景二、我们app经常会遇到一些cash,我们可以通过
runtime拦截替换这些方法,从而保证不会崩溃,例如如下列举
NSArray+NSRangeException
NSDictionary+NSRangeException
NSMutableAttributedString+NSRangeException
NSMutableString+NSRangeException
NSNull+Exception
NSObject+Swizzling
NSString+NSRangeException
UINavigationController+Consistent
load方法会在类第一次加载的时候被调用,调用的时间比较靠前,
适合在这个方法里做方法交换,方法交换应该被保证,
在程序中只会执行一次。
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class selfClass = [self class];
SEL oriSEL = @selector(sendAction:to:forEvent:);
Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);
SEL cusSEL = @selector(mySendAction:to:forEvent:);
Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);
BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
if (addSucc) {
class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else {
method_exchangeImplementations(oriMethod, cusMethod);
}
});
}
- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
[[Tool sharedManager] addCount];
[self mySendAction:action to:target forEvent:event];
}
实现NSCoding的自动归档和解档
- (void)encodeWithCoder:(NSCoder *)encoder
{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Movie class], &count);
for (int i = 0; i<count; i++) {
// 取出i位置对应的成员变量
Ivar ivar = ivars[i];
// 查看成员变量
const char *name = ivar_getName(ivar);
// 归档
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[encoder encodeObject:value forKey:key];
}
free(ivars);
}
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Movie class], &count);
for (int i = 0; i<count; i++) {
// 取出i位置对应的成员变量
Ivar ivar = ivars[i];
// 查看成员变量
const char *name = ivar_getName(ivar);
// 归档
NSString *key = [NSString stringWithUTF8String:name];
id value = [decoder decodeObjectForKey:key];
// 设置到成员变量身上
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
实现字典转模型的自动转换
字典转模型的应用可以说是每个app必然会使用的场景,
虽然实现的方式略有不同,但是原理都是一致的:遍历模型中所有
属性,根据模型的属性名,去字典中查找key,取出对应的值,
给模型的属性赋值。
像几个出名的开源库:JSONModel,MJExtension等都是通过这
种方式实现的。
// 创建对应模型对象
id objc = [[self alloc] init];
unsigned int count = 0;
// 1.获取成员属性数组
Ivar *ivarList = class_copyIvarList(self, &count);
// 2.遍历所有的成员属性名,一个一个去字典中取出对应的
value给模型属性赋值
for (int i = 0; i < count; i++) {
// 2.1 获取成员属性
Ivar ivar = ivarList[i];
// 2.2 获取成员属性名 C -> OC 字符串
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 2.3 _成员属性名 => 字典key
NSString *key = [ivarName substringFromIndex:1];
// 2.4 去字典中取出对应value给模型属性赋值
id value = dict[key];
// 获取成员属性类型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
}
如果模型比较简单,只有NSString,NSNumber等,
这样就可以搞定了。但是如果模型含有NSArray,或者NSDictionary等,那么我们还需要进行第二步转换。
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) {
// 是字典对象,并且属性名对应类型是自定义类型
// 处理类型字符串 @\"User\" -> User
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
// 自定义对象,并且值是字典
// value:user字典 -> User模型
// 获取模型(user)类对象
Class modalClass = NSClassFromString(ivarType);
// 字典转模型
if (modalClass) {
// 字典转模型 user
value = [modalClass objectWithDict:value];
}
}
if ([value isKindOfClass:[NSArray class]]) {
// 判断对应类有没有实现字典数组转模型数组的协议
if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
// 转换成id类型,就能调用任何对象的方法
id idSelf = self;
// 获取数组中字典对应的模型
NSString *type = [idSelf arrayContainModelClass][key];
// 生成模型
Class classModel = NSClassFromString(type);
NSMutableArray *arrM = [NSMutableArray array];
// 遍历字典数组,生成模型数组
for (NSDictionary *dict in value) {
// 字典转模型
id model = [classModel objectWithDict:dict];
[arrM addObject:model];
}
// 把模型数组赋值给value
value = arrM;
}
}