iOS runtime

runtime是一套C语言API,是在程序运行时,负责将OC代码动态转化成C代码,其中最主要的是"消息机制", 我们知道C语言的函数调用,在编译时期就决定调用那个函数了,OC在编译时期并不能决定真正调用那个函数,只有在真正运行的时候才会根据函数名称找到对应函数来调用

头文件 #import <objc/message.h>, 一般不会直接导入<objc/runtime.h>

使用场景

动态交换两个方法的实现

当第三方框架或者原生方法不能满足需求的时候,我们可以在保持系统原有方法的功能的基础上,添加额外功能

需求: 加载图片直接使用[UIImage imageNamed:@"name"]是无法知道图片有没有加载成功的,给系统的imageNamed添加额外功能

  • 方案一:继承系统的类,重写方法(弊端:每次都需要导入)
  • 方案二:使用runtime,交换方法
    实现步骤:
  1. 给系统的方法添加分类
  2. 自己实现一个带有扩展功能的方法
  3. 交换方法,只需要交换一次
@implementation UIImage (imageName)


/**
 load方法: 把类加载进内存的时候调用, 只会调用一次
 */
+ (void)load {
    //1,先获取imageNamed方法地址
    // class_getClassMethod(获取某个类的方法)
    Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
    // 2. 获取自定义的my_imageNamed方法地址
    Method my_imageNamedMethod = class_getClassMethod(self, @selector(my_imageNamed:));
    // 3. 交换方法地址, 相当于交换实现
    method_exchangeImplementations(imageNamedMethod, my_imageNamedMethod);
}

//加载图片 + 判断是否加载成功
+ (UIImage *)my_imageNamed:(NSString *)name {
    UIImage *image = [UIImage my_imageNamed:name];
    if (image){
        NSLog(@"名字:%@ ---> 加载成功", name);
    }else{
       NSLog(@"名字:%@ ---> 加载失败", name);
    }
    
    return image;
}


@end

动态添加属性

原理:给一个类添加属性,其实本质就是给这个类添加关联, 并不是直接把这个值得内存空间添加到类的内存空间

@interface UIImage (imageName)

//@peoperty分类: 只会生成get, set方法声明, 不会生成实现,也不会生成下划线成员属性
@property (nonatomic, strong) NSString *name;

@end

- (void)setName:(NSString *)name {
    // objc_setAssociatedObject(将某个值跟某个对象关联起来,将某个值存储到某个对象中)
    // object:给哪个对象添加属性
    // key:属性名称
    // value:属性值
    // policy:保存策略
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
}

- (NSString *)name {
    return objc_getAssociatedObject(self, @"name");
}
实现字典转模型的自动转换

思路: 利用运行时,遍历模型中的所有属性, 根据模型的属性名,去字典中查找对应的值,给模型的属性赋值
注意:

  1. 当字典的key和模型的属性匹配不上
  2. 模型中嵌套模型
  3. 数组中装着模型
发送消息

objc_msgSend(obj,@selector(makeText));

动态添加方法
@implementation Person 
// 没有返回值,1个参数 
// void,(id,SEL) 
void aaa(id self, SEL _cmd, NSNumber *meter) {  
       NSLog(@"跑了%@米", meter); 
} 

// 任何方法默认都有两个隐式参数,self,_cmd(当前方法的方法编号) 
// 什么时候调用:只要一个对象调用了一个未实现的方法就会调用这个方法,进行处理 
// 作用:动态添加方法,处理未实现 
+ (BOOL)resolveInstanceMethod:(SEL)sel { 
    // [NSStringFromSelector(sel) isEqualToString:@"run"]; 
    if (sel == NSSelectorFromString(@"run:")) { 
         // 动态添加run方法 
         // class: 给哪个类添加方法 
         // SEL: 添加哪个方法,即添加方法的方法编号 
         // IMP: 方法实现 => 函数 => 函数入口 => 函数名(添加方法的函数实现(函数地址)) 
         // type: 方法类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd         class_addMethod(self, sel, (IMP)aaa, "v@:@"); 
         return YES; 
    } 
    return [super resolveInstanceMethod:sel]; 
} 
@end

