结合源码谈谈 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 序列化了。




原创粉丝点击