由 Runtime 所想到的

来源:互联网 发布:python 修改文件名称 编辑:程序博客网 时间:2024/06/02 17:31

Objective-C是一门动态语言,可以在运行的时候动态决定调用哪个方法实现,甚至增加、替换方法的具体实现,而这些都归功于Objective-C的运行时(runtime)系统。

一. Runtime简介

Runtime 又叫运行时,是一套底层的 C 语言 API,是 iOS 系统的核心之一。开发者在编码过程中,可以给任意一个对象发送消息,在编译阶段只是确定了要向接收者发送这条消息,而接受者将要如何响应和处理这条消息,那就要看运行时来决定了。

C语言中,在编译期,函数的调用就会决定调用哪个函数。
而OC的函数,属于动态调用过程,在编译期并不能决定真正调用哪个函数,只有在真正运行时才会根据函数的名称找到对应的函数来调用。

Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。

先说一下isa指针

要认识什么是isa指针,我们得先明确一点:

在Objective-C中,任何类的定义都是对象。类和类的实例(对象)没有任何本质上的区别。任何对象都有isa指针。

那么什么是类呢?在xcode中用快捷键Shift+Cmd+O 打开文件objc.h 能看到类的定义:

#if !OBJC_TYPES_DEFINED/// An opaque type that represents an Objective-C class.typedef struct objc_class *Class;/// Represents an instance of a class.struct objc_object {    Class isa  OBJC_ISA_AVAILABILITY;};/// A pointer to an instance of a class.typedef struct objc_object *id;#endif/// An opaque type that represents a method selector.typedef struct objc_selector *SEL;

可以看出:

Class 是一个 objc_class 结构类型的指针, id是一个 objc_object 结构类型的指针.

我们再来看看 objc_class 的定义:

