一理论
Runtime:运行时,它是由c语言和汇编实现一个api是我们oc的基石,oc是一门高级编程语言,也是c的延伸,我们编写的代码在程序运行时会通过runtime汇编成c语言。其中最主要的基石消息机制。c语言,函数的调用在编译时就会决定调用哪个函数,我们oc的函数调用叫消息发送,属于动态调用,在编译的时候可以调用任意函数即使这个函数么有实现,只要声明就不会报错,只有在真正运行时才会确定调用哪个函数。
1.对象结构:
@interface NSObject <NSObject> {
Class isa;//指向该对象所属类
}
2.类结构
Class isa OBJC_ISA_AVAiLaBILITY isa指针 指向元类的objc_class结构体指针。
Class super_class 父类
const char *name。 类名
long version 类版本信息
long instance_size。 实列变量大小
struct objc_ivar_list *ivars。 成员变量链表
struct objc_method_list methodLists。方法列表
objc_cache cache。 对象使用过的方法缓存
3.消息发送机制
消息发送机制的核心是isa
当给调用对象[obj test]时是给obj对象发送一条test消息,如果obj对象能响应那么直接响应,如果obj不能响应这个消息时,那么根据obj中的isa指针去该对象的类中寻找(缓存列表,方法列表,找到调用)如果还没有找到那么类也是对象,他也有父类,根据类中的isa指针去父类中寻找,还没找到根据父类的isa指针找到该父类的元类–>一直找到根类(NSObject)如果没有找到进入消息转发机制(动态方法解析,备用者接受,消息重定向)
4消息转发机制
1)动态方法解析
动态方法解析发生在缓存方法和父类方法列表中都找不到执行的方法时触发
+(BOOL)resolveInstanceMethod:(SEL)sel{
//如果消息是test时
if (sel == @selector(test)) {
动态添加方法
class_addMethod([self class], sel, (IMP)testMethod, "v@");
return YES;
}
return [super resolveInstanceMethod:sel];
//如果返回NO那么进入被用者接受方法
return YES;
}
void testMethod(id obj,SEL _cmd){
NSLog(@"test");
}
2)被用者接受
当所属类不能动态添加方法的时候,runtime会询问是否有其他对象可以处理
这个未知的方法
-(id)forwardingTargetForSelector:(SEL)aSelector{
//
if (aSelector ==@selector(test)) {
//指定Person类作为备用接受者
return [[Person alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
//如果返回nil,那么进入第三阶段消息重定向
return nil;
3)消息重定向
当没有备用者能接受这个方法时,runtime会将未知消息细节打包成NSInvocation对象
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if ([NSStringFromSelector(aSelector) isEqualToString:@"test"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];//签名进入fowardInvocation
}
return [super methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
SEL sel = anInvocation.selector;
Person *p = [[Person alloc]init];
if ([p respondsToSelector:sel]) {
[anInvocation invokeWithTarget:p];
}else{
[self doesNotRecognizeSelector:sel];
}
}
结论:当我们给一个对象发送消息时:是列对象根据isa指针找到所属的class,遍历methodLists,如果没有,那么根据
该类super class元类如果父类还没有这个方法那么负类根类一层一层往下找如果都没有找到这个方法的实现,那么就会报错,
但是我们有三次机会补救,第一阶段:动态方法决议,第二阶段:消息转发机制
实类对象->对象所属类(类对象,类本质也是一个对象)->类的元类(元类也是对象)->根元类(继承NSObject)根元类的的isa指针指向自己
二 runtime常用方法
objc_msgs end(objc,@selector(方法名))调用对象方法
objc_msgSen([objc class],@slector(方法名))类名调用类方法
Method imageName=class_getClassMethod(self,@selector(方法民))获取一个类的方法
//方法交换
method_exchangeimplementations(需要交换的方法,方法2)交换两个方法的实现
//动态添加属性
objc_setAssociatedObject(self,被关联索引,被关联对象,缓存策略);关联对象
objc_getAssociatedObject(self,被关联者索引key);//获取关联对象
//获取实列方法
class_getinstanceMethod()
class_getClassMethod()
objc_property_t *ps=class_cpyPropertyList([self class],&count)获取类的中所有属性
property_getName()获取属性名称
Free(ps)释放数组
Ivar = class_copyIvarList([PerSon class],&count)//获取类中所有的成员变量
Ivar_getName()获取成员变量名称
Class_copyMethodList()获取类中所有的方法
sel_getName(SEL)获取类中所有方法名称
objc_getClass(“”);通过类名获取类
class_replaceMethod:替换类方法的定义
三实际使用
1:方法交换
1.1)使用场景:系统类的方法无法满足我们实际需求,比如字体适配,可以使用方法交换 将systemFontOfSize:<#(CGFloat)#> 换成自定义方法
实际使用:给image中的ImageNamed:@“”方法添加一个图片加载完成或失败提示
第一步:创建uiamge分类
第二步添加<objc/message.h>
第三步在load方法中进行方法交换
+(void)load{
//获取imageName:方法地址
Method imageName = class_getClassMethod(self, @selector(imageNamed:));
//获取自己写的iamgeWithName:方法地址
Method ljl_imageWithNameMethod = class_getClassMethod(self,@selector(ljl_imageNamed:));
//交换方法地址,相当于交换实现方式
method_exchangeImplementations(imageName,ljl_imageWithNameMethod);
// 调用imageNamed => ljl_imageNamedMethod
// 调用xmg_imageNamedMethod => imageNamed
}
第四步实现交换方法
/既能加载图片又能打印
+(UIImage *)ljl_imageNamed:(NSString *)name{
UIImage *image = [UIImage ljl_imageNamed:name];
if (image) {
NSLog(@"加载成功");
}else{
NSLog(@"加载失败");
}
return image;
}
注意:在分类中最好不要重写系统方法,一旦重写将会将系统方法覆盖掉
initialize与load的区别:前者当类被初始化的时候会调用,可能调用多次,没有子类之调用一次,柚子类会多次调用
后者当类加载进内存的时候调用,而且不管有没有子类,都只会调用一次,在main函数之前调用
2.对象关联
动态添加属性(分类添加属性)
需求:给btn添加一个btnCloudox字符串属性
步骤一创建btn分类
步骤二:给分类添加属性
@property(nonatomic,copy)NSString *btnCloudox;
第x步倒入<objc/runtime.h>
第四步实现set方法和get方法
(void)setBtnCloudox:(NSString *)btnCloudox{
objc_setAssociatedObject(self,&btnCloudox,btnCloudox,OBJC_ASSOCIATION_COPY_NONATOMIC);
}
//get方法
-(NSString *)btnCloudox{
return objc_getAssociatedObject(self,&buttonCloudox);
}
使用:btn.btnCloudox = @“我是分类中的属性”
3.字典转模型
需求:如果对象属性很多的时候,我们可以用kvc批量设置,但是当kvc设置时有个致命错误就是当字典中的键找到对应的值时会报错
创建一个NSObject+model的分类
+(instancetype)modelWithDic:(NSDictionary *)dic;
@end
.m文件实现
+(NSArray *)properList{
unsigned int count = 0;
//获取模型属性,返回值是所有的属性的数组objc_property_t
objc_property_t *propertyList = class_copyPropertyList([self class],&count);
NSMutableArray *array = [NSMutableArray array];
//遍历整个数组
for (NSInteger i=0; i<count;i++) {
//获取属性
objc_property_t property = propertyList[i];
//获取属性名称
const char *cName = property_getName(property);
NSString *name = [[NSString alloc]initWithUTF8String:cName];
//添加到数组中
[array addObject:name];
}
//释放属性数组
free(propertyList);
return array.copy;
}
+(instancetype)modelWithDic:(NSDictionary *)dic{
id obj = [self new];
//遍历属性数组
for (NSString *property in [self properList]) {
//判断字典中是否包含这个key
if (dic[property]) {
//使用kvc赋值
[obj setValue:dic[property] forKey:property];
}
}
return obj;
}
使用:在model中倒入粉类然后就ok
NSDictionary *dic = @{@"name":@"黄渤",@"sex":@"男",@"age":@"44"};
Model *model = [Model modelWithDic:dic];
NSLog(@"name:%@,sex:%@",model.name,model.sex);
4.解归档
#pragma maek----runtime解归档
-(void)encodeWithCoder:(NSCoder *)aCoder{
unsigned int count;
//获取指向当前类的所有属性的指针
objc_property_t *properties = class_copyPropertyList([self class],&count);
//遍历当前属性
for (NSUInteger i=0; i<count; i++) {
//当前属性指针
objc_property_t property = properties[i];
//获取c字符串属性名
const char *name = property_getName(property);
//c属性名转oc字符串
NSString *propertyName = [NSString stringWithUTF8String:name];
//通过关键字获取属性
NSString *properValue = [self valueForKey:propertyName];
//编码属性归档
[aCoder encodeObject:properValue forKey:propertyName];
}
free(properties);
}
-(id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
unsigned int count;
//获取当前类的所有属性指针
objc_property_t *properties = class_copyPropertyList([self class], &count);
//便利当前类中的所有属性
for (NSInteger i=0; i<count;i++) {
//获取属性指针
objc_property_t property = properties[i];
//获取c字符串属性名
const char *name = property_getName(property);
//换成oc属性名
NSString *propertyName = [NSString stringWithUTF8String:name];
//根据属性名获取属性值
NSString *properValue = [aDecoder decodeObjectForKey:propertyName];
//解档
[self setValue:properValue forKey:propertyName];
}
free(properties);
}
return self;
}
7.获取类属性,变量,方法
//获取类中的成员变量
unsigned count;
Ivar *ivars = class_copyIvarList([PerSon class],&count);
for (NSUInteger i=0;i<count;i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:name];
NSLog(@"成员变量名%ld==%@",i,ivarName);
}
free(ivars);
//获取类中的属性名
objc_property_t *propertys = class_copyPropertyList([PerSon class], &count);
for (NSInteger i=0;i<count;i++) {
objc_property_t property = propertys[i];
const char *name = property_getName(property);
NSString *properName = [NSString stringWithUTF8String:name];
NSLog(@"PerSon类中的属性名%ld==%@",i,properName);
}
free(propertys);
//获取类中的所有方法
Method *methhods = class_copyMethodList([PerSon class],&count);
for (NSInteger i=0;i<count;i++) {
Method method = methhods[i];
SEL methodSel = method_getName(method);
const char *name = sel_getName(methodSel);
NSString *methName = [NSString stringWithUTF8String:name];
NSLog(@"PerSon中的方法%ld===%@",i,methName);
}
free(methhods);