文档章节

Objective-C Runtime System

z
 zccdy
发布于 2014/10/14 11:51
字数 2743
阅读 7
收藏 0
点赞 0
评论 0
 

[0] Outline

  --  [1] 版本和平台

  --  [2] 与Runtime System交互

  --  [3] 方法的动态决议

  --  [4] 消息转发

  --  [5] 类型编码

  --  [6] 属性声明


[1] 版本和平台

Runtime System对于Objective-C来说就好比是它的操作系统,或者说是运行的支撑平台,它使得Objective-C代码能够按照既定的语言特性跑起来。相对于C/C++来说,Objective-C尽可能地把一些动作推迟到运行时来执行,即尽可能动态地做事情。因此,它不仅需要一个编译器,还需要一个运行时环境来执行编译后的代码。

Runtime System分为Legacy和Modern两个版本,一般来说,我们现在用的都是Modern版本。Modern版本的Runtime System有一个显著的特征就是“non-fragile”,即父类的成员变量的布局发生改变时,子类不需要重新编译。此外,还支持为声明的属性进行合成操作(即@property@synthesis)。

下面会讨论NSObject类Objective-C程序如何与Runtime System交互运行时动态地加载类发消息给其它对象,以及运行时如何获取对象信息


[2] 与Runtime System交互

Objective-C程序和Runtime System在三个不同层次进行交互:通过Objective-C源码;通过NSObject定义的函数;以及通过直接调用runtime functions


通常来讲,Runtime System都是在幕后工作,我们需要做的就是编写Objective-C代码,然后编译。编译器会为我们创建相应的数据结构和函数调用来实现语言的动态特性。这些数据结构保存着在类、Category定义和Protocol声明中所能找到的信息,包括成员变量模板、selectors,以及其它从源码中提取到的信息。

Runtime System是一个动态共享库,位于/usr/include/objc,拥有一套公共的接口,由一系列函数和数据结构组成。开发人员可以使用纯C调用一些函数来做编译器做的事情,或者扩展Runtime System,为开发环境制作一些工具等等。尽管一般情况下,编写Objective-C并不需要了解这些内容,但有时候会很有用。所有的函数都在Objective-C Runtime Reference有文档化信息。


Cocoa中大部分对象都是NSObject的子类(NSProxy是一个例外),继承了NSObject的方法。因此在这个继承体系中,子类可以根据需求重新实现NSObject定义的一些函数,实现多态和动态性,比如description方法(返回描述自身的字符串,类似Python中开头的三引号)。

一些NSObject定义的方法只是简单地询问Runtime System获得信息,使得对象可以进行自省(introspection),比如用来确定类类型的isKindOfClass:,确定对象在继承体系中的位置的isMemberOfClass:,判断一个对象是否能接收某个特定消息的respondsToSelector:,判断一个对象是否遵循某个协议的conformsToProtocol:,以及提供方法实现地址的methodForSelector:。这些方法让一个对象可以进行自省(introspect about itself)。


最主要的Runtime函数是用来发送消息的,它由源码中的消息表达式激发。发送消息是Objective-C程序中最经常出现的表达式,而该表达式最终会被转换成objc_msgSend函数调用。比如一个消息表达式[receiver message]会被转换成objc_msgSend(receiver, selector),如果有参数则为objc_msgSend(receiver, selector, arg1, arg2, …)

消息只有到运行时才会和函数实现绑定起来:首先objc_msgSend在receiver中查找selector对应的函数实现;然后调用函数过程,将receiving object(即this指针)和参数传递过去;最后,返回函数的返回值。

发送消息的关键是编译器为类和对象创建的结构,包含两个主要元素,一个是指向superclass的指针,另一个是类的dispatch table,该dispatch table中的表项将selector和对应的函数入口地址关联起来。

当一个对象被创建时,内存布局中的第一个元素是指向类结构的指针,isa。通过isa指针,一个对象可以访问它的类结构,进而访问继承的类结构。示例图可参见此处

当向一个对象发送消息时,objc_msgSend先通过isa指针在类的dispatch table中查找对应selector的函数入口地址,如果没有找到,则沿着class hierarchy(类的继承体系)寻找,直到NSObject类。这就是在运行时选择函数实现,用OOP的行话来说,就是动态绑定。

为了加速发送消息的速度,Runtime System为每个类创建了一个cache,用来缓存selector和对应函数入口地址的映射。


当objc_msgSend找到对应的函数实现时,它除了传递函数参数,还传递了两个隐藏参数:receiving objectselector。之所以称之为隐藏参数,是因为这两个参数在源代码中没有显示声明,但还是可以通过self和_cmd来访问。

