Objective-C的Runtime机制的应用示例总结

来源:互联网 发布:JavaScript什么是闭包 编辑:程序博客网 时间:2024/04/30 13:21

Objective-C是一门动态语言,不同于许多静态语言,例如C语言,只能在编译和链接阶段把程序运行的上下文做好,在运行期间,无法修改,缺少动态性。Objective-C的动态性,给开发者提供了一种在运行期,修改程序执行流程的机会,这要归功于其强大的Runtime机制。
这篇文章主要介绍,目前,Runtime机制在我们项目中的应用场景。

  • 前言

ObjC语言中,runtime运行机制主要依赖于两个头文件

#include <objc/runtime.h>#include <objc/message.h>

其中runtime.h声明了一些类,实例变量操作相关的东西,message.h声明了一些消息发送相关的东西。
下面列举一下,runtime.h中常用的方法,以及它们的用法。

object_getClass:    获取一个对象所属的类object_getIvar:     读取一个对象中某个变量的值objc_getClass:      通过字符串获取到类Classclass_getSuperclass:获取一个类的父类class_getInstanceMethod:获取实例方法,返回Methodclass_getMethodImplementation:获取实例方法的实现class_getProperty:      获取某个类的属性class_addMethod:        增加方法。...等等

上面大部分列举了get相关的方法。runtime.h提供了获取(get),设置(set)(类的属性,实例变量,实例方法,类方法)的操作。

下面列举一下message.h的相关方法。

objc_msgSend:       给对象发送消息。objc_msgSendSuper:  向父类的发送消息

message.h类,主要提供了这两个关键的方法。

  • 下面总结一下Runtime有哪些使用场景,并通过具体的代码,来说明各种使用场景如何运用Runtime。

总结了如下四种应用场景:

 1.动态拼接URL 2.交换两个方法的实现(用自己的方法,替换系统的方法) 3.给系统已有的方法添加新的功能(不影响系统方法原来的功能) 4.消息转发中Runtime的应用

下面结合项目中使用到Runtime的地方进行说明,并列举了一些代码示例。

  • 第一个使用场景:动态拼接URL

    在做app开发的时候,肯定会遇到http请求,URL拼接的问题。初期大部分采取的方案是,采用NSString的格式化方法,自己手动去拼接URL. 例如:

    NSMutableString *stringUrl = [NSMutableString stringWithFormat:@"http:mytest/host/code=%@",code];    [stringUrl appendString:@"&uin=45"];    [stringUrl appendString:@"&my=5"];    [stringUrl appendString:@"&your=6"];

这样写有几个问题:
1. 手动书写,很容易写错。
2. 如果很多类似的请求,都需要uin或者其他通用的参数,那么就会每个请求都需要写一遍。不满足OOP的特性。
3. 参数所代表的含义不够清晰,需要开发去猜测。
解决方法:
把url拼接,抽象成一个类。
上面的参数code, uin,my,your可以当成类的属性。通用的属性有uin,可以抽象出一个基类。实现方法如下:
CURLParamBase类

@interface CURLParamBase : NSObject@property (nonatomic, strong)       NSString        *uin;@end

需要构造的请求类XXX,如下定义:

@interface CURLParamXXX : CURLParamBase@property (nonatomic, strong)       NSString*   my;@property (nonatomic, strong)       NSString*   your;@end

使用:定义CURLParamXXX的类的实例,使用Runtime来获取属性名和属性的value来拼接参数,如下:

     unsigned int propertyCount;     //获取所有属性     objc_property_t *properties =        class_copyPropertyList(class, &propertyCount);     for (unsigned int i = 0; i < propertyCount; i++)     {          objc_property_t property = properties[i];          const char *propertyName = property_getName(property);//获取实例变量,某个属性的值       object_getInstanceVariable(self,propertyName,&value);     }

-第二个使用场景:交换两个方法的实现

使用newSEL方法名,新的newIMP实现,替换原有的origSEL方法及其实现。
实现代码如下,代码中添加了相关的注释。

/** **新方法的实现替换旧方法 **成功:返回YES, **失败:返回NO. */BOOL replaceMethodNewImpl(Class c, SEL origSEL, SEL newSEL, IMP newIMP){    //新方法已经存在    if ([c instancesRespondToSelector:newSEL])    {        return YES;    }    //旧方法    Method origMethod = class_getInstanceMethod(c, origSEL);    //先把新方法加入到class的方法列表中    if (!class_addMethod(c, newSEL, newIMP, method_getTypeEncoding(origMethod)))    {        //如果加入失败,直接返回        NSLog(@"Failed to add method:%@ on %@",NSStringFromSelector(newSEL),c);        return NO;    }    else    {        //如果加入成功        Method newMethod = class_getInstanceMethod(c, newSEL);        //给original添加新的实现        //有可能失败失败原因:(for example, the class already contains a method implementation with that name).        if (class_addMethod(c, origSEL, method_getImplementation(newMethod), method_getTypeEncoding(origMethod)))        {            //新方法的实现替换成旧方法的实现。            class_replaceMethod(c, newSEL, method_getImplementation(origMethod), method_getTypeEncoding(newMethod));        }        else        {            //交换实现            method_exchangeImplementations(origMethod, newMethod);        }    }    return YES;}
  • 第三个使用场景:给系统已有的方法添加功能
    例如给系统的UIView下的方法drawRect,添加NSLog的功能,这个可以参考上面说明的第三种使用场景。变化的只是,新方法的实现要去调用老的方法的实现。在override_drawRect方法中,调用drawRect方法。代码如下:
 - (void)override_drawRect:(CGRect)r{    // 调用旧的实现。因为它们已经被替换了    [self override_drawRect: r];    NSLog(@"rect = %@",NSStringFromCGRect(r));}
  • 第四使用场景:消息转发
    开发过程中,经常遇到unrecognized selector sent to instance 0x87 Terminating app due to uncaught exception
    NSInvalidArgumentException’, 这个问题。
    这是什么原因?直观上看,是系统没有处理某个消息。
    情况分两种,第一,接受消息的对象错了。第二,对象没错,发送的消息不对。
    消息转发的整个流程如下图所示:
    消息转发过程
    总体来说,就是给某个实例,发送某个消息。
    首先如果没找到响应方法,系统会给你转到其他方法的机会,只要实现了resolveInstanceMethod方法即可。
    其次,如果没有实现,你还可以修改接受消息的对象,让其他对象去响应消息,覆盖方法forwardingTargetSelector即可。
    如果这两者都没做,你还可以在forwardInvocation,做自己的逻辑处理,是否继续处理消息。
    这篇文章不具体讨论这个转发流程的细节,只是为了说明runtime在整个过程中的运用。
    Runtime在其中的运用。
    在resolveInstanceMethod方法中,可以通过覆盖resolveInstanceMethod方法,如下:
+ (BOOL)resolveInstanceMethod:(SEL)sel {    NSString *selectorString = NSStringFromSelector(sel);    if ([selectorString hasPrefix:@"set"]) {        class_addMethod(self, sel, (IMP)mySetMethod, "v@:@");    } else {        class_addMethod(self, sel, (IMP)myGetMethod, "@@:");    }    return YES;}

代码中,本类实现了动态的添加选择子,把选择子关联到自己定义的方法上,这样在访问某个属性的时候,会动态的访问我们自己定义的方法。在消息转发的流程中,实现了动态添加方法实现的能力。

综上所述,Runtime提供了Objective-C强大的动态性,可谓是方便灵活,运用起来能做很多事情,也欢迎大家补充说明在你们的项目中,Runtime都为你们做了什么。

(完)

0 0