runtime是运行时库(Runtime Library),也简称运行时。
它是一个主要是C和汇编写的库,对C进行了特殊的处理,将结构体视为对象,将函数视为方法,使得C有了面向对象的能力,从而才创造了Objective-C。
这点也可以看出,C是编译时语言,而OC是动态运行时语言,所以在编译阶段,尽管OC中的方法没有实现也不会报错,而C会报错。
在运行时,OC语言才进行方法的处理,比如讲[person eat];转换为objc_msgSend(person, @selector(eat));然后通过person的isa指针找到person对应的class,在class中先去cache中通过SEL方法选择器查找对应的method,若缓存中未找到,再去methodList查找,若还未找到,便去父类中查找,如果这个过程中找到了该方法,便会自动将该方法加入cache中,方便下一次的查找,并且,编译器开始执行找到的这个函数。
上面这段文字可以片面的理解runtime的消息机制,消息机制是runtime最主要的机制,如果要在代码中使用,需要导入。
首先,对objc_class结构体的内容做个简单的说明:
[objc]view plaincopy
structobjc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class//指向父类
constcharchar*name//类名
longversion//类的版本信息(默认是0,class_setVersion,clss_getVersion可set和get)
longinfo//类的标识信息
longinstance_size//该类的实例变量size
structobjc_ivar_list*ivars//用于存储每个成员变量的地址
structobjc_method_list**methodLists//与info的标识信息有关,如CLS_CLASS存储实例方法,CLS_META存储静态方法
structobjc_cache*cache//指向最近使用方法的指针,它的作用是提升效率,可以不严密的理解为缓存
structobjc_protocol_list*protocols//存储协议列表
#endif
} OBJC2_UNAVAILABLE;
在实际开发中,runtime最常用的场景就是交换方法、归档、字典转模型、动态生成属性方法、添加类方法等
1.创建一个类的分类
2.导入
3.实现交换并且自定义方法
[objc]view plaincopy
//由于交换方法我们希望在工程运行期间只执行一次,所以通常写在load里面
+ (void)load {
Method imageMethodNamed = class_getClassMethod(self,@selector(imageNamed:));
Method myImageMethodNamed = class_getClassMethod(self,@selector(myImageNamed:));
//实现两个方法的交换
method_exchangeImplementations(myImageMethodNamed,imageMethodNamed);
}
+ (instancetype)customImageNamed:(NSString*)name {
//实际调用的是imageNamed:
UIImage*image = [UIImagecustomImageNamed:name];
//*此处为自定义配置*
returnimage;
}
由于在类目中添加属性不自动生成setter和getter方法,如果在.m文件中使用@dynamic配置起来就较为繁琐,如果一定要在类目中添加属性,我们可以使用runtime关联方法,简单方便。
比如在类目.h文件中声明了score属性,在.m文件中如下实现:
[objc]view plaincopy
voidvoid*key;
- (void)setScore:(float)score {
//关联引用
/**
* 1.给哪个对象属性进行关联
* 2.用来保存传入的值的指针(用于get方法获取值)
* 3.传入的值(注意是对象类型)
* 4.关联引用的策略(这个根据属性添加的修饰而定)
*/
objc_setAssociatedObject(self, key, @(score), OBJC_ASSOCIATION_ASSIGN);
}
- (float)score {
idscore = objc_getAssociatedObject(self, key);
return[scorefloatValue];
}
归解档可以用kvc模式进行操作,但是如果属性过多的话,这样就显得极为繁琐而且容易出错,所以用runtime实现更为的科学和简洁。
[objc]view plaincopy
//归档
- (void)encodeWithCoder:(NSCoder*)aCoder {
//获取某个类的所有成员变量
unsignedintcount =0;
Ivar*ivarList = class_copyIvarList([selfclass], &count);
//归档
for(inti =0; i < count; i ++) {
Ivar aIvar = ivarList[i];
//获取成员变量的名称
constcharchar*iVarName = ivar_getName(aIvar);
idvalue = [selfvalueForKey:[NSStringstringWithUTF8String:iVarName]];
if(!value) {
}else{
[aCoderencodeObject:valueforKey:[NSStringstringWithUTF8String:iVarName]];
}
}
}
//解档
- (instancetype)initWithCoder:(NSCoder*)aDecoder {
if(self= [superinit]) {
unsignedintcount =0;
Ivar*ivarList = class_copyIvarList([selfclass], &count);
for(inti =0; i < count; i ++) {
Ivar aIvar = ivarList[i];
constcharchar*name = ivar_getName(aIvar);
idvalue = [aDecoderdecodeObjectForKey:[NSStringstringWithUTF8String:name]];
if(!value) {
}else{
[selfsetValue:valueforKey:[NSStringstringWithUTF8String:name]];
}
}
}
returnself;
}
这里需要注意的是,该方法只适用于字典的键和模型的属性一一对应的情况,如果要处理不一一对应的情况,最简单的解决方法是使用三方。
[objc]view plaincopy
+ (instancetype)modelWithDictionary:(NSDictionary*)dic {
Student*aStudent = [Studentnew];
unsignedintcount =0;
objc_property_t*propertyList = class_copyPropertyList([selfclass], &count);
for(inti =0; i < count; i ++) {
objc_property_t aProperty = propertyList[i];
//获取属性名
constcharchar*name = property_getName(aProperty);
idvalue = dic[[NSStringstringWithUTF8String:name]];
if(!value) {
}else{
//使用kvc给属性赋值
[aStudentsetValue:valueforKey:[NSStringstringWithUTF8String:name]];
}
}
returnaStudent;
}
[objc]view plaincopy
voidstudy(idreccevier,SELsel) {
}
// 如果调用的方法没有实现,就会走这个方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if(sel ==@selector(study)) {
class_addMethod(self,@selector(study), (IMP)study,"v@:");
}
return[superresolveInstanceMethod:sel];
}
值得注意的是,这个方法是在运行时动态调用的,所以编译的时候会有警告。