struct objc_class {    Class isa  OBJC_ISA_AVAILABILITY;#if !__OBJC2__    Class super_class                                        OBJC2_UNAVAILABLE;    const char *name                                         OBJC2_UNAVAILABLE;    long version                                             OBJC2_UNAVAILABLE;    long info                                                OBJC2_UNAVAILABLE;    long instance_size                                       OBJC2_UNAVAILABLE;    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;#endif} OBJC2_UNAVAILABLE;/* Use `Class` instead of `struct objc_class *` */

稍微解释一下各个参数的意思:

isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。

super_class:父类,如果该类已经是最顶层的根类,那么它为NULL。

version:类的版本信息,默认为0

info:供运行期使用的一些位标识。

instance_size:该类的实例变量大小

ivars:成员变量的数组

再来看看各个类实例变量的继承关系:

这里写图片描述

每一个对象本质上都是一个类的实例。其中类定义了成员变量和成员方法的列表。对象通过对象的isa指针指向类。

每一个类本质上都是一个对象,类其实是元类(meteClass)的实例。元类定义了类方法的列表。类通过类的isa指针指向元类。

所有的元类最终继承一个根元类,根元类isa指针指向本身,形成一个封闭的内循环。


OC的动态特性包含了以下三点:

1.动态类型

即运行时在决定对象的类型。 ==-isMemberOfClass: isKindOfClass:== 这两个方法 用于判断类或实例变量是属于哪个类。

2.动态绑定

基于动态类型,在某个实例对象被确定后,其类型便被确定了。该对象对应的属性和响应的消息也被完全确定,这就是动态绑定。在继续之前,需要明确 Objective-C中消息的概念。由于OC的动态特性,在OC中其实很少提及“函数”的概念,传统的函数一般在编译时就已经把参数信息和函数实现打包 到编译后的源码中了,而在OC中最常使用的是消息机制。调用一个实例的方法,所做的是向该实例的指针发送消息,实例在收到消息后,从自身的实现中寻找响应 这条消息的方法。动态绑定所做的,即是在实例所属类确定后,将某些属性和相应的方法绑定到实例上。这里所指的属性和方法当然包括了原来没有在类中实现的,而是在运行 时才需要的新加入的实现。

3、动态加载

根据需求加载所需要的资源,这点很容易理解,对于iOS开发来说,基本就是根据不同的机型做适配。最经典的例子就是在Retina设备上加载@2x 的图片,而在老一些的普通屏设备上加载原图。随着Retina iPad的推出,和之后可能的Retina Mac的出现,这个特性相信会被越来越多地使用。


runtime - 方法调用的实质

当我们写下一行代码

[obj doSth];

,在编译时,编译器会将我们的代码转化为

objc_msgSend(obj,@selector(doSth));

objc_msgSend()方法实现了函数查找和匹配,下面是它的原理: 根据对象obj找到对象类中存储的函数列表methodLists。 再根据SEL@selector(doSth)在methodLists中查找对应的函数指针method_imp。 根据函数指针method_imp调用响应的函数。

发送消息的步骤 (方法执行)

1.检测selector是否要执行,比如有了垃圾回收机制就不需要retain和release等方法选择器 。

2.检测targer是否为nil,Objective-C允许我们队一个nil对象执行方法,然后忽略掉。

3.上面都执行成功了,就开始查找这个方式实现的IMP,先从==Cache==中查找,如果找到了就执行。(Cache为方法调用的性能进行优化,也就是在方法调用的时候会首先从这个缓存列表中找,如果找到就执行,没有找到就通过isa指针在类的方法列表中寻找,找到之后执行并把这个方法添加到这个缓存列表中,以供下一次调用,当下一次调用的时候直接从缓存列表中找,这样就提高了的性能,不用通过isa指针去查找。

4.如果找不到就在类的方法列表中去查找

5.如果在类的方法列表中找不到就去父类的方法列表中去查找,一直到NSObject为止。

6.如果还找不到,当runtime系统在Cache和方法分发列表中找不到要执行的方法的时候,runtime会调用

+(BOOL)resolveInstanceMethod:(SEL)sel (实例方法)+(BOOL)resolveClassMethod:(SEL)sel (类方法)

这两个方法给我们一次动态添加方法的机会。我们可以用

class_addMethod

向特定类或类实例添加方法的功能。
例子:

void eat (id self,SEL sel){    NSLog(@"%@  %@",self,NSStringFromSelector(sel));}// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.// 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法+(BOOL)resolveInstanceMethod:(SEL)sel{    if (sel == @selector(eat)) {        // 第一个参数:给哪个类添加方法        // 第二个参数:添加方法的方法编号        // 第三个参数:添加方法的函数实现(函数地址)        // 第四个参数:函数的类型,(返回值+参数类型)          // v:void         // @:对象->self        // : 表示SEL->_cmd         class_addMethod(self,sel, eat, "v@:");    }    return  [super resolveInstanceMethod:sel];}详细解释  class_addMethod 的最后一个参数例子-(int)say:(NSString *)str;相应的实现函数就应该是这样:int say(id self, SEL sel, NSString *str) {     NSLog(@"%@", str);     return 100;//随便返回个值  } class_addMethod这句就应该这么写:class_addMethod([EmptyClass class], @selector(say:), (IMP)say, "i@:@");其中types参数为"i@:@“,按顺序分别表示:i 表示   方法 say 中的返回值@ 表示   方法 say 中的第一个参数 self  : 表示   方法 say 中的第二个参数 sel  @ 表示   方法 say 中的第三个参数 str

这些表示方法都是定义好的(Type Encodings),关于Type Encodings的其他类型定义请参考官方文档

7.如果上面的resolveInstanceMethod:或者resolveClassMethod:返回NO,消息转发就会进入消息转发机制,但是runtime又给我们一次机会再次修改接受者的机会,即当前的接受者不能收到这个消息,我们通过重载

- (id)forwardingTargetForSelector:(SEL)aSelector

这个消息转发给其他能接受消息的对象。

- (id)forwardingTargetForSelector:(SEL)aSelector{    if (aSelector == @selector(weight)) {        People *people = [People new];        return people;    }    return [super forwardingTargetForSelector:aSelector];}

参考 iOS开发之runtime详解


runtime - 给类别添加属性 - 动态添加属性

#import "LQPerson.h"@interface LQPerson (nameProperty)@property (copy, nonatomic) NSString *name;@end
#import "LQPerson+nameProperty.h"#import <objc/runtime.h>// 定义关联的keystatic const char *key = "name";@implementation LQPerson (nameProperty)-(NSString *)name{    return objc_getAssociatedObject(self, key);}-(void)setName:(NSString *)name{ /*    OBJC_ASSOCIATION_ASSIGN;            //assign策略    OBJC_ASSOCIATION_COPY_NONATOMIC;    //copy策略    OBJC_ASSOCIATION_RETAIN_NONATOMIC;  // retain策略    OBJC_ASSOCIATION_RETAIN;    OBJC_ASSOCIATION_COPY;     */     /*     * id object 给哪个对象的属性赋值       const void *key 属性对应的key       id value  设置属性值为value       objc_AssociationPolicy policy  使用的策略,是一个枚举值,和copy,retain,assign是一样的,手机开发一般都选择NONATOMIC          objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);     */    objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_COPY_NONATOMIC);}@end

主要用到 runtime 中的

objc_getAssociatedObjectobjc_setAssociatedObject

这个两个方法


runtime - 方法交换 - 给系统中的方法添加额外的实现

#import "UIImage+LogImageName.h"#import <objc/runtime.h>@implementation UIImage (LogImageName)+(void)load{    Method imageName = class_getClassMethod(self, @selector(imageNamed:));    Method imageName_log = class_getClassMethod(self, @selector(imageName_log:));    method_exchangeImplementations(imageName_log, imageName);}+(instancetype)imageName_log:(NSString*)name{    //这里调用 imageName_log 相当于 imageNamed    UIImage * image = [self imageName_log:name];    if (image == nil) {        NSLog(@"***************runTime没有该图片***************");    }    return image;}@end

这里要说一下

+ (void)load//只要在工程中创建了 这个类 就会调用+ (void)initialize//创建时不会调用 只有在第一次使用时才会被调用**这俩个方法 都只会别调用一次**

runtime - 动态的遍历一个类的所有成员变量,用于字典转模型,归档解档操作

Ivar *ivars = class_copyIvarList([BDPerson class], &count);//获得一个指向该类成员变量的指针    for (int i =0; i < count; i ++) {        //获得Ivar        Ivar ivar = ivars[i];        //根据ivar获得其成员变量的名称--->C语言的字符串        const char *name = ivar_getName(ivar);          NSString *key = [NSString stringWithUTF8String:name];        NSLog(@"%d----%@",i,key);}

参考博客:
http://ios.jobbole.com/89209/
http://www.jianshu.com/p/41735c66dccb

0 0
原创粉丝点击