RunTime的应用和理解

runtime的理解和应用

项目中经常会有一些的功能模块用到runtime,最近也在学习它.对于要不要阅读runtime的源码,我觉得仅仅是处理正常的开发,那真的没有必要,只要把常用的一些函数看下和原理理解下就可以了.但是如果真能静下心好好阅读源码,真的能帮你更加深入理解objc本身以及经过高阶包装出来的那些特性。

什么是runtimeruntime就是运行时,每个语言都有它的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,就可以看到用到runtime的实例,使学习更有目标和动力.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,690评论 0 9
  • 项目中经常会有一些的功能模块用到runtime,最近也在学习它.对于要不要阅读runtime的源码,我觉得仅...
    A_sura阅读 506评论 1 1
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,186评论 0 7
  • 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的...
    lylaut阅读 795评论 0 4
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 729评论 0 2