项目中经常会有一些的功能模块用到runtime,最近也在学习它.对于要不要阅读runtime的源码,我觉得仅仅是处理正常的开发,那真的没有必要,只要把常用的一些函数看下和原理理解下就可以了.

但是如果真能静下心好好阅读源码,真的能帮你更加深入理解objc本身以及经过高阶包装出来的那些特性。

什么是runtime

runtime就是运行时,每个语言都有它的runtime.通俗点讲就是程序运行时发生的事情.

比如C语言,在编译的时候就决定了调用哪些函数,通过编译后就一步步执行下去,没有任何二义性,所以它是静态语言.

而objc的函数调用则可以理解为发消息,在编译的时候完全不能决定哪个函数执行,只有在运行的时候才会根据函数名找到函数调用,所以在运行的时候它能动态地添加调换属性,函数.所以它是动态语言.

动态和静态语言没有明显的界限,我感觉它们就是以runtime来区分的,看它在runtime时,有多灵活,那么它就有多动态.

  • 相关定义
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可以找到函数的实现

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可以理解为存最近调用过的方法的数组,每次调用先访问它,提高效率

runtime常用方法

  • 获取列表

    我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)
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的实例,使学习更有目标和动力.

iOS runtime的理解和应用的更多相关文章

  1. iOS runtime探究(二): 从runtime開始深入理解OC消息转发机制

    你要知道的runtime都在这里 转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639289 本文主要解说runtime相关知识, ...

  2. iOS --runtime理解

    iOS~runtime理解 Runtime是想要做好iOS开发,或者说是真正的深刻的掌握OC这门语言所必需理解的东西.最近在学习Runtime,有自己的一些心得,整理如下,一为 查阅方便二为 或许能给 ...

  3. iOS Runtime的消息转发机制

    前面我们已经讲解Runtime的基本概念和基本使用,如果大家对Runtime机制不是很了解,可以先看一下以前的博客,会对理解这篇博客有所帮助!!! Runtime基本概念:https://www.cn ...

  4. ios runtime swizzle

    ios runtime swizzle @implementation NSObject(Extension) + (void)swizzleClassMethod:(Class)class orig ...

  5. ios runtime的相关知识

    一.iOS runtime原理 对于runtime机制,在网上找到的资料大概就是怎么去用这些东西,以及查看runtime.h头文件中的实现,当然这确实是一种很好的学习方法,但是,其实我们还是不会知道r ...

  6. iOS Runtime 实践(1)

    很多时候我们都在看iOS开发中的黑魔法——Runtime.懂很多,但如何实践却少有人提及.本文便是iOS Runtime的实践第一篇. WebView 我们这次的实践主题,是使用针对接口编程的方式,借 ...

  7. 包建强的培训课程(11):iOS Runtime实战

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...

  8. iOS Runtime 实操练习

    iOS  Runtime 知识详解: http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/ 一般可以运行Runtime进行以下操作 ...

  9. iOS开发SDWebImageOptions理解

    iOS开发SDWebImageOptions理解 原文 http://www.cnblogs.com/WJJ-Dream/p/5816750.html typedef NS_OPTIONS(NSUIn ...

随机推荐

  1. Azure上七层负载均衡APP Gateway

    Azure的SLB和ILB是最常用的4层负载均衡工具.但有些场景是7层的负载均衡,SLB和ILB就无能为力了. Azure上已经推出了APP Gateway的服务,就是7层负载均衡的负载均衡器. 如上 ...

  2. 基于jQuery的一个简单的图片查看器

    项目中自己diy了一个图片查看器.因为初始代码不是自己的,只是在上面改了一下也没有弄的很漂亮.等以后有时间了在重写一下样式和封装,作为备用的只是积累吧.如果有童鞋有用到,完全可以在此基础上改,比较容易 ...

  3. HTML5实现3D和2D可视化QuadTree四叉树碰撞检测

    QuadTree四叉树顾名思义就是树状的数据结构,其每个节点有四个孩子节点,可将二维平面递归分割子区域.QuadTree常用于空间数据库索引,3D的椎体可见区域裁剪,甚至图片分析处理,我们今天介绍的是 ...

  4. SQL--存储过程

    声明和调用有返回值的存储过程 分页存储过程 转账的存储过程:

  5. 30天C#基础巩固----Lambda表达式

         这几天有点不在状态,每一次自己很想认真的学习,写点东西的时候都会被各种小事情耽误,执行力太差.所以自己反思了下最近的学习情况,对于基础的知识,可以从书中和视频中学习到,自己还是需要注意下关于 ...

  6. HashSet 与TreeSet和LinkedHashSet的区别

    Set接口      Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false.      Set判断两个对象相同不是使用==运算符,而是根据equals方法.也就 ...

  7. Mailbox unavailable. The server response was: 5.1.1 User unknown

    昨晚至今早,在新的项目中,实现一个小功能,就是当有访问者浏览网页在留言簿留言时,系统把留言内容发送至某一个邮箱或是抄送指定的邮箱中. 使用以前能正常发送邮件的代码,但在新项目中,测试时,就是出现标题的 ...

  8. 把DataTable转换为泛型List<T>或是JSON

    在开发ASP.NET Web API或ASP.NET MVC时,我们从数据库得到的数据往往是DataSet或是DataTable.为了能让前端JQuery能方便使用至这些数据,我们需要把这些数据转换为 ...

  9. 温故而知新--sql存储过程复习

    存储过程是已编译好的T-SQL语句的集合,可以随时调用,速度快,不易出错. 可以传递参数,普通参数和输出参数(output) 实例1 create proc Newpro @testVarA int, ...

  10. C# 通过GPS坐标,计算两点之间距离

    之前在网上有很多这种计算的,但是代码都不怎么全.经过多方打听查询.找到完整代码.现将代码共享给大家. 有需要者觉得有用者欢迎使用.觉得用或简单的高手,请绕. public static double ...