因为 编译器 最终都会将 OC代码 转化 为运行时代码;
例:调用方法即是 向其发送了消息 等于底层:
objc_msgSend(receiver,@selector(方法)); //向receiver 发送方法消息
验证: 可以通过终端 输入命令 : clang -rewrite-objc 用C重写,可以看到上面代码;
常用场景
常用函数:
引用头文件
//交换Person 的 两个方法;
Method m1 = class_getClassMethod ([Person class],@Selector(run));
Method m2 = class_getClassMethod ([Person class],@Selector(eat));
method_exchangeImplementations(m1,m2);
一般是用来交换系统的方法;当你觉得系统方法不够好,功能不够,或是有特殊需求要整体更换某一效果的;(PS.分类重写系统方法虽然可以覆盖系统的方法,但是这样系统的此方法就会完全的失效,想要保留的功能也会失效.)
//例:1. 增加系统方法的 一个适配iOS7 功能:不同系统显示不同风格图片
+ (void)load //当某个类或者分类加载进内场是,就会调用一次. 在此方法里设置交换;
{把 UIImage 的 imageNamed:方法和我们给它分类添加的 bq_imageNamed: 方法交换};
+ (UIImage *)bq_imageNamed:(NSString *)name {
double version = [[UIDevice currentDevice].systemVersion doubleValue];
if (version >= 7.0){ //系统大于7.0 进行图片名拼接;
name = [name stringByAppendingString:@"_ios7"];
}
return [UIImage bq_imageNamed:name]; // 注意: 方法已经交换了;
}
//2. 交换系统方法后就可以拦截系统调用的消息,进行系统方法的监听,原理代码如上;
引用头文件
@interface NSObject (Extension)
@property (nonatomic, copy) NSString *name;
// 分类中使用@property只会生成set/get的声明没有实现;
@end
//.m 中
@implementation NSObject (Extension)
char NameKey;
//用运行时实现set方法
-(void)setName: (NSString * )name {
/*
设置关联对象 /Users/Valien/百度云同步盘/同步盘/百度云同步盘/同步盘/运行时runtime.mkd
参数1: 关联哪个对象
参数2: 取值的key (每调用一次objc方法,就会关联一个值,为了知道要取的是哪一个值,给value联系一个key)
参数3: 值
参数4: 属性的存储关键字(如copy,retain等)
*/
objc_setAssociatedObject(self, &NameKey, name, OBJC_ASSOCIATION_COPY_ANATOMIC);
}
-(NSString*) name {
return objc_getAssociatedObject(self, &NameKey);
}
Unsigned int outCount ; //用于存储成员变量数量
//返回的是指针, 把指针和count结合起来, 就如同获取 成员变量数组
Ivar *ivars = class_copyIvarList([Person class], &outCount);
//遍历:
for (int i=0; i//获得每个成员变量 和 类型;
const char *name = ivar_getName(ivar);
const char *type = ivar_getTypeEncoding(ivar);
}
free(ivars);
1. 归解档
//1. 归档解档时,通过协议方法需要告诉对象 存取哪些成员变量 如何存取;
如果: 对象的成员很多 , 怎么办, 写垃圾代码么 ?
//归档
-(void)encodeWithCoder:(NSCoder *) encoder {
Unsigned int outCount ;
Ivar *ivars = class_copyIvarList([Person class], &outCount);
for (int i=0; iNSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [self valueForKeyPath:key];
[encoder encodeObject:value forKey:key]; //编码
}
free(ivars);
}
//解档
- (void)initWithCoder:(NSCoder*)decoder {
Unsigned int outCount ;
Ivar *ivars = class_copyIvarList([Person class], &outCount);
for (int i=0; iNSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [decoder decodeobjectForKey:key];
[self setValue:value forKeyPath:key]; //解码
}
free(ivars);
}
注意:由于C语言中包含creat/copy/retain/new等关键字,那么最后需要单独的释放free()/xxxrelease(如有CG前缀的); // initWithCoder不要写在分类中,否则父类方法会被覆盖;
ps. 如果某个类继承,那么从父类继承的属性按上面方法不能完全同步解归档;需要补充
Class c = self.class;
while(c&& c!= [NSObject class]) { //没有父类就为nil
Unsigned
xxxxx
free(ivars)
c = [c superclass];
}
2. 实现字典模型自转换
//当属性 name与字典 key一一对应时;(其实不对应也成功,应为是遍历的是属性组,有这个属性才赋值,没有就不会执行)
Class c = self.class;
while(c&& c!= [NSObject class]) { //没有父类就为nil
Unsigned int outCount ;
Ivar *ivars = class_copyIvarList([Person class], &outCount);
for (int i=0; iNSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
key = [key substringFromIndex:1];
//对应取出字典值
id value = dict[key];
// 将字典中的值设置到模型上
[self setValue:value forKeyPath:key];
//也可使用运行时方法: objc_setIvar(c,name,value)
}
free(ivars);
c = [c superclass];
}
//当模型嵌套模型时:
//当遍历到这个嵌套属性时. 传入的value 就是一个就是字典;
//只需调用一次这个模型的字典模型转换方法,传入字典; 具体细节在于判断
//for循环中添加如下代码:
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
NSRange range = [type rangeOfString:@"@"];
if(range.location != NSNotFound) { // 为了通用,能找到@,表示是@{}字典
type = [type substringWithRange:NSMakeRange(2,type.length-3)];//根据类型名字位置与长度截取 .type就是类型名了
if(![type hasPrefix:@"NS"]) { // 排除 NS开头的类
value = [NSClassFormString(type) objectWithDict:value]; //递归方法
}
}
//最终: 可以解决一般字典转模型自动转换 :
// 唯一还有一点是 属性是模型数组的话 ; 需要继续解决,具体可以参考下MJExtension的框架.
//如果模型有某个属性, 字典里面没有, 访问字典就会报错. KVC的value不能设空值.
加一句: if (nil == value) continue;
参考:MJExtension框架