项目中常常会有一些的功能模块用到runtime,最近也在学习它.对于要不要阅读runtime的源码,我以为仅仅是处理正常的开发,那真的没有必要,只要把经常使用的一些函数看下和原理理解下就能够了.
可是若是真能静下心好好阅读源码,真的能帮你更加深刻理解objc自己以及通过高阶包装出来的那些特性。ios
runtime就是运行时,每一个语言都有它的runtime.通俗点讲就是程序运行时发生的事情.
好比C语言,在编译的时候就决定了调用哪些函数,经过编译后就一步步执行下去,没有任何二义性,因此它是静态语言.
而objc的函数调用则能够理解为发消息,在编译的时候彻底不能决定哪一个函数执行,只有在运行的时候才会根据函数名找到函数调用,因此在运行的时候它能动态地添加调换属性,函数.因此它是动态语言.
动态和静态语言没有明显的界限,我感受它们就是以runtime来区分的,看它在runtime时,有多灵活,那么它就有多动态.git
typedef struct objc_method *Method struct objc_method { SEL method_name; char *method_types; IMP method_imp; }
SEL是char*,能够理解为函数的姓名.
IMP就是函数指针,指向函数的实现.
==在objc_class中method list保存了一个SEL<>IMP的映射.因此经过SEL能够找到函数的实现==github
typedef struct objc_ivar *Ivar; struct objc_ivar { char *ivar_name; char *ivar_type; int ivar_offset; #ifdef __LP64__ int space; #endif }
实例变量,跟某个对象关联,不能被静态方法使用,与之想对应的是类变量数组
typedef struct objc_category *Category; struct objc_category { char *category_name; char *class_name; struct objc_method_list *instance_methods; struct objc_method_list *class_methods; struct objc_protocol_list *protocols; }
Catagory能够动态地为已经存在的类添加新的行为。好比类方法,实例方法,协议.
==根据结构可知,不能添加属性,实例变量==缓存
struct objc_method_list { struct objc_method_list *obsolete; int method_count; int space; struct objc_method method_list[1]; } struct objc_ivar_list { int ivar_count; int space; struct objc_ivar ivar_list[1]; }
==简单地理解为存有方法和实例变量的数组==框架
//类在runtime中的表示 struct objc_class { Class isa;//指针,顾名思义,表示是一个什么, //实例的isa指向类对象,类对象的isa指向元类 #if !__OBJC2__ Class super_class; //指向父类 const char *name; //类名 long version; long info; long instance_size struct objc_ivar_list *ivars //成员变量列表 struct objc_method_list **methodLists; //方法列表 struct objc_cache *cache;//缓存 //一种优化,调用过的方法存入缓存列表,下次调用先找缓存 struct objc_protocol_list *protocols //协议列表 #endif }; struct objc_cache { unsigned int mask; unsigned int occupied; Method buckets[1]; };
==objc_cache能够理解为存最近调用过的方法的数组,每次调用先访问它,提升效率==函数
class_copyPropertyList //获取属性列表 class_copyMethodList //获取方法列表 class_copyIvarList //获取成员变量列表 class_copyProtocolList //获取协议列表
常见用于字典转模型的需求中:性能
@interface LYUser : NSObject @property (nonatomic,strong)NSString *userId; @property (nonatomic,strong)NSString *userName; @property (nonatomic,strong)NSString *age; @end - (void)viewDidLoad { [super viewDidLoad]; //利用runtime遍历一个类的所有成员变量 NSDictionary *userDict = @{@"userId":@"1",@"userName":@"levi",@"age":@"20"}; unsigned int count; LYUser *newUser = [LYUser new]; objc_property_t *propertyList = class_copyPropertyList([LYUser class], &count); for (int i = 0; i < count; i++) { const char *propertyName = property_getName(propertyList[i]); NSString *key = [NSString stringWithUTF8String:propertyName]; [newUser setValue:userDict[key] forKey:key]; } NSLog(@"%@--%@--%@",newUser.userId,newUser.userName,newUser.age); }
==这只是最简单的转化,还要考虑容错,转换效率,如今有不少开源框架作的很不错.这是一些开源框架的性能对比:==模型转换库评测结果学习
class_getInstanceMethod() //类方法和实例方法存在不一样的地方,因此两个不一样的方法得到 class_getClassMethod() //以上两个函数传入返回Method类型 method_exchangeImplementations //()交换两个方法的实现
==这个用到的地方不少,能够大大减小咱们的代码量,经常使用的有防错措施,统计打点,统一更新界面效果==优化
防错措施
-(void)viewDidLoad { NSMutableArray *testArray = [NSMutableArray new]; [testArray addObject:@"1"]; NSString *a = nil; [testArray addObject:a]; for (NSInteger i = 0; i < testArray.count; i++) { NSLog(@"%@",testArray[i]); } } @implementation NSMutableArray(ErrorLog) +(void)load { Method originAddMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(addObject:)); Method newAddMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(el_addObject:)); method_exchangeImplementations(originAddMethod, newAddMethod); } /* * 本身写的方法实现 */ -(void)el_addObject:(id)object { if (object != nil) { [self el_addObject:object]; } else { //能够添加错误日志 NSLog(@"数组添加nil"); } } @end
统计打点
和上面的实现方式一致.在对应类的Category的load方法里交换.
// 统计页面出现 Method originAddMethod = class_getInstanceMethod([self class], @selector(viewDidLoad)); Method newAddMethod = class_getInstanceMethod([self class], @selector(el_ViewDidLoad)); method_exchangeImplementations(originAddMethod, newAddMethod); // 统计Button点击 Method originAddMethod = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:)); Method newAddMethod = class_getInstanceMethod([self class],@selector(el_sendAction:to:forEvent:))); method_exchangeImplementations(originAddMethod, newAddMethod);
统一更新界面效果
不少时候咱们作项目都是先作逻辑,一些页面颜色,细节都是最后作.这就遇到了一些问题,可能只是改个cell右边箭头边距,placeholder默认颜色.若是一个个改过来又麻烦又有可能有疏漏,这个时候runtime就能够大显神通了.
//这个就能够统一cell右边箭头格式,很是方便 + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(layoutSubviews); SEL swizzledSelector = @selector(swizzling_layoutSubviews); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); method_exchangeImplementations(originalMethod, swizzledMethod); }); } //设置cell右边箭头 - (void)setAccessoryType:(UITableViewCellAccessoryType)accessoryType { if (accessoryType == UITableViewCellAccessoryDisclosureIndicator) { UIImageView *accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"about_arrow_icon"]]; accessoryView.centerY = self.centerY; accessoryView.right = self.width-16; self.accessoryView = accessoryView; } else if (accessoryType == UITableViewCellAccessoryNone) { self.accessoryView = nil; } } //设置cell右边箭头间距 - (void)swizzling_layoutSubviews { [self swizzling_layoutSubviews]; if (self.imageView.image) { self.imageView.origin = CGPointMake(16, self.imageView.origin.y); self.textLabel.origin = CGPointMake(CGRectGetMaxX(self.imageView.frame)+10, self.textLabel.origin.y); } else { self.textLabel.origin = CGPointMake(16, self.textLabel.origin.y); } self.textLabel.width = MIN(self.textLabel.width, 180); self.accessoryView.right = self.width-16; }
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) objc_getAssociatedObject(id object, const void *key)
前面已经讲过,Category不能添加属性,经过关联对象就能够在运行时动态地添加属性.
这但是神器,对于封装代码颇有用,例如很常见的,textField限制长度.每一个都在delegate里重复代码确定不行.本身写个自定义textField,better,不过仍是有点麻烦.而runtime就能够很优雅地解决问题.
.h @interface UITextField (TextRange) @property (nonatomic, assign) NSInteger maxLength; //每次限制的长度设置下就好了 @end .m - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)setMaxLength:(NSInteger)maxLength { objc_setAssociatedObject(self, KTextFieldMaxLength, @(maxLength), OBJC_ASSOCIATION_RETAIN_NONATOMIC); [self textField_addTextDidChangeObserver]; } - (NSInteger)maxLength { return [objc_getAssociatedObject(self, KTextFieldMaxLength) integerValue]; } #pragma mark - Private method - (void)textField_addTextDidChangeObserver { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textField_textDidChange:) name:UITextFieldTextDidChangeNotification object:self]; } #pragma mark - NSNotificationCenter action - (void)textField_textDidChange:(NSNotification *)notification { UITextField *textField = notification.object; NSString *text = textField.text; MYTitleInfo titleInfo = [text getInfoWithMaxLength:self.maxLength]; if (titleInfo.length > self.maxLength) { UITextRange *selectedRange = [textField markedTextRange]; UITextPosition *position = [textField positionFromPosition:selectedRange.start offset:0]; if (!position) { UITextRange *textRange = textField.selectedTextRange; textField.text = [textField.text subStringWithMaxLength:self.maxLength]; textField.selectedTextRange = textRange; } } }
以上就是关于runtime最经常使用的介绍,我还在学习当中,会不停地完善,和你们分享进步.
最后给你们一个学习runtime的小技巧,毕竟看源码真的很枯燥,能够去github上输入import <objc/runtime.h>
,就能够看到用到runtime的实例,使学习更有目标和动力.