结合源码谈谈 runtime 特性的应用场景(持续更新中)
2017-05-20 16:55
561 查看
关于 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:做到的
代码案例:
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_ 前缀后的方法。
代码案例:
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 需要使用关联属性存取
代码案例:
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 的源码可以从苹果开发者网站下载:下载链接
下面直接切入主题,看看常用的 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 序列化了。
相关文章推荐
- 结合源码谈谈 RunLoop 的应用场景(持续更新中)
- (源码)Silverlight DataGrid 应用,Demo更新至1.2版本!!!!!!!!!!!!!
- jquery的应用(持续更新……)
- 搜应用网调查:2011~2012互联网电子商务IT业裁员倒闭年[持续更新]
- 树的应用小算法大全--持续更新中
- jQuery1.6.1源码分析系列(持续更新)
- 应用框架的设计与实现学习手札系列(持续更新)
- Android 源码编译下一些问题总结(持续更新)
- php源码函数汇总(持续更新)
- Knockout应用开发指南 持续更新中
- 对比主流NoSQL数据库特性与应用场景
- JQuery $ 应用手记 (持续更新中)
- Silverlight实用窍门系列:24.Silverlight多线程技术BackgroundWorker的应用,更新ProgressBar控件【附带源码实例】
- Silverlight实用窍门系列:23.Silverlight多线程技术Thread的应用,后台线程更新UI控件,向多线程传递参数【附带源码实例】
- jQuery1.6.1源码分析系列(持续更新)
- (源码)Silverlight DataGrid 应用,Demo更新至1.2版本!!!!!!!!!!!!!
- Lua源码分析(原创)[持续更新中]
- 【持续更新】Android 源码下载地点
- 对比主流NoSQL数据库特性与应用场景
- [源码、文档、分享] iOS/iPhone学习系列、代码教程----~~~持续更新中~~~