最近公司开发的模块对接外部厂商时,遇到方法相互调用的问题.想起来了万能的runtime.网上找了一些资料基本都是碎片化,之言片语.通过相互融合整理了一下.
1.谈一下我对runtime的理解
对c语言而言在编译的时候就决定了调用顺序,如果没有实现会报错.对于OC来说的话,只要声明过即使不实现也不会报错.
我对OC的称呼为是 预编译消息发送类型的语言,在编译时并不能决定真正调用那个函数.只有在运行的时候根据收到的消息来调用函数.runtime就是把消息的内容给它动态的换掉.放到古代可以说是假传圣旨.
runtime发送消息,源码解释
/ 创建对象 -> 调用方法
erha *d = [[erha alloc]init];// 调用方法 -> 实例方法
Class d = objc_allocateClassPair([erha class], "d", 0);
// [d run];
// 系统底层本质 -> 让对象发消息objc_msgSend(d,@selector(run));// 等同于 [d run];// 调用方法 -> 类方法
// [erha eat];
objc_msgSend([Dog class], @selector(eat)); // 等同于 [erha eat];
2.谈谈runtime具有那些常用的功能,以及与其它实现的对比优劣.
runtime的基本常用场景:能获得某个类的所有成员变量, 能获得某个类的所有属性, 能获得某个类的所有方法,交换方法实现 , 能动态添加一个成员变量, 能动态添加一个属性,字典转模型,runtime归档/反归档. 如果 用JSPatch实现热更新的话,上面的场景都是重点.
下面重点讲解几个
1. 动态的添加对象的成员变量和方法
动态的添加成员变量和方法 与直接在父类里面做修改作对比. 其实在父类里面直接添加 成员变量与方法 子类继承父类会比较更好一点.
2. 动态交换两个方法的实现
一般是在项目没有做好容错 或者已经开发完成的项目出现了问题 或可能出现问题 用这个时候runtime就要大显身手,在运行的时候替换掉原来的方法.推荐使用.
3. 实现分类也可以添加属性
分类里面添加方法大家都知道,分类里面添加属性,正常来说是不行,缺乏key与value的绑定关系.所以会报错.后面我会讲解用runtime绑定key与value.
给系统的类添加分类扩展方法和属性是一件很美好的事情,推荐使用/
4. 实现NSCoding的自动归档和解档
主要还是通过class_copyIvarList,遍历对象属性,来做事情.
5. 实现字典转模型的自动转换
优秀的JSON转模型第三方库JSONModel、YYModel等都利用runtime对属性进行获取,赋值等操作,要比KVC进行模型转换更加强大,更有效率。阅读YYModel的源码可以看出,YY大神对NSObject的内容进行了又一次封装,添加了许多描述内容。其中YYClassInfo是对Class进行了再次封装,而YYClassIvarInfo、YYClassMethodInfo、YYClPropertyInfo分别是对Class的Ivar、Method和property进行了封装和描述。在提取Class的相关信息时都运用了Runtime。
3.关键词Ivar介绍
1.Ivar的定义
Ivar是objc_ivar的指针,包含变量名,变量类型等成员,代码形式如下.
typedef objc_ivar * Ivar; struct objc_ivar {
char *ivar_name;
char *ivar_type;
int ivar_offset;
#ifdef __LP64__ int space;
#endif }
2.Ivar的一些常用方法
//获取Ivar的名称 const char *ivar_getName(Ivar v);
//获取Ivar的类型编码, const char *ivar_getTypeEncoding(Ivar v)
//通过变量名称获取类中的实例成员变量 Ivar class_getInstanceVariable(Class cls, const char *name)
//通过变量名称获取类中的类成员变量 Ivar class_getClassVariable(Class cls, const char *name)
//获取指定类的Ivar列表及Ivar个数 Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
//获取实例对象中Ivar的值 id object_getIvar(id obj, Ivar ivar)
//设置实例对象中Ivar的值 void object_setIvar(id obj, Ivar ivar, id value)
4.方法实现上源码
1. 动态的添加对象的成员变量和方法
1.1添加成员变量
用到了class_addProperty,这个可以给已经存在的类添加新的property,而且能够用过反射遍历到动态添加的属性。正好适合于JSPatch打补丁的情况
之所以不通过调用class_addIvar来添加实例变量,是因为它会改变一个已有类的内存布局,一般是通过objc_allocateClassPair动态创建一个class,才能调用class_addIvar创建Ivar,最后通过objc_registerClassPair注册class。一般情况下打补丁没有这个需求。
方法1 使用 objc_allocateClassPair
运行时规定,只能在objc_allocateClassPair与objc_registerClassPair两个函数之间为类添加变量
//额外空间 未知,通常设置为 0 Class clazz = objc_allocateClassPair(父类class,类名,额外空间);
//以NSString*为例 //变量size sizeof(NSString) //对齐 指针类型的为log2(sizeof(NSString*)) //类型 @encode(NSString*) BOOL flag = class_addIvar(clazz,变量名,变量size,对齐,类型);
objc_registerClassPair(clazz);
方法2 使用class_addIvar
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
示例:
Class People = objc_allocateClassPair([NSObject class], "People", 0);
objc_registerClassPair(People);
//T@ objc_property_attribute_t attribute1;
attribute1.name = "T";
attribute1.value=@encode(NSString*);
//Noatomic objc_property_attribute_t attribute2 = {"N",""};//value无意义时通常设置为空 //Copy objc_property_attribute_t attribute3 = {"C",""};
//V_属性名 objc_property_attribute_t attribute4 = {"V","_name"};
//特性数组 objc_property_attribute_t attributes[] ={attribute1,attribute2,attribute3,attribute4};
//向People类中添加名为name的属性,属性的4个特性包含在attributes中 class_addProperty(People, "name", attributes, 4);
//获取类中的属性列表 unsigned int propertyCount;
objc_property_t * properties = class_copyPropertyList(People, &propertyCount);
for (int i = 0; i< propertyCount,i++){
NSLog(@"属性的名称为 : %s",property_getName(properties[i]));
NSLog(@"属性的特性字符串为: %s",property_getAttributes(properties[i]));
}
//释放属性列表数组 free(properties);
1.2 添加对象的方法
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp,
constchar*types)
__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
Class cls cls 参数表示需要添加新方法的类。
SEL name name 参数表示 selector 的方法名称,可以根据喜好自己进行命名。
IMP imp imp 即 implementation ,表示由编译器生成的、指向实现方法的指针。也就是说,这个指针指向的方法就是我们要添加的方法。
const char *types 最后一个参数 *types 表示我们要添加的方法的返回值和参数 这些表示方法都是定义好的(Type Encodings),关于Type Encodings的其他类型定义请参考官方文档 示例如果types参数为"i@:@“,按顺序分别表示:
i:返回值类型int,若是v则表示void
@:参数id 对象
::SEL(_cmd)
@:id(str)
代码示例 :
-(void)answer{
给类添加一个guess的方法,guessAnswer为guess方法的指针,指向guessAnswe实现的方法. 第四个参数是函数的返回值以及参数内容 v代表无返回值void,如果是i则代表int;@代表 id sel;
class_addMethod(Class cls, @selector(guess), (IMP)guessAnswer, "v@:");
if ([Class cls respondsToSelector:@selector(guess)]) {
//Method method = class_getInstanceMethod(Class cls, @selector(guess));
// [Class cls performSelector:@selector(guess)];
[ Class cls performSelector:@selector(guess)];
} else{
NSLog(@"Sorry,I don't know");
}
}
void guessAnswer(id self,SEL _cmd){
NSLog(@"He is from erha");
}
2. 动态交换方法的实现
用class_getInstanceMethod来取类方法,用class_getClassMethod来取实例方法时
Classclass= [selfclass];
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method newMethod = class_getInstanceMethod(class, newSelector);
BOOLdidAddMethod = class_addMethod(class, originalSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
给老方法天加新方法的实现(也可以说重写老方法), 如果新方法的实现在类里面存在,则添加方法失败,直接交换方法即可. 如果不存在,就会用新方法的实现重写老方法.
if(didAddMethod) {
class_replaceMethod(class, newSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); //class_replaceMethod 会调用class_addMethod和method_setImplementation 添加新方法,并获得老方法的实现.
}else{
存在直接交换 新老方法
method_exchangeImplementations(originalMethod, newMethod);
}
3. 实现分类也可以添加属性
关联对象就是runTime界的NSMultableDictionary 主要函数:
void objc_setAssociatedObject(id object,constvoid*key, id value, objc_AssociationPolicy policy);//相当于 setValue:forKey 进行关联value对象
id objc_getAssociatedObject(id object, constvoid*key); //用来读取对象
void objc_removeAssociatedObjects(id object);//函数来移除一个关联对象,或者使用objc_setAssociatedObject函数将key指定的关联对象设置为nil。
参数讲解:
key:要保证全局唯一,key与关联的对象是一一对应关系。必须全局唯一。通常用@selector(methodName) ,静态变量&btnKey,_cmd。
value作为key:要关联的对象。
policy:关联策略。有五种关联策略。OBJC_ASSOCIATION_ASSIGN 等价于 @property(assign)。OBJC_ASSOCIATION_RETAIN_NONATOMIC等价于 @property(strong, nonatomic)。OBJC_ASSOCIATION_COPY_NONATOMIC等价于@property(copy, nonatomic)。OBJC_ASSOCIATION_RETAIN等价于@property(strong,atomic)。OBJC_ASSOCIATION_COPY等价于@property(copy, atomic)。
代码实现
定义属性 @property(nonatomic,copy) NSString *chineseName;
在.m文件里重写 set get方法
-(void)setChineseName:(NSString *) chineseName{
char cName;
objc_setAssociatedObject(self, &cName, chineseName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)chineseName{
return objc_getAssociatedObject(self, &cName);
}
4. 实现NSCoding的自动归档和解档
5. 实现字典转模型的自动转换
最近项目比较近还没写完,后续会继续更新