结合源码谈谈 runtime 特性的应用场景(持续更新中)
来源:互联网 发布:淘宝导航字体颜色代码 编辑:程序博客网 时间:2024/06/13 05:58
关于 runtime 主题的文章网上已经很多了,底部会给出一些参考链接,此文就不再展开了。
runtime 的源码可以从苹果开发者网站下载:下载链接
下面直接切入主题,看看常用的 runtime 特性的应用场景。
1.交换两个方法的实现,即 method swizzling(俗称黑魔法)
应用场景:
这个应该算是 runtime 最常见的应用,当第三方框架 或者 系统原生方法功能不能满足我们的时候,我们可以在保持系统原有方法功能的基础上,添加额外的功能。
实现方式:
1.利用 method_exchangeImplementations 交换两个方法的实现
2.利用 class_replaceMethod 替换方法的实现
3.利用 method_setImplementation 来直接设置某个方法的IMP
经典案例:
1.第三方开源代码 Aspects (方便 AOP 编程,主要通过将原方法实现替换为_objc_msgForward或_objc_msgForward_stret),但是和 JSPatch 一起使用会有一些冲突问题(详见:http://www.jianshu.com/p/dc1deaa1b28e)
2.第三方库 MagicRecord 如果定义了宏 MR_SHORTHAND ,那么在调用一些 CoreData 相关类的方法时不需要 MR_ 前缀了,是通过替换
resolveClassMethod: 与resolveInstanceMethod:做到的
代码案例:
+ (void)load{ Method m1; Method m2; m1 = class_getInstanceMethod(self, @selector(sofaViewDidAppear:)); m2 = class_getInstanceMethod(self, @selector(viewDidAppear:)); method_exchangeImplementations(m1, m2);}- (void)sofaViewDidAppear:(BOOL)animated{ if ([self.navigationController isKindOfClass:[SafePushNavigationController class]]) { ((SafePushNavigationController *)self.navigationController).transitionInProgress = NO; }; [self sofaViewDidAppear:animated];}
void replaceSelectorForTargetWithSourceImpAndSwizzle(Class sourceClass, SEL sourceSelector, Class targetClass, SEL targetSelector){ Method sourceClassMethod = class_getClassMethod(sourceClass, sourceSelector); Method targetClassMethod = class_getClassMethod(targetClass, targetSelector); Class targetMetaClass = objc_getMetaClass([NSStringFromClass(targetClass) cStringUsingEncoding:NSUTF8StringEncoding]); BOOL methodWasAdded = class_addMethod(targetMetaClass, sourceSelector, method_getImplementation(targetClassMethod), method_getTypeEncoding(targetClassMethod)); if (methodWasAdded) { class_replaceMethod(targetMetaClass, targetSelector, method_getImplementation(sourceClassMethod), method_getTypeEncoding(sourceClassMethod)); }}
+ (BOOL)copyMethod:(SEL)origSel_ toMethod:(SEL)dstSel_ error:(NSError *__autoreleasing *)error_ { Method origMethod = class_getInstanceMethod(self, origSel_); if (!origMethod) {#if TARGET_OS_IPHONE SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]);#else SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]);#endif return NO; } Method dstMethod = class_getInstanceMethod(self, dstSel_); if (!dstMethod) {#if TARGET_OS_IPHONE SetNSError(error_, @"destination method %@ not found for class %@", NSStringFromSelector(dstSel_), [self class]);#else SetNSError(error_, @"destination method %@ not found for class %@", NSStringFromSelector(dstSel_), [self className]);#endif return NO; } class_addMethod(self, dstSel_, class_getMethodImplementation(self, dstSel_), method_getTypeEncoding(dstMethod)); method_setImplementation(class_getInstanceMethod(self, dstSel_), class_getMethodImplementation(self, origSel_)); return YES;}+ (BOOL)copyClassMethod:(SEL)origSel_ toClassMethod:(SEL)dstSel_ error:(NSError *__autoreleasing *)error_ { return [GetClass((id)self) copyMethod:origSel_ toMethod:dstSel_ error:error_];}
2.动态添加方法
应用场景:
利用消息转发机制,可以在运行时第一次调用某些未实现的方法时,在resolveClassMethod: 与resolveInstanceMethod:方法中动态添加该方法的实现(有点懒加载的感觉)
经典案例:
1.Google 开源库 Protocol Buffer, 根据 proto 文件生成的 OC 文件 .h 中声明了所有属性的 property,但是 .m 中使用了 @dynamic 却没有实现 setter 和 getter 方法,但是基类 GPBMessage 中重写了resolveInstanceMethod 动态添加 setter 和 getter 方法。
2.第三方库 MagicRecord 如果定义了宏 MR_SHORTHAND ,那么在调用一些 CoreData 相关 MagicRecord 扩展的的方法时不需要 MR_ 前缀了,是通过替换
resolveClassMethod: 与resolveInstanceMethod:方法后在其中判断然后使用添加 MR_ 前缀后的方法。
代码案例:
#import "Foo.h" #include <objc/runtime.h> void dynamicMethodIMP(id self, SEL _cmd) { NSLog(@" >> dynamicMethodIMP"); } @implementation Foo -(void)Bar { NSLog(@" >> Bar() in Foo"); } + (BOOL)resolveInstanceMethod:(SEL)name { NSLog(@" >> Instance resolving %@", NSStringFromSelector(name)); if (name == @selector(MissMethod)) { class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:"); return YES; } return [super resolveInstanceMethod:name]; } @end
3.给已有的类动态添加属性
应用场景:
当我们不想通过继承给一个类添加属性,可以考虑使用创建分类,并声明 @property,然后在 setter 和 getter 方法中通过 runtime 的添加/获取关联属性达到目的(注意这种情况下的属性内存空间并不象iVar一样是在实例对象中)。
实现方式:
利用 void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) 和
id objc_getAssociatedObject(id object, const void *key) 方法
经典案例:
1.JSPatch 中给类添加属性是通过关联属性做到的
2.UIAlertView 可通过添加分类,添加 showWithBlock: 接口支持传入点击事件处理的 block,其中传入的 block 需要使用关联属性存取
代码案例:
#import "UIAlertView+LBXAlertAction.h"#import <objc/runtime.h>static char key;@implementation UIAlertView (LBXAlertAction)- (void(^)(NSInteger buttonIndex))block{ return objc_getAssociatedObject(self, &key);;}- (void)setBlock:(void(^)(NSInteger buttonIndex))block{ if (block) { objc_removeAssociatedObjects(self); objc_setAssociatedObject(self, &key, block, OBJC_ASSOCIATION_COPY); }}- (void)showWithBlock:(void(^)(NSInteger buttonIndex))block{ self.block = block; self.delegate = self; [self show];}- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{ if (self.block) { self.block(buttonIndex); } objc_removeAssociatedObjects(self);}@end
4.字典转模型
应用场景:
有些后台服务返回数据 或 Hybrid 开发时传参使用 json 字符串,需要转换成 OC 中 Model 对象。
实现方式:
1.利用运行时,遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值
2.遍历字典中的键值,然后查找 Model 是否有对应 key 类型为 value 的属性,有则给模型的属性赋值
代码案例:
详见 JSONModel、Mantle、MJExtension、YYModel 的源码(注意其中对[NSNull null]、嵌套Model、NSArray中为Model、字段需要换转处理、
可选字段支持、未知字段(向后兼容)等支持情况)
5.实现 NSCoding 的自动归档和解档
应用场景:
这个跟上面的使用场景很相似,避免每个 Model 需要手动写编解码方法。
实现方式:
利用运行时,遍历模型中所有属性,按序编解码
代码案例:
详见 Mantle、MJExtension、YYModel 的源码,注意 JSONModel 的实现是直接将 JSON string 序列化了。- 结合源码谈谈 runtime 特性的应用场景(持续更新中)
- 结合源码谈谈 RunLoop 的应用场景(持续更新中)
- 矩阵的应用(持续更新中)
- 测试设计中容易被忽略的场景(持续更新中)
- 你必须了解的c++的特性(持续更新中)
- OC的runtime(运行时)运行机制及应用场景
- 谈谈团队合作(持续更新)
- 结合源码谈谈Activity的exported属性
- 源码:我的关于NLP的博客(持续更新中...)
- Runtime常用的几个应用场景
- Runtime的应用场景,以及实现过程
- 树的应用小算法大全--持续更新中
- 计算机视觉源码收集(持续更新中)
- JQuery源码学习笔记(持续更新中)
- java集合框架源码学习目录(持续更新中)
- 【持续更新】C++里的一些特性
- golang的坑(持续更新中....)
- Gradle 的使用 (持续更新中)
- 5.20 一些快捷操作
- eagle pcb v8.2 便捷性大大提升
- [记录二]学习扩展卡尔曼滤波器
- Django 之 (5)Django 部署(Nginx)
- numpy的问题
- 结合源码谈谈 runtime 特性的应用场景(持续更新中)
- 通过PUTTY实现win向远程Linux(CentOS)传输文件
- java 网络
- MySQL索引
- java加密与解密的艺术(四)——散列函数(哈希函数)
- K-means++ 图像分割
- Java中Scanner的next()和nextLine()区别
- 如何配置jdk环境变量
- Unity3D PlayerPrefs 存取二进制数据