由 Runtime 所想到的

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>

// 定义关联的key
static 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_getAssociatedObject
objc_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

猜你喜欢

转载自blog.csdn.net/lwq718691587/article/details/52777479