当一个消息要被发送给某个对象很多次的时候,可以直接使用methodForSelector:来进行优化,比如下述代码:


  1. //////////////////////////////////////////////////////////////   
  2. void (*setter)(id, SEL, BOOL);  
  3. int i;  
  4.   
  5. setter = (void (*)(id, SEL, BOOL))[target  
  6.      methodForSelector:@selector(setFilled:)];  
  7. for ( i = 0; i < 1000, i++ )   
  8.      setter(targetList[i], @selector(setFilled:), YES);  
  9. //////////////////////////////////////////////////////////////  
//////////////////////////////////////////////////////////////
void (*setter)(id, SEL, BOOL);
int i;

setter = (void (*)(id, SEL, BOOL))[target
     methodForSelector:@selector(setFilled:)];
for ( i = 0; i < 1000, i++ ) 
     setter(targetList[i], @selector(setFilled:), YES);
//////////////////////////////////////////////////////////////


 

其中,methodForSelector:是由Cocoa Runtime System提供的,而不是Objective-C本身的语言特性。这里需要注意转换过程中函数类型的正确性,包括返回值和参数,而且这里的前两个参数需要显示声明为id和SEL。


[3] 方法的动态决议

有时候我们想要为一个方法动态地提供实现,比如Objective-C的@dynamic 指示符,它告诉编译器与属性对应的方法是动态提供的。我们可以利用resolveInstanceMethod:resolveClassMethod:分别为对象方法和类方法提供动态实现。

一个Objective-C方法本质上是一个拥有至少两个参数(self和_cmd)的C函数,我们可以利用class_addMethod向一个类添加一个方法。比如对于下面的函数:


  1. //////////////////////////////////////////////////////////////   
  2. void dynamicMethodIMP(id self, SEL _cmd) {  
  3.      // implementation ….   
  4. }  
  5. //////////////////////////////////////////////////////////////  
//////////////////////////////////////////////////////////////
void dynamicMethodIMP(id self, SEL _cmd) {
     // implementation ….
}
//////////////////////////////////////////////////////////////


 

我们可以利用resolveInstanceMethod:将它添加成一个方法(比如叫resolveThisMethodDynamically):


  1. //////////////////////////////////////////////////////////////   
  2. @implementation MyClass  
  3. + (BOOL)resolveInstanceMethod:(SEL)aSEL  
  4. {  
  5.      if (aSEL == @selector(resolveThisMethodDynamically)) {  
  6.           class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");  
  7.           return YES;  
  8.      }  
  9.      return [super resolveInstanceMethod:aSEL];  
  10. }  
  11. @end  
  12. //////////////////////////////////////////////////////////////  
//////////////////////////////////////////////////////////////
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
     if (aSEL == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
     }
     return [super resolveInstanceMethod:aSEL];
}
@end
//////////////////////////////////////////////////////////////


 

动态决议和发送消息并不冲突,在消息机制起作用之前,一个类是有机会动态决议一个方法的。当respondsToSelector:或者instancesRespondToSelector:被激活时,dynamic method resolver会优先有个机会为这个selector提供一份实现。如果实现了resolveInstanceMethod:,对于不想动态决议而想让其遵循消息转发机制的selectors,返回NO即可。

Objective-C程序可以在运行时链接新的类和category。动态加载可以用来做很多不同的事情,比如System Preferences里头各种模块就是动态加载的。尽管有运行时函数可以动态加载Objective-C模块(objc/objc-load.h中的objc_loadModules),但Cocoa的NSBundle类提供了更方便的动态加载接口。


[4] 消息转发

向一个对象发送它不处理的消息是一个错误,不过在报错之前,Runtime System给了接收对象第二次的机会来处理消息。在这种情况下,Runtime System会向对象发一个消息,forwardInvocation:,这个消息只携带一个NSInvocation对象作为参数——这个NSInvocation对象包装了原始消息和相应参数。

通过实现forwardInvocation:方法(继承于NSObject),可以给不响应的消息一个默认处理方式。正如方法名一样,通常的处理方式就是转发该消息给另一个对象:


  1. //////////////////////////////////////////////////////////////   
  2. - (void)forwardInvocation:(NSInvocation *)anInvocation  
  3. {  
  4.      if ([someOtherObject respondsToSelector:[anInvocation selector]])  
  5.           [anInvocation invokeWithTarget:someOtherObject];  
  6.      else  
  7.           [super forwardInvocation:anInvocation];  
  8. }  
  9. //////////////////////////////////////////////////////////////  
//////////////////////////////////////////////////////////////
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
     if ([someOtherObject respondsToSelector:[anInvocation selector]])
          [anInvocation invokeWithTarget:someOtherObject];
     else
          [super forwardInvocation:anInvocation];
}
//////////////////////////////////////////////////////////////


 

