您的位置:首页 > 其它

结合源码谈谈 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:做到的
   代码案例:

+ (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 序列化了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: