1、消息发送机制
objc语法中类似[object hello]的方法调用语法,在运行时会转换成c语言的objc_msgSend函数,并默认传入两个参数:(id self,SEl sel),参数id 传递方法调用对象,sel传递方法选择器。因此,我们可以通过运行时直接调用objc_msgSend函数调用方法(包括私有方法)。
objc_msgSend函数做了动态绑定所需要的一切:①它首先找到方法选标所对应的方法实现。因为不同的类对同一方法可能会有不同的实现,所以找到的方法实现依赖于消息接收者的类型。②然后将消息接收者的对象以及方法中指定的参数传给找到的方法实现。③将方法实现的返回值作为该函数的返回值返回。
当一个方法会被连续调用很多次,并且你希望节省每次调用都要发送消息的开销时,可以使用方法地址来调用就显得很有效果。
ps:首先要到build Settings关闭 strict checking,否则会报错。
- (void)sendMsg{
Person *p = [Person new];
//调用类方法
objc_msgSend([Person class],@selector(run));
//调用对象方法
objc_msgSend(p, @selector(run));
}
//Person.m文件中定义两个私有方法
+ (void)run{
NSLog(@"调用了run类方法");
}
- (void)run{
NSLog(@"调用了run实例方法");
}
2、交换方法
当对象调用方法时,会先到所对应的类中匹配方法列表,若没有则往父类找,一直没找到的话就报unrecognize selector的错误。但父类的方法列表中并不记录方法的具体内容,它记录的是方法实现(IMP)的入口。我们可以通过runtime交换方法的实现,从而达到调a方法名,但对应的是b的实现的效果。
假如有如下场景:iOS7刚流行时,项目过程中,产品经理本来说全部采用扁平化的图片,但后来又要求为iOS6之前的配备拟物化的图片。此时,你的图片设置逻辑已经写了很多了,逐个去查找修改肯定不现实。
解决方案:可以为image添加一个分类,分类中自定义一个方法,内部做系统版本的逻辑判断,最后仍调用系统的方法产生图片。然后使用运行时交换自定义方法和系统原本方法来达到不改动源代码达到目的的效果。
//分类中
#import "UIImage+Exchange.h"
#import
@implementation UIImage (Exchange)
/**
* 为了让方法交换一开始就生效,把方法交换的代码写在load方法里。load方法在程序一启动时就生效。
*/
+ (void)load{
//获取原生的方法
Method imageNamed = class_getClassMethod([UIImage class], @selector(imageNamed:));
//获取自定义的方法
Method imageWithName = class_getClassMethod([UIImage class], @selector(imageWithName:));
//交换方法
method_exchangeImplementations(imageNamed, imageWithName);
}
/**
* 自定义图片类方法
*
* @param name 图片名字
*
* @return 返回图片实例
*/
+ (UIImage *)imageWithName:(NSString *)name{
//判断系统版本
CGFloat version = [[UIDevice currentDevice].systemVersion floatValue];
if (version > 6.0) {
NSLog(@"返回扁平化的图片");
//此处做相应逻辑处理,如修改资源名字
}else{
NSLog(@"返回拟物化的图片");
}
//调用系统的方法生成对应的图片。因为已经交换了方法,所以使用imageWithName才能掉到系统方法
[self imageWithName:name];
//返回符合要求的图片
return nil;
}
3、返回实例变量
什么是实例变量? 使用属性时自动帮你生成的带下划线的,如_age
应用场景:如字典转模型
- (void)getVarList{
//类中实例变量的数量。函数返回一个数组,还有一个返回值通过传地址进去返回。
unsigned int ivarCount;
Ivar* ivarList = class_copyIvarList([Person class], &ivarCount);
//遍历返回的实例变量数组
for (int i = 0; i < ivarCount; i++) {
//打印实例变量名
NSLog(@"%s",ivar_getName(ivarList[i]));
}
}
4、获取方法列表,类似获取实例变量
- (void)getMethodList{
unsigned int outCount;
Method *methodList = class_copyMethodList([Person class], &outCount);
for (int i = 0; i < outCount; i++) {
SEL sel = method_getName(methodList[i]);
NSLog(@"%@",NSStringFromSelector(sel));
}
}
5、通过runtime动态创建类并添加实例变量和方法(kvo底层用到了这个,动态创建了派生类,并重写了set方法)
- (void)addClass{
//分别为 继承父类的类名 ,类名 ,
Class myClass = objc_allocateClassPair([NSObject class], "City", 0);
//添加实例变量,分别为变量所需类名,变量名,变量类型大小,变量类型编码
BOOL flag = class_addIvar([myClass class], "_name", sizeof(NSString *), 0, "@");
if (flag) {
//如果添加成功,使用kvc为变量赋值
id newCity = [[myClass alloc]init];
[newCity setValue:@"深圳" forKey:@"_name"];
//读取变量值
NSLog(@"%@",[newCity valueForKey:@"_name"]);
}
//添加方法,方法所属类,方法选择器,方法实现,方法实现的类型编码
//类型编码要参考官方文档,v标识void返回类型 ,
//调用方法时,默认传入 调用者对象和方法选择器:对应 @:
//第三个参数为NSString对象,所以用 @,综合为 v@:@
flag = class_addMethod([myClass class], @selector(aMethodSel:),(IMP)aMethodImp, "v@:@");
//调用方法
id newCity = [[myClass alloc]init];
[newCity aMethodSel:@"添加方法"];
}
//方法实现,方法选择器最终找到方法实现并执行里面的代码
void aMethodImp(id self,SEL sel, NSString *name){
NSLog(@"%s methodname=%@",__func__,name);
}
//方法选择器,类似于声明,里面的内容并不会执行
- (void)aMethodSel:(NSString *)name{
NSLog(@"%s selname=%@",__func__,name);
}
6、为分类添加属性,有时候为了调用方便,采用分类的形式,但又需要保存某些值,可以使用这个方法
//分类中定义一个url属性,重写set,get
- (void)setUrl:(NSString *)url{
/**
* 关联属性的对象
属性的key值,这个值名字可以随便设,和取值时候保持一致就行
属性的value值
//内存管理策略,如copy,assign等
*/
objc_setAssociatedObject(self, "url", url, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)url{
//根据key值取出保存的value值
return objc_getAssociatedObject(self, "url");
}