什么是method swizzling(就是方法交换)

实现交换的几种方式

  1. 利用method_exchangeImplementations交换两个方法实现
  2. 利用class_replaceMethod替换方法的实现
  3. 利用method_setImplementation来直接设置某个方法的IMP
Q1 OBJC在向一个对象发送消息时,发生了什么?

A: 根据对象的isa指针找到类对象,查询类对象的methodlists方法列表, 如果没有找到,就沿着superClass,寻找父类, 在父类methodLists方法列表中查询,最终找到SEL,根据id和SEL确认IMP(方法实现),
A: 如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误

Q2 什么时候会报unrecognized selector错误? iOS有哪些机制避免走到这一步?

A1: 当发送消息时, 会根据类里面的methodLists方法列表查询要调用的方法, 当查询不到时,会一直沿着父类查询,若最终查询不到的时候会报unrecognized selector错误,当系统查询不到方法的时候,会调用+(BOOL)resolveInstanceMethod:(SEL)sel动态添加方法,若没有动态添加会使用转发-(id)forwardingTargetForSelector:(SEL)aSelector, 来保证不会崩溃

Q3: runtime如何实现weak变量的自动置nil?

A: runtime对注册的类, 会进行布局, 对于weak对象会放入一个hash表中, 用weak指向的对象内存地址作为key, 若这个对象的引用计数为0时, 就会用这个key在表中找到这个对象,并置为nil

runtime防止重复点击

思路:

  1. 创建UIButton分类
  2. 为分类添加属性(点击时间 和 点击时间间隔 两个时间)
  3. 利用runtime的method_exchangeImplementations交换方法, 交换的是UIButton的响应点击时间sendAction:to:forEvent:
  4. 自定义方法中判断, 如果当前时间 - 点击的时间 < 时间间隔就return
@interface UIButton (CS_FixMultiClick)
@property (nonatomic, assign) NSTimeInterval cs_acceptEventInterval; // 重复点击的间隔 
@property (nonatomic, assign) NSTimeInterval cs_acceptEventTime; 
@end
#import "UIControl+CS_FixMultiClick.h" 
#import <objc/runtime.h> 
@implementation UIButton (CS_FixMultiClick) 
// 因category不能添加属性,只能通过关联对象的方式。 
static const char *UIControl_acceptEventInterval = "UIControl_acceptEventInterval"; 
- (NSTimeInterval)cs_acceptEventInterval { 
    return [objc_getAssociatedObject(self, UIControl_acceptEventInterval) doubleValue]; 
    } 
- (void)setCs_acceptEventInterval:(NSTimeInterval)cs_acceptEventInterval {     objc_setAssociatedObject(self, UIControl_acceptEventInterval, @(cs_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}
static const char *UIControl_acceptEventTime = "UIControl_acceptEventTime";
- (NSTimeInterval)cs_acceptEventTime { 
    return [objc_getAssociatedObject(self, UIControl_acceptEventTime) doubleValue]; 
    } 
    
- (void)setCs_acceptEventTime:(NSTimeInterval)cs_acceptEventTime {    objc_setAssociatedObject(self, UIControl_acceptEventTime, @(cs_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
} 
// 在load时执行hook 
+ (void)load { 
    Method before = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:)); 
    Method after = class_getInstanceMethod(self, @selector(cs_sendAction:to:forEvent:)); 
    method_exchangeImplementations(before, after); 
} 

- (void)cs_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event { 
   if ([NSDate date].timeIntervalSince1970 - self.cs_acceptEventTime < self.cs_acceptEventInterval) { 
   return; 
   } 
   if (self.cs_acceptEventInterval > 0) { 
      self.cs_acceptEventTime = [NSDate date].timeIntervalSince1970; 
   } 
   [self cs_sendAction:action to:target forEvent:event]; 
   } 
@end

runtime实现空白页

交换UITableView, UICollectionView, UIWebView的reloadData方法, 加判断添加空白页

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

推荐阅读更多精彩内容