对于不识别的消息(在dispatch table中找不到),forwardInvocation:就像一个中转站,想继续投递或者停止不处理,都由开发人员决定。


[5] 类型编码

为了支持Runtime System,编译器将返回值类型、参数类型进行编码,相应的编译器指示符是@encode

比如,void编码为v,char编码为c,对象编码为@,类编码为#,选择符编码为:,而符合类型则由基本类型组成,比如


  1. typedef struct example {  
  2.      id     anObject;  
  3.      char *aString;  
  4.      int anInt;  
  5. } Example;  
typedef struct example {
     id     anObject;
     char *aString;
     int anInt;
} Example;


 

编码为{example=@*i}。


[6] 属性声明

当编译器遇到属性声明时,它会生成一些可描述的元数据(metadata),将其与相应的类、category和协议关联起来。存在一些函数可以通过名称在类或者协议中查找这些metadata,通过这些函数,我们可以获得编码后的属性类型(字符串),复制属性的attribute列表(C字符串数组)。因此,每个类和协议的属性列表我们都可以获得。


与类型编码类似,属性类型也有相应的编码方案,比如readonly编码为R,copy编码为C,retain编码为&等。

通过property_getAttributes函数可以后去编码后的字符串,该字符串以T开头,紧接@encode type和逗号,接着以V和变量名结尾。比如:


  1. @property char charDefault;  
@property char charDefault;

描述为:Tc,VcharDefault


  1. @property(retain)ididRetain;  
@property(retain)ididRetain;

描述为:T@,&,VidRetain


Property结构体定义了一个指向属性描述符的不透明句柄:typedef struct objc_property *Property;

通过class_copyPropertyList和protocol_copyPropertyList函数可以获取相应的属性数组:


  1. objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)  
  2. objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)  
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)


 

通过property_getName函数可以获取属性名称。

通过class_getProperty和protocol_getProperty可以相应地根据给定名称获取到属性引用:


  1. objc_property_t class_getProperty(Class cls, const char *name)  
  2. objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)  
objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)


 

通过property_getAttributes函数可以获取属性的@encode type string:

const char *property_getAttributes(objc_property_t property)


以上函数组合成一段示例代码:


  1. @interface Lender : NSObject {  
  2.      float alone;  
  3. }  
  4. @property float alone;  
  5. @end  
  6.   
  7. id LenderClass = objc_getClass("Lender");  
  8. unsigned int outCount, i;  
  9. objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);  
  10. for (i = 0; i < outCount; i++) {  
  11.      objc_property_t property = properties[i];  
  12.      fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));  
  13. }  
@interface Lender : NSObject {
     float alone;
}
@property float alone;
@end

id LenderClass = objc_getClass("Lender");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) {
     objc_property_t property = properties[i];
     fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}


 

本文转载自:http://www.apkbus.com/android-710-1.html

共有 人打赏支持
z
粉丝 0
博文 5
码字总数 2112
作品 0
成都
Runtime of Objective-C

