netfoucs.com核心网络

iOS_运行时runtime

最终效果图:


打开XCode帮助文档,搜索Objective-C Runtime

找到结果共6章,如下所示:

This document has the following chapters:

  • “Runtime Versions and Platforms”
  • “Interacting with the Runtime”
  • “Messaging”
  • “Dynamic Method Resolution”
  • “Message Forwarding”
  • “Type Encodings”
  • “Declared Properties”

OC代码,最终都是转成C++和C代码,通过【终端】可以看到
1、打开【终端】,进入工程目录

2、命令:clang-rewrite-objc main.m 回车
因为Mac OS 10.9用的是基于LLVM的clang编译器(linux多是gcc)


3、发现多了一个【main.cpp】文件,大小2.5M,打开可以看到

共有93259行C++和C代码
意思是:Objective-C先翻译C++\C,再转成汇编代码
OC非真正面向对象,而是假的面向对象

4、搜索【int main】,找到主函数所在位置

int main(int argc, const char * argv[]){    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;         NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_xpndkrtn3gbc99jjvrdxxlj00000gn_T_main_599312_mi_0);        Person *p = ((id (*)(id, SEL))(void *)objc_msgSend)((id)((id (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)p, sel_registerName("setAge:"), 16);        NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_xpndkrtn3gbc99jjvrdxxlj00000gn_T_main_599312_mi_1,((int (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("age")));    }    return 0;}


对应的OC代码是:

两边对应起来看就是:

//  2 

Person *p = [[Personalloc]init];

p.age = 10;//相当于[p setAge:10];

去掉 强转 后的cpp代码:

5、在main.m文件中,导入头文件,手动执行底层运行时代码
#import <objc/runtime.h>
#import <objc/message.h>

运行时代码:发消息

objc_msgSend(p, @selector(setAge:),16);


objc_msgSend(p, @selector(setName:),@"nana");


下面通完一个json转对象模型,演示一个运行时的强大之处
先看这个json数据,代表从服务器返回的一条状态,里面有发状态的用户

一、先通过  NSJSONSerialization  类,将json 文件转成字典

二、然后,新建两个model模型,
一个是Status,一个是User


三、先演示使用KVC键值编码,一句话转字典为模型

可以看到,除了成员属性user,其他属性均已填充;
因此,还需重写setUser方法

重写setUser方法

此时,可以看到,已经完全通过KVC,将字典转成模型了;但KVC效率低

四、上面是KVC实现字典转模型,下面是通过运行时实现
新建一个NSObject的分类,先导入:

#import<objc/runtime.h>

#import<objc/message.h>

原理是:拿出类中每一个成员属性(instance variable 简称ivar),通过对成员属性名称的拼装,最后调用下面这方法:

objc_msgSend(self,NSSelectorFromString(setXxx:),dict[xxx]);

例如:

objc_msgSend(self,NSSelectorFromString(setName:),@"nana");

下面方法是class_copyIvarList
拷贝出属性列表,其中,Ivar就相当于基本数据类型
返回值是:指针(类似于数组名、数组首地址)




可以进入XCode文档 搜索Runtime

文档中的 运行时方法列表 


重要方法,根据ivar获得以下划线开头的 成员属性名,如_name

打印输出:ivar_getName

Object的分类,重要的~~~核心代码:
  NSObject+Ivar.m//  cmd_01_runtime  Created by beyond on 14-9-29.//  Copyright (c) 2014年 com.beyond. All rights reserved.//#import "NSObject+Ivar.h"// 字典转模型,下面是通过运行时实现#import <objc/runtime.h>#import <objc/message.h>@implementation NSObject (Ivar)// 使用字典,进行填充 成员属性- (void)populateWithDict:(NSDictionary *)dict{    Class cls = [self class];    unsigned int count;    // Ivar 全称实例变量,就是类的成员属性    // 通过 运行时代码,获得,类内部的成员属性的个数  放到count内存地址    // 返回结果 是一个指针,其所指向的内存空间为 首个Ivar的内存地址,类似于数组名    Ivar * ivarArr = class_copyIvarList(cls, &count);    // 遍历数组 (数组名 存放的就是数组首地址,就是一个指针,两者可互换)    for (int i = 0 ; i < count; i++) {        // 可以把 Ivar当成一个基本类型,如double,int        Ivar ivar = ivarArr[i];                const char * name = ivar_getName(ivar);        // 输出:_create_time        NSLog(@"%s",name);                // 1.  c 字符,转成 OC 【_create_time】        NSMutableString *ocName = [NSMutableString stringWithUTF8String:name];        // 2.  删掉最前面的_   【create_time】        [ocName deleteCharactersInRange:NSMakeRange(0, 1)];                // 2.  从字典取值时,用的key 【create_time】        NSString *key = [ocName copy];        id value = dict[key];                // 3.   自定义方法,将【create_time】转成【setCreate_name】        NSString *setter = [self ivarNameToMsgName:ocName];                // 4.   最后一步,使用运行时,填充数据        // 4.1  如果是NSNumber,转成int        if ([value isKindOfClass:[NSNumber class]]) {            int intValue = [value intValue];            // 如年龄 21            objc_msgSend(self, NSSelectorFromString(setter),intValue);        } else {            // 如果是...其他特殊情况,都要一一将对象类型,转成,基本类型            objc_msgSend(self, NSSelectorFromString(setter),value);        }                                    }        }// 自定义方法,将【_create_time】转成【setCreate_name】- (NSString *)ivarNameToMsgName:(NSMutableString *)ocName{    // 1.  取出首字母,变成大写    NSString *capital = [ocName substringToIndex:1].uppercaseString;    // 再将 首字母 由小写的,替换成大写的,此时,【Create_time】    [ocName replaceCharactersInRange:NSMakeRange(0, 1) withString:capital];    // 2.  拼接set,变成,【setCreate_time】    return [NSString stringWithFormat:@"set%@:", ocName];}@end


使用进来相当方便:
使用运行时,通过NSObject的分类,将字典转成对象,
同KVC一样,仅需一句,但效率更高:

[selfpopulateWithDict:dict];



最终效果也和KVC一样,字典全转成了对象











Objective-C具有相当多的动态特性,有动态类型(Dynamic typing),动态绑定(Dynamic binding)和动态加载(Dynamic loading)。

而在这之后,OC在底层也提供了相当丰富的运行时的特性,

比如枚举类属性方法(class_copyIvarList)获取方法实现等。

1、动态类型
即运行时再决定对象的类型。
如id类型。 id类型即通用的对象类,任何对象都可以被id指针所指,
而在实际使用中,往往使用introspection【内省】来确定该对象的实际所属类:

id obj = 某实例对象;if ([obj isKindOfClass:某类]){ 某类 *classSpecifiedInstance = (某类 *)obj;    // 现在强转后,已经是某类的实例对象了}
  -isKindOfClass:是NSObject的方法,

用意确定某个NSObject对象是否是某个类的成员。

与之相似的为-isMemberOfClass:

可以用以确定某个对象是否是某个类或其子类的成员。

这两个方法为典型的introspection【内省】方法。

在确定对象为某类成员后,可以安全地进行强制转换。

2、动态绑定
基于动态类型,在某个实例对象被确定后,其类型便被确定了。
该对象对应的属性和响应的消息也被完全确定,这就是动态绑定。
由于OC的动态特性,在OC中很少提及“函数”的概念,
传统的函数一般在编译时就已经把参数信息和函数实现打包到编译后的源码中了,
而在OC中最常使用的是消息机制
调用一个实例的方法,所做的是向该实例的指针发送消息
实例在收到消息后,从自身的实现中寻找响应这条消息的方法。

动态绑定所做的,即是在实例所属类确定后将某些属性和相应的方法绑定到实例上。

这里所指的属性和方法包括了原来没有在类中实现的,后来在运行时才需要的新加入的实现。

向一个NSObject对象发送-respondsToSelector:

或者-instancesRespondToSelector:等   来确定对象是否可以对某个SEL做出响应,

而在OC消息转发机制被触发之前,

对应的类的+resolveClassMethod:和+resolveInstanceMethod:将会被调用

在此时有机会动态地向类或者实例添加新的方法也即类的实现是可以动态绑定的。

void dynamicMethodIMP(id self, SEL _cmd){    // 代码 ....}

//该方法在OC消息转发  生效前被调用
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{ if (aSEL == @selector(resolveThisMethodDynamically))
{
//向[self class]中新加入一个方法:返回为void,SEL名字为aSEL,实现的具体内容为dynamicMethodIMP
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, “v@:”);
return YES;
}
return [super resolveInstanceMethod:aSel];
}  

可以在任意需要的地方,通过调用class_addMethod或者method_setImplementation(前者添加实现,后者替换实现),来完成动态绑定的需求。

3、动态加载
根据需求加载所需要的资源,如根据不同的机型做适配。如@2X的图片
基本的动态特性在常规的Cocoa开发中非常常用,特别是动态类型和动态绑定
由于Cocoa程序大量地使用Protocol-Delegate的设计模式,
因此绝大部分的delegate指针类型必须是id,以满足运行时delegate的动态替换(在Java里这个设计模式被叫做Strategy策略设计模式)。

这类运行时特性大多由/usr/lib/libobjc.A.dylib这个动态库提供,

里面定义了对于类、实例成员、成员方法和消息发送的很多API,

包括获取类实例变量列表,替换类中的方法,为类成员添加变量,动态改变方法实现等

一个简单的例子,比如在开发Universal应用或者游戏时,如果使用IB构建了大量的自定义的UI,

那么在由iPhone版转向iPad版的过程中所面临的一个重要问题就是如何从不同的nib中加载界面?

在iOS5之前,所有的UIViewController在使用默认的界面加载时(init或者initWithNibName:bundle:),

都会走-loadNibNamed:owner:options:。

但是,由于无法拿到-loadNibNamed:owner:options的实现,所以无法对其重载。

因此,只能通过运行时,使用自己实现的新的类似-loadNibNamed:owner:options的方法将系统的原方法替换掉

同时保证非iPad的设备还走原来的loadNibNamed:owner:options方法

交换自己实现的loadPadNibNamed:owner:options和系统的loadNibNamed:owner:options,

之后所有的loadNibNamed:owner:options消息,

都将会发为loadPadNibNamed:owner:options,由自己的代码进行处理。

+(BOOL)swizze {    Method 旧方法 = <span style="color:#cc0000;"><strong>class_getInstanceMethod</strong></span>(self, @selector(loadNibNamed:owner:options:));    if (!oldMethod) {        return NO;    }Method 新方法 = <strong><span style="color:#ff0000;">class_getInstanceMethod</span></strong>(self, @selector(loadPadNibNamed:owner:options:));if (!newMethod) {return NO;}<strong><span style="color:#ff0000;">method_exchangeImplementations</span></strong>(oldMethod, newMethod);return YES;}  


loadPadNibNamed:owner:options的实现如下,注意在其中的loadPadNibNamed:owner:options由于之前已经进行了交换,因此实际会发送为系统的loadNibNamed:owner:options。以此完成了对不同资源的加载。

-(NSArray *)loadPadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options {    NSString *newName = [name stringByReplacingOccurrencesOfString:@"@pad" withString:@""];    newName = [newName stringByAppendingFormat:@"@pad"];

//判断是否存在
NSFileManager *fm = [NSFileManager defaultManager];
NSString* filepath = [[NSBundle mainBundle] pathForResource:newName ofType:@”nib”];

//这里调用的loadPadNibNamed:owner:options:实际为为交换后的方法,即loadNibNamed:owner:options:
if ([fm fileExistsAtPath:filepath]) {
return [self loadPadNibNamed:newName owner:owner options:options];
} else {
return [self loadPadNibNamed:name owner:owner options:options]; 
}
}  

1. 传统的面向过程的语言开发,例如c语言。

实现c语言编译器很简单,只要按照语法规则实现一个LALR语法分析器就可以了。

 这里实现了编译器其中最最基础和原始的目标之一就是把一份代码里的函数名称,转化成一个相对内存地址,把调用这个函数的语句转换成一个jmp跳转指令

在程序开始运行时候,调用语句可以正确跳转到对应的函数地址。 这样很好,也很直白,但是。。。太死板了。所有事情都是预先定义死的!

2. 希望灵活,于是需要开发面向对象的语言,例如c++。

 c++在c的基础上增加了类的部分。

但这到底意味着什么呢?

在写它的编译器要如何考虑呢?

其实,就是让编译器多绕个弯,在严格的c编译器上增加一层类处理的机制,把一个函数限制在它处在的class环境里,每次请求一个函数调用,先找到它的对象, 其类型,返回值,参数等等,确定了这些后再jmp跳转到需要的函数

这样很多程序增加了灵活性,

因此同样一个函数调用会根据请求参数和类的环境返回完全不同的结果。

增加类机制后,就模拟了现实世界的抽象模式

不同的对象有不同的属性和方法。

同样的方法,不同的类有不同的行为!

 这里大家就可以看到作为一个编译器开发者都做了哪些进一步的思考。但是。。。还是死板, 仍然叫c++是static language。

3. 希望更加灵活! 于是完全把上面哪个类的实现部分抽象出来,做成一套完整运行阶段的检测环境。

这次再写编译器甚至保留部分代码里的sytax名称,名称错误检测,runtime环境注册所有全局的类,函数,变量等等信息等等

可以无限的为这个层增加必要的功能。

调用函数时候,会先从这个运行时环境里检测所以可能的参数再做jmp跳转,这就是runtime。

编译器开发起来比上面更加弯弯绕。

但是这个层极大增加了程序的灵活性。  

例如当调用一个函数时候,前2种语言,很有可能一个jmp到了一个非法地址导致程序crash, 但是在这个层次里面,runtime就过滤掉了这些可能性。 

这就是为什么dynamic langauge更加强壮。

 因为编译器和runtime环境开发人员已经帮你处理了这些问题。

好了上面说着这么多,再返回来看objective-c.  

现在你是不是能理解这样的语句了呢?
    id obj=self;
    if ([obj respondsToSelector:@selector(function1:)) {
    }
    if ([obj isKindOfClass:[NSArray class]] ) {
    }
    if ([obj conformsToProtocol:@protocol(myProtocol)]) {
    }           
    if ([[obj class] isSubclassOfClass:[NSArray class]]) {
    }
    [obj someNonExistFunction];

看似很简单的语句,但是为了让语言实现这个能力,语言开发者要付出很多努力实现runtime环境。

这里运行时环境,处理了弱类型、函数存在检查工作

runtime会检测注册列表里是否存在对应的函数,类型是否正确,

最后确定下来正确的函数地址,

再进行保存寄存器状态,压栈,函数调用等等实际的操作。

    id knife=[Knife grateKnife];
    NSArray *monsterList=[NSArray array];
    

[monsterListmakeObjectsPerformSelector:@selector(killMonster:) withObject:knife];

在c,c++年代去完成这个功能是非常麻烦的,但是动态语言却非常简单。

关于执行效率问题。 

“静态语言执行效率要比动态语言高”,这句没错。

因为一部分cpu计算损耗在了runtime过程中。

而静态语言生成的机器指令更简洁。

正因为知道这个原因,所以开发语言的人付出很大一部分努力为了保持runtime小巧上。

所以objecitve-c是c的超集+一个小巧的runtime环境。 


一 多态
动态绑定能使程序直到运行时才确定调用对象的实际方法。
         C++中的多态性具体体现在运行和编译两个方面,编译时多态是静态多态(重载、模版),在编译时就可以确定对象使用的形式,运行时多态是动态多态(虚函数抽象类,覆盖)。
          C++使用虚函数(虚函数表)来实现动态绑定,当基类对象的指针(或引用)指向派生类的对象时候,实际调用的是派生类相应函数。
         Objective-c 是动态语言,Objective-c系统总是跟踪对象所属的类。对于类型的判断和方法的确定都是在运行时进行。 

二 Objective-c多态

     首先看下面代码
     draw.h文件
     @interface Draw : NSObject
     @property (nonatomic,strongNSString *name;
       - (void) Print;
       - (void) draw;
     @end
     
     draw.m文件
      #import "Draw.h"
     @implementation Draw
     @synthesize name;
     - (id) init
     {
         if (self = [super init])
         {
             self.name = @"Draw Demo";
         }     
         return self;
     }
     - (void) draw
     {
           NSLog(@"Draw::draw.......");
     }
     - (void) Print
     {
         NSLog(@"i am  %@.",self.name);
     }
     @end
     
      cricle.h文件
      #import "Draw.h"
     @interface Circle : Draw
     @end     

      circle.m文件
      #import "Circle.h"
     @implementation Circle
     - (void) draw
     {
         NSLog(@"%@:draw circle",self.name);
     }
     @end
     
     Retangle.h文件
     #import "Draw.h"
     @interface Retangle : Draw
     @end
     
     Retangle.m文件
     #import "Retangle.h"
     @implementation Retangle
     - (void) draw
     {
         [super draw]; //通过super关键字可以调用基类的draw函数
         NSLog(@"%@:draw retangle",self.name);
     }
     @end

       定义了一个Draw基类,里面有一个数据成员name,和两个函数成员draw和Print,Circle和Retangle是从Draw派生的两个类,他们重写了基类Draw的draw方法。

代码使用
        Draw* base = [[Circle allocinit];
      [base draw]; //draw circle
      NSLog(@"address:%@",base);
       
      base = [[Retangle allocinit];
      [base draw]; //draw retangle
      NSLog(@"address:%@",base);
      [base Print];

输出结果
      Draw Demo:draw circle
      address:<Circle: 0x1002027a0>
      Draw::draw.......
      Draw Demo:draw retangle
      address:<Retangle: 0x100205e70>
      i am  Draw Demo.

      使用基类的指针分别指向创建的两个派生类对象,然后分别调用各自的draw函数,通过输出结果可以发现他们调用的是各自的draw方法。由于Retangele没有重写基类的Print函数,所有使用[base Print]调用的是基类的方法。同时通过address的输出发现base指向了两个不同的对象。


小结:
        1.与C++ 的多态相比,在Objective-c中是没有virtual关键字的,默认情况下只要子类重写了父类的方法就实现了覆盖(这一点和java类似),在Objective-c中同一类中的函数是不能被重载的。

        2.在Objective-c中,通过super关键字可以调用基类的函数,这个在C++中是没有的,在C++中可通过作用域运算符访问基类成员。
      
     除了上面的调用方式外,也可以这样:

         id base = [[Circle allocinit];
        [base draw]; //draw circle
         NSLog(@"address:%@",base);
       
        base = [[Retangle allocinit];
        [base draw]; //draw retangle
         NSLog(@"address:%@",base);
        [base Print];
   
       其输出结果和上面是一样的
 

三  类对象

       虽然Objective-c没有虚函数表,但是它有一个根类NSObject 

     objc.h 文件中关于NSObject的定义
     @interface NSObject <NSObject>
     {
         Class isa  OBJC_ISA_AVAILABILITY;
     }

     typedef struct objc_class *Class; 
     typedef struct objc_object {
         Class isa;
     } *id;

     typedef struct objc_selector  *SEL;
     typedef id (*IMP)(idSEL, ...);
    
 
通过上面的定义可以知道以下事实:
     1.Class isa 是NSObject类的第一个数据成员。
     2.Class 是一个指针,它指向一个objc_class的结构体。
     3.id 类型是一个指针,它指向一个objc_object的结构体,该结构体只有一个成员即Class isa;
     4.id 类型是一个指针,它指向一个存有objc_class的结构对象的指针的指针。

 3.1 isa介绍

以下是苹果官方文档对isa的介绍说明:    
       Every object is connected to the run-time system through its isa instance variable, inherited from the NSObject class. isa identifies the object's class; it points to a structure that's compiled from the class definition. Through isa, an object can find whatever information it needs at run timesuch as its place in the inheritance hierarchy, the size and structure of its instance variables, and the location of the method implementations it can perform in response to messages.
              
       实例变量是通过isa成员链接到运行时系统环境中的,任意NSObject的子类都会继承NSObjectisa成员,而且当NSObject的子类实例化对象时,isa实例变量永远是对象的第一个实例变量isa指向该对象的类对象,它是实例和类对象连接的桥梁。
      
      
     实例变量和类对象的关联,如下图所示:


下面是类对象(objc_class)的结构体

     struct objc_class {
         Class isa;                                             /* metaclass */                Class super_class                                  /* 父类的地址 */         const char *name                                  /*  类名称   */         long version                                         /*  版本     */         long info                                             /*  类信息   */         long instance_size                                /*  实例大小  */         struct objc_ivar_list *ivars                    /*  实例参数列表*/         struct objc_method_list **methodLists 	/*  方法列表  */         struct objc_cache *cache                      /*  方法缓存  */         struct objc_protocol_list *protocols     	/*  protocol链表*/
     } ;  

        在Objective-C中类也是一种对象,而且在程序运行时一直存在 类对象是一个根据类定义生成的一个结构体,里面存储了类的基本信息, 如:类的大小,类的名称,类的版本以及消息与函数的映射表等信息。
类对象所保存的信息在程序编译时确定,在程序启动 时加载到内存中。

 3.2 id介绍
     
    由上面的定义知道,id类型是一个指向类对象的指针的指针。在Objective-c中,id类型是一种通用的指针类型,id类型可以用来指向属于任何类的对象(只要该对象是属于NSObject即成体系)。 
   
    id类型的使用如下图所示:



   在使用id类型的时候要注意:
   
   1. id类型本事是一个指针类型,在使用时就不用加*号了,例如上面的例子 id base = [[Circle allocinit];

   2.  id类型是通用指针类型,弱类型,编译时不进行类型检查
       Objective-C可以将对象分为id类型和静态类型,如果不涉及到多态,尽量使用静态类型。
      在上的例子中使用了两种方式来调用派生类函数,第一种使用的即使静态类型,第二种使用的是id动态类型。在写代码时候,尽量使用静态类型,静态类型可更好的在编译阶段而不是运行阶段指出错误,同时能够提高程序的可读性。

        实例变量中isa成员用于保持其类对象在内存的地址,类对象对于所有实例来说在内存中只有一份副本,任何一个实例都可以通过 isa成员,访问类对象所保持的类的信息,isa成员可以通过类对象获得当前实例可以访问的消息列表,以及消息对应的函数地址。

         Objecive-c使用类对象的形式来实现运行多态,每个对象都保存其类对象的地址,类对象中保存了类的基本信息。类对象是进行动态创建(反射),动态识别,消息传递等机制的基础。

一 函数调用概述


     Objective-C不支持多重继承(同Java和Smalltalk),而C++语言支持多重继承。

 Objective-C是动态绑定,它的类库比C++要容易操作。

Objective-C在运行时可以允许根据字符串名字来访问方法和类,还可以动态连接和添加类。

 C++ 跟从面向对象编程里的Simula 67(一种早期OO语言)学派,

而Objecive-C属于Smalltalk学派,Simula 67学派更安全,因为大部分错误可以在编译时查出。

 在C++里,对象的静态类型决定你是否可以发送消息给它,

而对Objective-C来说,由动态类型来决定。


     


二 消息的产生

     在Objective-c中消息一直到运行时才能绑定到对应的函数:
     [reveiver message];
     编译器在处理时会将上面的表达式处理称下面这种形式:
     objc_msgSend(receiver,selector);
     如果方法有多个参数的时候会处理成下面这种形式:
     objc_msgSend(receiver,selector,arg1,arg2,…….); 
     现在知道了在Objective-c中函数调用都会被编译器进行预处理,调用obj_msgSend函数进行消息的发送。
 
    
三 类对象中与消息有关的结构
首先看一下与消息传递有关的几个数据结构和数据类型。
  1. struct objc_method_list **methodLists
  在类对象objc_class中有一个结构体 struct objc_method_list **methodLists ,它其实是方法的映射表。
  下面是objc_method_list的结构
 struct objc_method_list {    struct objc_method_list *obsolete                            int method_count                                             struct objc_method method_list[1]                        }


在objc_method_list中包含一个objc_method结构体
下面是objc_method的结构体
struct objc_method {          SEL  method_name char *method_types        IMP method_imp }  

   objc_method用来代表一个方法,其包含一个方法 SEL – 表示该方法的名称,一个types – 表示该方法的参数,一个 IMP - 指向该方法的具体实现的函数指针。  对于每个参数下面将具体说明。
     
      函数映射表的大致情况如下图所示



  2.SEL (An opaque type that represents a method selector)

     被定义为 typedef struct objc_selector *SEL 
  


      Objective-C 在编译的时候,会根据方法的名字,生成 一个用来区分这个方法唯一的 ID ,这个 ID 就是  SEL 类型的。 A selector is the name used to select a method to execute for an object, or the unique identifier that replaces the name when the source code is compiled. A selector by itself doesn’t do anything. It simply identifies a method. The only thing that makes the selector method name different from a plain string is that the compiler makes sure that selectors are unique. ) 只要方法的名字(包括参数序列)相同,那么它们的  ID 都是相同的。就是 说,不管是超类还是子类,不管是有没有超类和子类的关系,只要名字相同那么 ID 就是一样的。

           SEL就是一个函数标识,该标识是编译器根据其函数声明原型生成的。

  3.IMP ( A pointer to the function of a method implementation)

       被定义为typedef id (*IMP)(idSEL, ...)   
     IMP是一个函数指针,它指向的函数第一个参数实例变量的地址,即接受消息的对象的地址(receiver),第二个参数是SEL要调用的方法、第三个蚕食是函数参数,它是一个不定参数,函数的返回值为id类型。
     IMP 是消息最终调用的执行代码,该是方法是真正的实现代码 。可以像在C语言里面一样使用这个函数指针。

     - (void) viewDidLoad     {         [super viewDidLoad];         //声明一个函数指针         void (*MyPrint)(id,SEL,NSString*);           MyPrint = ( void (*)(id,SEL,NSString*) )[self methodForSelector:@selector(Print:)];             MyPrint(self,@selector(Print:),@"Hello World");     }          - (void) Print:(NSString*) str     {         NSLog(@"%@",str);          }

    - (IMP)methodForSelector:(SEL)aSelector函数的作用是通过SEL生成的ID来查找和定位函数的实现地址。
     MyPrint(self,@selector(Print:),@"Hello World ); 调用MyPrint函数指针所指向的函数,参数分别为receiver,SEL和要传递给函数的参数。
      
   通过上面的例子,明白了通过函数的第一个参数receiver可以区分不同类的函数声明相同的函数。


四 Objective-c消息传递

    首先,编译器根据函数声明生成一个唯一函数ID,每个实例变量的第一个成员是isa,它指向类对象,在类对象中保存有该类所拥有的方法列表,通过生成的的函数ID可以找到其对应的函数地址,从而调用函数。如果在当前类对象的函数映射表中没有找到函数的话,就继续搜索其父类中(每个类对象的super_class 存储了父类的类对象地址),如果到达其根类还是没找到的话,会报运行时错误。 其过程如下图所示:
 
     
    
    
    消息的成功传递依赖于两个重要的要素,通过实例中的 isa指针找到实例所属的类对象,然后通过类对象中的消息映射表找到所要调用的函数。
    首先通过传递的selector参数找到消息映射表中的函数然后调用函数,将实例的地址和参数传递给调用的这 个函数。最后返回返回值。 

 
      
     3.消息接收者对象(指向消息接收者对象的指针)以及方法中指定的参数传递给方法实现 IMP。
     4.最后,将方法实现的返回值作为该函数的返回值返回。
    注:编译器会自动插入调用消息函数objc_msgSend,无须在代码中显示调用该消息函数。
五 消息传递小结
     Objective-c的运行时和MFC的RTTI挺像的。
    下图是MFC的运行时机制,CRuntimeClass相当于Objectiv-c的object_clas类对象,在CRuntimeClass中有指向基类函数的指针,与MFC不同的是object_class没有类似与m_pNext_ClassMFC成员。
     1.编译器会将消息转换为对消息函数 objc_msgSend的调用,该函数有两个主要的参数:消息接收者 receiver 和消息对应的方法ID,即SEL, 同时接收消息不定参数列表。

    2.objc_masSend 通过 receiver中的isa找到类对象,从而找到 SEL 对应的方法实现 IMP。注:因为不同的类对同一方法可能会有不同的实现,所以找到的方法实现依赖于消息接收者(receiver)。
下面再做一下详细消息传递过程:
                           
                           
 
      
       正因为有像CRuntimeClass和object_class这些类类型的存在才使得它们拥有了运行识别,动态创建,序列化等机制。
一 消息查找优化
	发现如果每次函数调用都经历上面的过程(那函数调用的效率就会很低,尤其是当类的继承层次很多的时候,它需要一层层的查找其效率将会更低,为了加快查找调用的速度,Objective-c对消息查找做了优化。
	从类对象知道它含有一个 struct objc_cache *cache成员,这个缓存就是为了提高查找的效率的。每个类都有自己的缓存,同时包括继承的方法和在该类中定义的方法。当在查找IMP 时:
     1.首先去该类的方法 cache 中查找,如果找到了就返回它
     2.如果没有找到,就去该类的方法列表中查找。如果在该类的方法列表中找到了,则将 IMP 返回,并将它加入cache中缓存起来。根据最近使用原则,这个方法再次调用的可能性很大,缓存起来可以节省下次调用再次查找的开销。
     3.如果在该类的方法列表中没找到对应的 IMP,在通过该类结构中的 super_class指针在其父类结构的方法列表中去查找,直到在某个父类的方法列表中找到对应的IMP,返回它,并加入cache中。

     4.如果在自身以及所有父类的方法列表中都没有找到对应的 IMP,则进入下文中要讲的消息转发流程。


二 消息转发
      给一个对象发送它不能处理的消息会得到出错提示,然而,Objective-C运行时系统在抛出错误之前,会给消息接收对象发送一条特别的消息forwardInvocation 来通知该对象,该消息的唯一参数是个NSInvocation类型的对象——该对象封装了原始的消息和消息的参数。

可以实现forwardInvocation:方法来对不能处理的消息做一些默认的处理,也可以将消息转发给其他对象来处理,而不抛出错误。
       关于消息转发的作用,可以考虑如下情景:假设,需要设计一个能够响应negotiate消息的对象,并且能够包括其它类型的对象对消息的响应。 通过在negotiate方法的实现中将negotiate消息转发给其它的对象来很容易的达到这一目的。      更进一步,假设希望的对象和另外一个类的对象对negotiate的消息的响应完全一致。一种可能的方式就是让的类继承其它类的方法实现。 然而,有时候这种方式不可行,因为的类和其它类可能需要在不同的继承体系中响应negotiate消息。       虽然的类无法继承其它类的negotiate方法,但仍然可以提供一个方法实现,这个方法实现只是简单的将negotiate消息转发给其他类的对象,就好像从其它类那儿“借”来的现一样。如下所示:- negotiate  {    if ([someOtherObject respondsToSelector:@selector(negotiate)])        return  [someOtherObject negotiate];
    return self;
}
      这种方式显得有欠灵活,特别是有很多消息都希望传递给其它对象时,就必须为每一种消息提供方法实现。此外,这种方式不能处理未知的消息。当写下代码时,所有需要转发的消息的集合都必须确定。然而,实际上,这个集合会随着运行时事件的发生,新方法或者新类的定义而变化。     forwardInvocation:消息给这个问题提供了一个更特别的,动态的解决方案:当一个对象由于没有相应的方法实现而无法响应某消息时,运行时系统将通过forwardInvocation:消息通知该对象。每个对象都从NSObject类中继承了forwardInvocation:方法。然而,NSObject中的方法实现只是简单地调用了doesNotRecognizeSelector:。通过实现自己的forwardInvocation:方法,可以在该方法实现中将消息转发给其它对象。 要转发消息给其它对象,forwardInvocation:方法所必须做的有:     1.决定将消息转发给谁,并且     2.将消息和原来的参数一块转发出去消息可以通过invokeWithTarget:方法来转发:- (void) forwardInvocation:(NSInvocation *)anInvocation{    if ([someOtherObject respondsToSelector:[anInvocation selector]])        [anInvocation invokeWithTarget:someOtherObject];    else        [super forwardInvocation:anInvocation];} 
     转发消息后的返回值将返回给原来的消息发送者。可以返回任何类型的返回值,包括: id,结构体,浮点数等。       forwardInvocation:方法就像一个不能识别的消息的分发中心,将这些消息转发给不同接收对象。或者它也可以象一个运输站将所有的消息都发送给同一个接收对象。它可以将一个消息翻译成另外一个消息,或者简单的"吃掉“某些消息,因此没有响应也没有错误。forwardInvocation:方法也可以对不同的消息提供同样的响应,这一切都取决于方法的具体实现。该方法所提供是将不同的对象链接到消息链的能力。
注意: forwardInvocation:方法只有在消息接收对象中无法正常响应消息时才会被调用。 所以,如果希望一个对象将negotiate消息转发给其它对象,则这个对象不能有negotiate方法。否则,forwardInvocation:将不可能会被调用。

IOS中整个函数的调用流程。
         其实如果熟悉Python的人应该也知道,Python是一个全动态的语言,它的类型系统和Objective-c有些相像

可以通过NSObject的一些方法获取运行时信息或动态执行一些消息:
/*Returns a Boolean value that indicates whether the receiving class is a subclass of, or identical to, a given class.*/
 + (BOOL)isSubclassOfClass:(Class)aClass;
/*Returns a Boolean value that indicates whether instances of the receiver are capable of responding to a given selector.*/
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
 例如:if ([[Retangle class] respondsToSelector@selector(print)]==YES) {……

/*Returns a Boolean value that indicates whether the receiver is an instance of a given class.*/
- (BOOL)isMemberOfClass:(Class)aClass;
 例如:[obj isMemberOfClass:[Retangle class]];


除此之外还有:
conformsToProtocol 检查对象是否实现了指定协议类的方法
methodForSelector  返回指定方法实现的地址
performSelector:withObject 执行SEL 所指代的方法
 


- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
这三个方法,均为同步执行,与线程无关,主主线程和子一程中均可调用成功。等同于直接调用该方法。在需要动态的去调用方法的时候去使用。
例如:[self performSelector:@selector(test2)] 与[self test2];执行效果上完全相同。



- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
这两个方法为异步执行,即使delay传参为0,仍为异步执行。只能在主线程中执行,在子线程中不会调到aSelector方法。可用于当点击UI中一个按钮会触发一个消耗系统性能的事件,在事件执行期间按钮会一直处于高亮状态,此时可以调用该方法去异步的处理该事件,就能避免上面的问题。
在方法未到执行时间之前,取消方法为:

+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
注意:调用该方法之前或在该方法所在的viewController生命周期结束的时候去调用取消函数,以确保不会引起内存泄露。


- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
这两个方法,在主线程和子线程中均可执行,均会调用主线程的aSelector方法
如果wait传YES,则为异步执行,如果wait传NO,则为同步执行。

注意:apple不允许程序员在主线程以外的线程中对ui进行操作,此时必须调用performSelectorOnMainThread函数在主线程中完成UI的更新


- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
调用指定线程中的某个方法。分析效果同3。


- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg  开子线程在后台运行
 
             


2014-09-29 16:44 星期一  阅读(5051)
网站开发 by LA
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