runtime简称运行时,也就是在运行时候的一些机制。对于oc函数,属于动态调用过程,在编译的时候并不能决定真正调用哪些函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
交换两个方法的实现,拦截系统自带的方法调用功能
导入
获得某个类的类方法:
Method class_getClassMethod(Class cls , SEL name)
获得某个类的实例对象方法
Method class_getInstanceMethod(Class cls , SEL name)
交换两个方法的实现
void method_exchangeImplementations(Method m1 , Method m2)
下面举个例子:
1.方法简单的交换
创建一个Person类,类中实现以下两个类方法,并在.h 文件中声明
+ (void)run {
NSLog(@"跑");
}
+ (void)study {
NSLog(@"学习");
}
在viewcontroller中引用,导入
// 获取两个类的类方法
Method m1 = class_getClassMethod([Person class], @selector(run));
Method m2 = class_getClassMethod([Person class], @selector(study));
// 开始交换方法实现
method_exchangeImplementations(m1, m2);
// 交换后,先打印学习,再打印跑
[Person run];
[Person study];
上面可以看出,交换了方法的实现。
2.拦截系统方法
一般是写个类的分类,然后在分类中重写该类的一个方法,最后在load方法中交换一下自定义的方法和系统的方法。当然了,自定义方法中最后一定要再调用一下系统的方法。
1.为UIImage建一个分类(UIImage+Category)
2、在分类中实现一个自定义方法,方法中写要在系统方法中加入的语句,比如版本判断
+ (UIImage *)junjie_imageNamed:(NSString *)name {
double version = [[UIDevice currentDevice].systemVersion doubleValue];
if (version >= 7.0) {
// 如果系统版本是7.0以上,使用另外一套文件名结尾是‘_os7’的扁平化图片
name = [name stringByAppendingString:@"_os7"];
}
return [UIImage junjie_imageNamed:name];}
3、分类中重写UIImage的load方法,实现方法的交换
+(void)load {
// 获取两个类的类方法
Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
Method m2 = class_getClassMethod([UIImage class], @selector(junjie_imageNamed:));
// 开始交换方法实现
method_exchangeImplementations(m1, m2);
}
分类中设置属性,给任何一个对象设置属性
在分类中,是没有属性的,也就是说,你在分类的.h文件中@property只能创建get和set方法的声明,但无法生成属性变量,虽然在分类的.m中我们可以设置个全局变量,代码如下:
NSString *_ssex;
- (NSString *)Mysex {
return _ssex;
}
- (void)setMysex:(NSString *)Mysex {
_ssex = Mysex;
}
在viewdidload中
UIImage *image = [UIImage new];
image.Mysex = @"男生";
_MyImage = [UIImage new];
_MyImage.Mysex = @"女生";
NSLog(@"image == %@, _MyImage == %@", image.Mysex, _MyImage.Mysex);
会发现打印结果都是女生,这是因为全局变量程序整个执行过程中内存中只有一份,我们创建多个对象修改其属性值都会修改同一个变量,这样就无法保证像属性一样每个对象都拥有其自己的属性值。所以这时候就需要借用runtime为分类添加属性的功能了。
还是在刚才的分类中,代码如下:
NSString *_ssex;
- (NSString *)Mysex {
// return _ssex;
return objc_getAssociatedObject(self, &_ssex);
}
- (void)setMysex:(NSString *)Mysex {
// _ssex = Mysex;
//将某个值跟某个对象关联起来,将某个值存储到某个对象中
objc_setAssociatedObject(self, &_ssex, Mysex, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
运行viewdidload,发现第一个输出男生,第二个输出女生了。
方法解惑:
void objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy)
set方法,将值value 跟对象object 关联起来(将值value 存储到对象object 中)
参数 object:给哪个对象设置属性
参数 key:一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,以后建议用char 可以节省字节,我这里不改了
参数 value:给属性设置的值
参数policy:存储策略 (assign 、copy 、 retain就是strong)
id objc_getAssociatedObject(id object , const void *key)
利用参数key将对象object中存储的对应值取出来。
获得一个类的所有成员变量,并对其操作。
Ivar *ivars = class_copyIvarList(Class cls , unsigned int *outCount)
ivars:返回值,存放所有获取到的属性。
cls:哪个类。
outCount:放一个接收值的地址,用来存放属性的个数。
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([Person class], &outCount);
NSLog(@"count == %d", outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i]; //取出i位置对应的成员变量
const char *name = ivar_getName(ivar);
const char *type = ivar_getTypeEncoding(ivar);
NSLog(@"成员变量名:%s, 成员变量类型:%s", name, type);
}
free(ivars); //释放内存
通过上面的代码我们可以获取到自定义类中所有成员变量的名字和类型。
但是有啥用啊。。。网上有的作者就这么一提,获取到又不能操作有什么用。后来发现了这个方法object_setIvar,他可以设置值,接下来我来教大家怎么用。
比如说我们之前在Person类中定义了私有成员变量_name,NSString类型的。
在Person类中定义两个方法,如下:
- (void)getName:(NSString *)junjie {
_name = junjie;
NSLog(@"他的名字叫:%@",_name);
}
- (void)printfName {
NSLog(@"我的name is %@", _name);
}
这两个方法用到了私有变量_name
在viewdidload中,作如下操作
unsigned int outCount;
Ivar *IvarArray = class_copyIvarList([Person class], &outCount);
[person getName:@"feifei"];
[person printfName];
//操作后
object_setIvar(person, IvarArray[1], @"123");// 我在这里是第二个元素
[person printfName];
打印结果如下:
可见我们在外面利用runtime,直接将私有变量的值给修改了,是不是感觉很吊。。。
当然了,有set就有get,object_getIvar方法,他可以获取到上面set设置的值(一般出现在一个类中),有兴趣的童鞋可以去研究研究。