- (void)forwardInvocation:(NSInvocation *)anInvocation { if ([someOtherObject respondsToSelector:[anInvocation selector]]) [anInvocation invokeWithTarget:someOtherObject]; else ......

Im刘亚芳 ⋅ 2014/12/05 ⋅ 0

iOS开发小结(一):ObjCRuntimeGuide小记

Jason Lee @ 杭州 博客:http://blog.csdn.net/jasonblog 微博:http://weibo.com/jasonmblog 版本和平台 Runtime System对于Objective-C来说就好比是它的操作系统/运行平台,它使得Objecti...

迷途d书童 ⋅ 2012/03/05 ⋅ 0

The Objective-C runtime

By your cmd This post is a write-up of a talk I gave at Alt Tech Talks: London on the Objective-C runtime. Seriously though, you should’ve been there. The Objective-C runtime?......

TaciturnKnightYQ ⋅ 2015/12/19 ⋅ 0

Understanding the Objective-C Runtime

The Objective-C Runtime is one of the overlooked features of Objective-C initially when people are generally introduced to Cocoa/Objective-C. The reason for this is that while O......

木木情深 ⋅ 2014/02/28 ⋅ 0

iOS开发 runtime实现原理以及实际开发中的应用

主要是这两个帖子 http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/ http://tech.glowing.com/cn/objective-c-runtime/ 然后,关于里面的代码实现有2个比较不错的博客,可以参...

神补刀 ⋅ 2015/09/20 ⋅ 0

这些年我们爱着的 Objective-C

Objective-C是开发OS X和iOS应用的标准语言。即便是天天跟它打交道的开发者,有些也会误以为Objective-C就是Apple公司创建出来的语言,但实际上它并不是Apple的亲骨肉,而是从别人家过继过来...

oschina ⋅ 2016/06/26 ⋅ 12

GNUstep Objective-C Runtime 1.6 发布

“继1.0版本发布之后一年多一点,1.6版本的GNUstep Objective-C Runtime与大家见面了。它提供了Apple's Mac OS X 10.7/iOS 5 runtimes 的一个超集,并为自由软件平台上Objective-C 和Objecti...

xyxzfj ⋅ 2011/11/26 ⋅ 0

详解Objective-C runtime

原文地址:http://blog.securemacprogramming.com/2013/12/by-your-cmd/ 感谢翻译小组成员wingpan热心翻译。本篇文章是我们每周推荐优秀国外的技术类文章的其中一篇。如果您有不错的原创或译...

Michael-W ⋅ 2014/01/06 ⋅ 0

Associated Objects

Written by Mattt Thompson on Feb 10th, 2014 #import <objc/runtime.h> Objective-C developers are conditioned to be wary of whatever follows this ominous incantation. And for good......

whj ⋅ 2014/03/09 ⋅ 0

(1)知识准备【利用objective-c的runtime特性,结合FMDB实现一个轻量级的ORM】

版权声明:本文为博主原创文章,未经博主允许不得转载。 (本ORM的源码已经上传到github上 (https://github.com/helloclq/BCSqliteORMFMDB),大家可以下载测试,如发现什么问题或意见,欢迎大...

辉兔狼 ⋅ 2015/09/09 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

win10怎么彻底关闭自动更新

win10自带的更新每天都很多,每一次下载都要占用大量网络,而且安装要等得时间也蛮久的。 工具/原料 Win10 方法/步骤 单击左下角开始菜单点击设置图标进入设置界面 在设置窗口中输入“服务”...

阿K1225 ⋅ 今天 ⋅ 0

Elasticsearch 6.3.0 SQL功能使用案例分享

The best elasticsearch highlevel java rest api-----bboss Elasticsearch 6.3.0 官方新推出的SQL检索插件非常不错,本文一个实际案例来介绍其使用方法。 1.代码中的sql检索 @Testpu...

bboss ⋅ 今天 ⋅ 0

informix数据库在linux中的安装以及用java/c/c++访问

一、安装前准备 安装JDK(略) 到IBM官网上下载informix软件:iif.12.10.FC9DE.linux-x86_64.tar放在某个大家都可以访问的目录比如:/mypkg,并解压到该目录下。 我也放到了百度云和天翼云上...

wangxuwei ⋅ 今天 ⋅ 0

PHP语言系统ZBLOG或许无法重现月光博客的闪耀历史[图]

最近在写博客,希望通过自己努力打造一个优秀的教育类主题博客,名动江湖,但是问题来了,现在写博客还有前途吗?面对强大的自媒体站点围剿,还有信心和可能型吗? 至于程序部分,我选择了P...

原创小博客 ⋅ 今天 ⋅ 0

IntelliJ IDEA 2018.1新特性

工欲善其事必先利其器,如果有一款IDE可以让你更高效地专注于开发以及源码阅读,为什么不试一试? 本文转载自:netty技术内幕 3月27日,jetbrains正式发布期待已久的IntelliJ IDEA 2018.1,再...

Romane ⋅ 今天 ⋅ 0

浅谈设计模式之工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻...

佛系程序猿灬 ⋅ 今天 ⋅ 0

Dockerfile基础命令总结

FROM 指定使用的基础base image FROM scratch # 制作base image ,不使用任何基础imageFROM centos # 使用base imageFROM ubuntu:14.04 尽量使用官方的base image,为了安全 LABEL 描述作...

ExtreU ⋅ 昨天 ⋅ 0

存储,对比私有云和公有云的不同

导读 说起公共存储,很难不与后网络公司时代的选择性外包联系起来,但尽管如此,它还是具备着简单和固有的可用性。公共存储的名字听起来也缺乏专有性,很像是把东西直接堆放在那里而不会得到...

问题终结者 ⋅ 昨天 ⋅ 0

C++难点解析之const修饰符

C++难点解析之const修饰符 c++ 相比于其他编程语言,可能是最为难掌握,概念最为复杂的。结合自己平时的C++使用经验,这里将会列举出一些常见的难点并给出相应的解释。 const修饰符 const在c...

jackie8tao ⋅ 昨天 ⋅ 0

聊聊spring cloud netflix的HystrixCommands

序 本文主要研究一下spring cloud netflix的HystrixCommands。 maven <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-clo......

go4it ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部