1.给分类动态添加属性

FDFullscreenPopGesture中给UIViewController的分类里有这么一个属性:

@property (nonatomic, copy) _FDViewControllerWillAppearInjectBlock fd_willAppearInjectBlock;

这是一个block的属性,block定义如下:

typedef void (^_FDViewControllerWillAppearInjectBlock)(UIViewController *viewController, BOOL animated);

看到这里也许你会提问,OC中不是不能给分类添加属性么?正常情况下,OC是不允许给OC添加属性的。但是利用Runtime的特性,这是可以办到的。实现方法如下:

- (_FDViewControllerWillAppearInjectBlock)fd_willAppearInjectBlock
{
return objc_getAssociatedObject(self, _cmd);// 根据关联的key,获取关联的值。这里的key等于_cmd,_cmd等于fd_willAppearInjectBlock
} - (void)setFd_willAppearInjectBlock:(_FDViewControllerWillAppearInjectBlock)block
{
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, @selector(fd_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);//关联对象
}

动态给分类添加属性的方法是:

objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

获取这个属性的方法是:

objc_getAssociatedObject(id object, const void *key)

还有一个方法是移除属性:

objc_removeAssociatedObjects(id object)

是的,这样就动态的给UIViewController的分类添加了fd_willAppearInjectBlock这么一个属性。

NOTE:在使用Runtime的这些方法的时候不要忘了导入objc/runtime.h这个头文件哦!

2.动态添加方法

要想动态添加方法我们必须了解方法是如何执行的,通常我们调用方法是通过[object message]这种方法,除了这种方法还有一种是比较少用的,就是[object performSelector:@selector(message)]这种方式。通过下面这张图我们可以了解一下他们对消息的处理的不同之处。

iOS消息转发.png

通过上图,我们可以得知,要想动态添加方法必须是通过[object performSelector:@selector(message)]这种方式调用方法才能在运行时阶段通过Runtime的一些方法达到动态的添加方法。如果现在有一个Person类,在其它地方通过performSelector的方式调用Personrun方法。但是Person类中并没有实现这个方法。

Person p = [Person alloc] init];
// 这个时候即使Person类没有实现run方法编译器也不会报错
[p performSelector:@selector(run)];

这时候只需要在Person中实现resolveInstanceMethod:方法就可以达到动态添加方法的目的。

//首先我们要在Person类里面实现我们要动态添加的方法
// 要注意,默认方法都有两个隐式参数
void run(id self,SEL sel){
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
// 刚好可以用来判断未实现的方法是不是我们想要动态添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
//先判断一下传过来的是不是run方法
if (sel == @selector(run)){
//如果是run方法就动态添加run方法
class_addMethod(self.class, @selector(run),(IMP)run, "v@:");
// 第一个参数:给哪个类添加方法
// 第二个参数:添加方法的方法编号
// 第三个参数:添加方法的函数实现(函数地址),如果是OC方法
//可以用+(IMP)instanceMethodForSelector:(SEL)aSelector;获得方法的实现。
// 第四个参数:方法的签名,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
}
}

这样就达到了给一个类动态添加方法的效果了,如果想把方法转发给其他的类实现,需要处理消息转发的第二或第三个函数了。

3.替换系统自带的方法

当一些时候,系统自带效果满足不了我们的时候,要么我们自定义,要么直接替换系统的方法。在公有的API是没有方法办到的。我们来看一段FDFullscreenPopGesture的代码(注释是我加的):

+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class]; //获取系统方法的SEL
SEL originalSelector = @selector(viewWillAppear:);
//获取替换方法的SEL
SEL swizzledSelector = @selector(fd_viewWillAppear:);
//为了获取IMP指针,获得方法的Method
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//为了安全起见,先判断是否已经存在要交换的方法
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
} - (void)fd_viewWillAppear:(BOOL)animated
{
//不要认为这句代码有错,其实很好理解,在调用这句的时候方法已经交换了
// Forward to primary implementation.
[self fd_viewWillAppear:animated]; if (self.fd_willAppearInjectBlock) {
self.fd_willAppearInjectBlock(self, animated);
}
}

通过上面的代码我们可以看出来,替换系统自带的方式实现需要用到的重要方法是method_exchangeImplementations()方法,并且要注意替换方法里面对自己的调用。这个方法也就是人们常说的Method Swizzling黑魔法,用的时候要注意,这是一把双刃剑!

结尾

Runtime在项目中很少用,但是要理解它,理解了之后用起来也不危险。如果你喜欢我的文章,不妨扫一扫下面的二维码请我喝杯茶。祝大家在iOS开发的道路上玩得愉快!

Runtime常用的几个场景的更多相关文章

  1. Runtime常用的几个应用场景

    Runtime常见的几个应用场景. Runtime常见应用场景 具体应用拦截系统自带的方法调用(Method Swizzling黑魔法) 实现给分类增加属性 实现字典的模型和自动转换 JSPatch替 ...

  2. iOS开发之Runtime常用示例总结

    经常有小伙伴私下在Q上问一些关于Runtime的东西,问我有没有Runtime的相关博客,之前还真没正儿八经的总结过.之前只是在解析第三方框架源码时,聊过一些用法,也就是这些第三方框架中用到的Runt ...

  3. Java 常用List集合使用场景分析

    Java 常用List集合使用场景分析 过年前的最后一篇,本章通过介绍ArrayList,LinkedList,Vector,CopyOnWriteArrayList 底层实现原理和四个集合的区别.让 ...

  4. redis五种数据类型和常用命令及适用场景

    一.redis的5种数据类型: 1.基础理解: string 字符串(可以为整形.浮点型和字符串,统称为元素) list 列表(实现队列,元素不唯一,先入先出原则) set 集合(各不相同的元素) h ...

  5. jedis实现操纵redis的常用api及使用场景

    简单记录一下,和描述一下常用的业务场景.好记性不如烂笔头. pom.xml <!--整合redis--> <dependency> <groupId>redis.c ...

  6. php数组去重、魔术方法、redis常用数据结构及应用场景

    一.用函数对数组进行去重的方法 1.arrau_unique函数的作用 移除数组中重复的值. 将值作为字符串进行排序,然后保留每个值第一次出现的健名,健名保留不变. 第二个参数可以选择排序方式: SO ...

  7. Redis常用数据类型及使用场景

    Redis最为常用的数据类型 字符串(String) 字符串列表(list) 字符串集合(set) 哈希(hash) 有序的字符串集合(sorted set) String(字符串) 字符串是最基本的 ...

  8. Redis数据类型的常用API以及使用场景

    一.通用命令 1.keys  遍历出所有的key 一般不在生产环境使用 2.dbsize key的总数 3.exists key 4.del key  删除指定key-value 5.expire k ...

  9. Unity学习笔记(3):一些常用API和应用场景

    Mathf.Lerp(float a,float b,float t)插值函数,当a < b时往a中插入t,以此来实现颜色,声音等渐变效果. GameObject.FindWithTag(str ...

随机推荐

  1. django APPEND_SLASH

    #设置项是否开启URL访问地址后面不为/跳转至带有/的路径APPEND_SLASH=True

  2. Hash算法总结(转)

    1. Hash是什么,它的作用先举个例子.我们每个活在世上的人,为了能够参与各种社会活动,都需要一个用于识别自己的标志.也许你觉得名字或是身份证就足以代表你这个人,但是这种代表性非常脆弱,因为重名的人 ...

  3. logstash 切分tomcat日志

    以下配置是logstash切分tomcat catalina.out日志. http://grok.qiexun.net/  分割时先用这个网站测试下语句对不对,能不能按需切割日志. input { ...

  4. EMF32名词解释

    (EFM32)32位节能微控制器(Energy Friendly Microcontroller 32-bit) (Gecko)壁虎 (Starter Kit)入门套件 (STK)入门套件 (Debu ...

  5. 三个猜数字游戏代码(Python)

    def binary_search(list,item): low = 0 high = len(list)-1 while low <= high: mid = (low + high)//2 ...

  6. Android Spannable为同一TextView设直不同样式

    /** * UNICODE * <p> * 偶尔吃(1-2次/周) ( 中文破弧 * 经常吃(3-5次/周) ( 英文破弧 * * @param name * @return */ pri ...

  7. python中re正则表达式

    1.re匹配的语法 re.math 从头开始匹配,没有匹配到返回None re.seach 匹配包含,,没有匹配到返回None re.findall 把所有匹配到的字符,以列表的形式返回,没有匹配到返 ...

  8. python中logging模块

    1. 日志的等级 DEBUG.INFO.NOTICE.WARNING.ERROR.CRITICAL.ALERT.EMERGENCY 级别 何时使用 DEBUG 详细信息,典型地调试问题时会感兴趣. 详 ...

  9. C# 乐观锁、悲观锁、共享锁、排它锁、互斥锁

    悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁.传统的关系型数据 ...

  10. 9. maps

    C++有vertor,java有HashMap,C语言想使用则需要自行封装,不同的类型还需要再封装,特别麻烦. 看看Go语言的map的使用方法:var member map[string]int,创建 ...