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 Objective-C (the language) is easy to pick up in only a couple hours, newcomers to
Cocoa spend most of their time wrapping their heads around the Cocoa Framework and adjusting to how it works. However the runtime is something that everybody should at least know how it works in some detail beyond knowing that code like [target
gets translated into
doMethodWith:var1];objc_msgSend(target,@selector(doMethodWith:),var1);
by
the compiler. Knowing what the Objective-C runtime is doing will help you gain a much deeper understanding of Objective-C itself and how your app is run. I think Mac/iPhone Developers will gain something from this, regardless of your level of experience.
Objective-C运行时对于刚刚学习cocoa/Objective-c的新手来说是一个很宏观的基础特征。这个原因是Ojbective-c是一门在几个小时之内就可以学会的语言,但是初学者将会花费大量的时间纠结在Cocoa Framework和弄明白他是怎么工作的。但是每一个人都至少得知道运行时在细节上是怎么工作的要超过像这样的代码 [target doMethodWith:var1](可以被编译器翻译成objc_msgSend(target,@selector(doMethodWith:),var1);)理解运行时可以帮助你对objective-c有一个更深的认识,并且知道自己的程序是怎样原型的。我想Mac/iphone开发者将能够从这里获取很多东西,忽略掉你的经验。
The Objective-C Runtime is Open Source
Objective-c运行时是开源的
The Objective-C Runtime is open source and available anytime from http://opensource.apple.com. In fact examining the Objective-C
is one of the first ways I went through to figure out how it worked, beyond reading Apples documentation on the matter. You can download the current version of the runtime (as of this writting) for Mac OS X 10.6.2 here objc4-437.1.tar.gz.
Objective-c运行时是一开源的,并且可以从 获得。实际上测试Objective-c是我解释它怎么运行的第一种方法,要比阅读拼过的文档要好,在这个问题上。你可以下载当前版本的运行时。
Dynamic vs Static Languages
动态语言和静态语言的比较
Objective-C is a runtime oriented language, which means that when it‘s possible it defers decisions about what will actually be executed from compile & link time to when it‘s actually executing on the runtime. This gives you a lot of flexibility in that you
can redirect messages to appropriate objects as you need to or you can even intentionally swap method implementations, etc. This requires the use of a runtime which can introspect objects to see what they do & don‘t respond to and dispatch methods appropriately.
If we contrast this to a language like C. In C you start out with a main()
method
and then from there it‘s pretty much a top down design of following your logic and executing functions as you‘ve written your code. A C struct can‘t forward requests to perform a function onto other targets. Pretty much you have a program like so
Objective-c是一个运行时的面向对象的怨言,这意味着它将在运行时决定执行什么而不是在编译时。这给了你很大的便利性,直接按照你的需要向对象发送消息。或者你甚至可以故意的改变方法实现等等。。。这需要对能够对对象内省来看起能够和不能够执行哪些方法并且将方法发送给对象的运行时的使用。如果我们和像C一样的语言比较。使用C语言,你从main()方法开始,然后完全看着你设计的逻辑和你的代码自上而下的执行。一个c结构体不能直接的讲一个函数运行在一个对象上。就像这个程序:
#include < stdio.h > int main(int argc, const char **argv[]) { printf("Hello World!"); return 0; }
which a compiler parses, optimizes and then transforms your optimized code into assembly
编译器语法分析,优化然后将你最佳化的代码翻译成汇编语言
.text .align 4,0x90 .globl _main _main: Leh_func_begin1: pushq %rbp Llabel1: movq %rsp, %rbp Llabel2: subq $16, %rsp Llabel3: movq %rsi, %rax movl %edi, %ecx movl %ecx, -8(%rbp) movq %rax, -16(%rbp) xorb %al, %al leaq LC(%rip), %rcx movq %rcx, %rdi call _printf movl $0, -4(%rbp) movl -4(%rbp), %eax addq $16, %rsp popq %rbp ret Leh_func_end1: .cstring LC: .asciz "Hello World!"
and then links it together with a library and produces a executable. This contrasts from Objective-C in that while the process is similar the code that the compiler generates depends on the presence of the Objective-C Runtime Library. When we are all initially
introduced to Objective-C we are told that (at a simplistic level) what happens to our Objective-C bracket code is something like…
然后将库和它链接起来,产生一个可执行文件。这和Objective-C相比依赖Objective-C库的编译器产生了相同的代码。当我们刚开始接触Objective-C的时候,我们被告诉像这样被中括号包裹的Objective-C代码
[self doSomethingWithVar:var1];
gets translated to…
被翻译成
objc_msgSend(self,@selector(doSomethingWithVar:),var1);
but beyond this we don‘t really know much till much later on what the runtime is doing.
但是超过这,实际上我们并不知道之后运行时做了些什么。
What is the Objective-C Runtime?
什么是Objective-C运行时环境?
The Objective-C Runtime is a Runtime Library, it‘s a library written mainly in C & Assembler that adds the Object Oriented capabilities to C to create Objective-C. This means it loads in Class information, does all method dispatching, method forwarding, etc.
The Objective-C runtime essentially creates all the support structures that make Object Oriented Programming with Objective-C Possible.
Objective-C运行时是一个运行时库。,这是一个用C和汇编写的库,为了给C语言提供面向对象的能够,以创造Objective-C。这意味它加载类信息,分配方法,执行方法等等。Objective-c主要提供了让Objective-C面向对象的能力。
Objective-C Runtime Terminology
Ojbective-C 运行时术语
So before we go on much further, let‘s get some terminology out of the way so we are all on the same page about everything. 2 Runtimes As far as Mac & iPhone Developers are concerned there are 2 runtimes: The Modern Runtime & the Legacy Runtime Modern Runtime:
Covers all 64 bit Mac OS X Apps & all iPhone OS Apps Legacy Runtime: Covers everything else (all 32 bit Mac OS X Apps) Method There are 2 basic types of methods. Instance Methods (begin with a ‘-‘ like -(void)doFoo;
that
operate on Object Instances. And Class Methods (begin with a ‘+‘ like +
. Methods are just like C Functions in that they are a grouping of code that performs a small task like
(id)alloc
于是在我们更进一步之前,我们先了解一些在这个页面中出现的基本术语。对于Mac和Iphone开发者而言有两个运行时环境:现代运行时环境和遗留下来的运行时环境,涵盖64位Mac OS X程序和所有Iphone OS程序的遗留下来的运行时环境,涵盖所有程序。这里有两个种基本的方法。 实例方法(以“-”开始),一个作用于对象实例的动作。还有类方法(以“+”开始)。这种方法就像C方法一样是一段代码的结合一完成一个简单的任务,比如
-(NSString *)movieTitle { return @"Futurama: Into the Wild Green Yonder"; }
Selector A selector in Objective-C is essentially a C data struct that serves as a mean to identify an Objective-C method you want an object to perform. In the runtime it‘s defined like so…
选择器A 选择器在Objective-C中是一个重要的C数据结构提供了辨识具体的Objective-C方法在你需要的对象上执行的方法。在运行时这个定义像:
typedef struct objc_selector *SEL;
and used like so…
并且使用时像
SEL aSel = @selector(movieTitle);
Message
消息
[target getMovieTitleForObject:obj];
An Objective-C Message is everything between the 2 brackets ‘[ ]‘ and consists of the target you are sending a message to, the method you want it to perform and any arguments you are sending it. A Objective-C message while similar to a C function call is different.
The fact that you send a message to an object doesn‘t mean that it‘ll perform it. The Object could check who the sender of the message is and based on that decide to perform a different method or forward the message onto a different target object. Class If
you look in the runtime for a class you‘ll come across this…
一个Objective-C消息是在[]之间的所有东西,它由你要发送消息的对象和你要执行的方法以及你要发送的参数组成。一个Objective-C消息与C函数调用相比是不同的。实际上你发送了一个消息给一个对象并不意味着这个对象会执行这个方法。对象将会检测谁是这个消息的发送者然后决定是执行一个别的方法还是传递这个消息给其他的对象。如果你观察类在运行时的情况,你将会发现
typedef struct objc_class *Class; typedef struct objc_object { Class isa; } *id;
Here there are several things going on. We have a struct for an Objective-C Class and a struct for an object. All the objc_object has is a class pointer defined as isa, this is what we mean by the term ‘isa pointer‘. This isa pointer is all the Objective-C
Runtime needs to inspect an object and see what it‘s class is and then begin seeing if it responds to selectors when you are messaging objects. And lastly we see the id pointer. The id pointer by default tells us nothing about Objective-C objects except that
they are Objective-C objects. When you have a id pointer you can then ask that object for it‘s class, see if it responds to a method, etc and then act more specifically when you know what the object is that you are pointing to. You can see this as well on
Blocks in the LLVM/Clang docs
很明显这里有很多东西正在进行。我们有一个Objective-C的数据结构。所有的objc对象有一个ISA的类指针。这就是我们用“isa pointer”所描述的。 ISA指针是所有Ojective-c运行时所需要的,去辨别对象,和对象类型然后判断其是否对一个选择器有响应当你发送消息时。然后我们看到了对象指针。对象指针默认没有告诉我任何东西,除了这是个Objective-C对象之外。当你有一个对象指针,你可以询问这个对象的类型,检查是否响应某个方法等等,然后执行当你知道这个指针指向的是一个什么对象。你能发现同样的东西关于Blocks在LLVM/CLang文档中。
struct Block_literal_1 { void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor_1 { unsigned long int reserved; // NULL unsigned long int size; // sizeof(struct Block_literal_1) // optional helper functions void (*copy_helper)(void *dst, void *src); void (*dispose_helper)(void *src); } *descriptor; // imported variables };
Blocks themselves are designed to be compatible with the Objective-C runtime so they are treated as objects so they can respond to messages like -retain
,-release
,-copy
,etc.
IMP (Method Implementations)
Block被设计成能够与Objective-C运行时结合,方便把他们当成对象处理,以使他们能够相应这些消息。
typedef id (*IMP)(id self,SEL _cmd,...);
IMP‘s are function pointers to the method implementations that the compiler will generate for you. If your new to Objective-C you don‘t need to deal with these directly until much later on, but this is how the Objective-C runtime invokes your methods as we‘ll
see soon. Objective-C Classes So what‘s in an Objectve-C Class? The basic implementation of a class in Objective-C looks like
IMP是编译器将会产生给你的函数指针。如果你刚接触Objective-C,你不用直接处理这些知道很长一段时间之后,但是这是为什么Objective-C运行时像你看到的一样执行行你的方法的基础实现。对于Objective-C类来说什么是一个Objetive-C的类呢?一个Objective-C的累的基础实现像这样:
@interface MyClass : NSObject { //vars NSInteger counter; } //methods -(void)doFoo; @end
but the runtime has more than that to keep track of
但是运行时有更多的东西以追踪
#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
We can see a class has a reference to it‘s superclass, it‘s name, instance variables, methods, cache and protocols it claims to adhere to. The runtime needs this information when responding to messages that message your class or it‘s instances.
我们可以看到类有一个指向它夫类的引用,它的名字,实例变量,实例方法,缓存和它所支持的协议。运行时需要这些信息当你发送消息给你的类或者实例时响应这些消息。
So Classes define objects and yet are objects themselves? How does this work
于是类定义对象而不是对象本身。这是怎样实现的
Yes earlier I said that in objective-c classes themselves are objects as well, and the runtime deals with this by creating Meta Classes. When you send a message like [NSObject
you are actually sending a message to the class object, and that class object needs to be an instance of the MetaClass which itself is an instance of the root meta class. While if you say subclass from NSObject, your class points to NSObject
alloc]
as it‘s superclass. However all meta classes point to the root metaclass as their superclass. All meta classes simply have the class methods for their method list of messages that they respond to. So when you send a message to a class object like [NSObject
then
alloc]objc_msgSend()
actually
looks through the meta class to see what it responds to then if it finds a method, operates on the Class object.
正像我遭袭时候说过的一样Objective-C类本身也是对象。并且运行时通过创建元信息来处理它。当你发送一个消息,你实际上是发送了一个消息给类对象。并且类对象必须是一个元类型的实例。当你说要从NSObject继承的时候,你的类指针指向NSObject做为它的父类。无论怎样,所有元类型都指向根元类型做为他们的夫类。所有的元类型很简单的有用类方法他们能够响应的消息的列表。所以,当你发送一个消息给一个类对象的时候,实际上检查了元类型来确定它时候能够响应,并且如果可以找到一个方法,在类上执行该方法。
Why we subclass from Apples Classes
为什么我们从苹果的类型继承
So initially when you start Cocoa development, tutorials all say to do things like subclass NSObject and start then coding something and you enjoy a lot of benefits simply by inheriting from Apples Classes. One thing you don‘t even realize that happens for
you is setting your objects up to work with the Objective-C runtime. When we allocate an instance of one of our classes it‘s done like so…
在你最开始开发Cocoa程序的时候,教程都出要从NSobject结成,然后开始写代码,你会享受从苹果的类继承带来的乐趣。一件事情你甚至没有注意到,就是到底法身了什么当你组织你的对象在Objectiv-C运行时环境上。但你实例化了你的类,像这样:
MyObject *object = [[MyObject alloc] init];
the very first message that gets executed is +alloc
.
If you look at the documentation it
says that "The isa instance variable of the new instance is initialized to a data structure that describes the class; memory for all other instance variables is set to 0." So by inheriting from Apples classes we not only inherit some great attributes, but
we inherit the ability to easily allocate and create our objects in memory that matches a structure the runtime expects (with a isa pointer that points to our class) & is the size of our class.
第一个执行的消息是alloc。如果你查阅文档,它说“一个新实例的ISA实例被初始化成描述这个类的一个数据结构,所有其他的实例的没存被设置成0”。于是从苹果累继承我们不止继承了一些属性,而且我们还继承了简单的内存分配和在内存中创建符合运行时期望大小的我们的对象。
So what‘s with the Class Cache? ( objc_cache *cache )
类Cache是个什么东西?
When the Objective-C runtime inspects an object by following it‘s isa pointer it can find an object that implements many methods. However you may only call a small portion of them and it makes no sense to search the classes dispatch table for all the selectors
every time it does a lookup. So the class implements a cache, whenever you search through a classes dispatch table and find the corresponding selector it puts that into it‘s cache. So when objc_msgSend()
looks
through a class for a selector it searches through the class cache first. This operates on the theory that if you call a message on a class once, you are likely to call that same message on it again later. So if we take this into account this means that if
we have a subclass of NSObject
called MyObject
and
run the following code
当Objective-C运行时通过跟踪一个对象的ISA指针来检查一个对象的时候,它将会发现一个实现了很多方法的对象。无论怎样,你只会调用他们很少的一部分并且完全没有意义去检查类的方法执行表每次检查的时候。于是类实现了一个Cache,当你查找类的dispatch table并且想找到对应的方法的时,将会把方法放入cache中。于是当Ojbc_msgSend()检查一个类查找一个方法的时候,它会先检查cache。这个操作基于这样的理论,当你在一个类上执行一个方法一次之后,你将会执行相同的方法在之后的一段时间内。于是,如果我们着重考虑一下这个,如果我们有一个叫做MyObject的NSObject的子类,并且运行下列代码
MyObject *obj = [[MyObject alloc] init]; @implementation MyObject -(id)init { if(self = [super init]){ [self setVarA:@”blah”]; } return self; } @end
the following happens
这些将会发生,
(1) [MyObject alloc]
gets executed first.
MyObject class doesn‘t implement alloc so we will fail to find +alloc
in
the class and follow the superclass pointer which points to NSObject
(1) [MyObject alloc]
将会第一个执行。MyObject类没有实现alloc,于是我们查找 +alloc
失败在这个类中,然后我们跟踪着他的父类指针 NSObject
(2) We ask NSObject
if it responds to +alloc
and
it does. +alloc
checks the receiver
class which is MyObject
and allocates
a block of memory the size of our class and initializes it‘s isa pointer to the MyObject class and we now have an instance and lastly we put +alloc
in NSObject‘s class cache for the class object
我们查询 NSObject
是否对 +alloc
响应,并且的确如此. +alloc
查找接受类 MyObject
并且分配内存,将ISA指针指向MyObject类并且我们有了一个实例,最后我们将alloc放入这个类的缓存中
(3) Up till now we were sending a class messages but now we send an instance message which simply calls -init
or
our designated initializer. Of course our calss responds to that message so -(id)init
get‘s
put into the cache
现在我们正在发送一个类消息,但是我们发送了一个实例消息(被称做init或者我们的构造器)。当然我们的类响应该消息并且放入了cache。
(4) Then self = [super init]
gets called.
Super being a magic keyword that points to the objects superclass so we go to NSObject
and
call it‘s init method. This is done to insure that OOP Inheritance works correctly in that all your super classes will initialize their variables correctly and then you (being in the subclass) can initialize your variables correctly and then override the superclasses
if you really need to. In the case of NSObject, nothing of huge importance goes on, but that is not always the case. Sometimes important initialization happens. Take this…
然后 self = [super inti]被调用。super是一个神奇的关键字指向类的父类(NSObject),并且调用父类的初始化方法。这是为了确保OOP继承工作正常。在所有的父类初始化他们的变量正常之后,你可以初始化你的变量。然后重写你的父类,如果你真的要这么做。在NSObject的情况下,没有什么非常重要的事情进行,但是这并不是常态。有些时候很重要的初始化会发生,考虑:
#import < Foundation/Foundation.h> @interface MyObject : NSObject { NSString *aString; } @property(retain) NSString *aString; @end @implementation MyObject -(id)init { if (self = [super init]) { [self setAString:nil]; } return self; } @synthesize aString; @end int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; id obj1 = [NSMutableArray alloc]; id obj2 = [[NSMutableArray alloc] init]; id obj3 = [NSArray alloc]; id obj4 = [[NSArray alloc] initWithObjects:@"Hello",nil]; NSLog(@"obj1 class is %@",NSStringFromClass([obj1 class])); NSLog(@"obj2 class is %@",NSStringFromClass([obj2 class])); NSLog(@"obj3 class is %@",NSStringFromClass([obj3 class])); NSLog(@"obj4 class is %@",NSStringFromClass([obj4 class])); id obj5 = [MyObject alloc]; id obj6 = [[MyObject alloc] init]; NSLog(@"obj5 class is %@",NSStringFromClass([obj5 class])); NSLog(@"obj6 class is %@",NSStringFromClass([obj6 class])); [pool drain]; return 0; }
Now if you were new to Cocoa and I asked you to guess as to what would be printed you‘d probably say
现在如果你刚接触Cocoa,如果我问你将会打印什么你会说。
NSMutableArray NSMutableArray NSArray NSArray MyObject MyObject
but this is what happens
obj1 class is __NSPlaceholderArray obj2 class is NSCFArray obj3 class is __NSPlaceholderArray obj4 class is NSCFArray obj5 class is MyObject obj6 class is MyObject
This is because in Objective-C there is a potential for +alloc
to
return an object of one class and then -init
to
return an object of another class.
因为在Objective-C中存在这样的可能,alloc返回一个类型,而init返回另外一个类型。
So what happens in objc_msgSend anyway?
在objc_msgSend时发生了什么?
There is actually a lot that happens in objc_msgSend()
.
Lets say we have code like this…
实际上在objc_msgSend()中发生了很多事情。让我看一下假设我们有这样的代码...
[self printMessageWithString:@"Hello World!"];
it actually get‘s translated by the compiler to…
它实际上被编译器翻译成了
objc_msgSend(self,@selector(printMessageWithString:),@"Hello World!");
From there we follow the target objects isa pointer to lookup and see if the object (or any of it‘s superclasses) respond to the selector @selector(printMessageWithString:)
.
Assuming we find the selector in the class dispatch table or it‘s cache we follow the function pointer and execute it. Thus objc_msgSend()
never
returns, it begins executing and then follows a pointer to your methods and then your methods return, thus looking like objc_msgSend()
returned.
Bill Bumgarner went into much more detail ( Part
1, Part
2& Part
3) on objc_msgSend()
than
I will here. But to summarize what he said and what you‘d see looking at the Objective-C runtime code… 从这里我们跟踪目标对象的ISA指针来查找和判断这个对象是否响应
@selector(printMessageWithString:)。假设我们找到了这个选择器在这个类的dispatch表或者cache中。我们循着函数指针然后执行它。然后objc_msgSend()从来不返回。它开始执行,并且跟踪指向你的方法的指针,然后你的方法返回。这看起来像objc_msgSend()返回的一样。Bill Bumgarner 想要解释更多的东西关于objc_msgSend()比我在这里。但是总结起来,他说的和你在运行时看到的。
1. Checks for Ignored Selectors & Short Circut - Obviously if we are running under garbage collection we can ignore calls to -retain
,-release
,
etc 检查忽略的Selector和ShortCircuit,很明显当我们运行在有垃圾回收机制的情况的时候我们将会忽略retai和release等等2.
Check for nil target. Unlike other languages messaging nil in Objective-C is perfectly legal & there are some valid reasons you‘d want to. Assuming we have a non nil target we go on…检查nil目标。不想其他语言,nil在objective-C中是一个完全合法,并且这里有很多原因你也愿意这样。如果我们有一个非空的对象我们将会继续 3.
Then we need to find the IMP on the class, so we first search the class cache for it, if found then follow the pointer and jump to the function然后我们查找IMP在这个雷尚,我们现在cache中检查它,如果找到了就循着指针跳转到这个函数 4.
If the IMP isn‘t found in the cache then the class dispatch table is searched next, if it‘s found there follow the pointer and jump to the pointer如果IMP没有在cache中找到,我们就检查dispatch
table。如果找到了我们就跳转到这个函数之行 5. If the IMP isn‘t found in the cache or class dispatch table then we jump to the forwarding mechanism
This means in the end your code is transformed by the compiler into C functions. So a method you write like say…
如果IMP没有在cahce和dispatch table中找到,我们就会跳转到转发机制。这意味着在你的代码被编译器转化成了C函数。
-(int)doComputeWithNum:(int)aNum
would be transformed into...
int aClass_doComputeWithNum(aClass *self,SEL _cmd,int aNum)
And the Objective-C Runtime calls your methods by invoking function pointers to those methods. Now I said that you cannot call those translated
methods directly, however the Cocoa Framework does provide a method to get at the pointer…
并且Objective-C运行时通过调用对应的函数指针来调用你的方法。现在我说你不能直接调用这些翻译后的方法,虽然cocoa framework提供了方法来获取这些指针。
//declare C function pointer int (computeNum *)(id,SEL,int); //methodForSelector is COCOA & not ObjC Runtime //gets the same function pointer objc_msgSend gets computeNum = (int (*)(id,SEL,int))[target methodForSelector:@selector(doComputeWithNum:)]; //execute the C function pointer returned by the runtime computeNum(obj,@selector(doComputeWithNum:),aNum);
In this way you can get direct access to the function and directly invoke it at runtime and even use this to circumvent the dynamism of
the runtime if you absolutely need to make sure that a specific method is executed. This is the same way the Objective-C Runtime invokes your method, but using objc_msgSend()
.
通过这种方法你可以直接找到函数入口并且在运行时调用它。甚至使用它改变运行时,当你真的想这么做,并且知道什么方法被执行的时候。这是同样的方法来之行你的方法,但是应该使用objc_msgSend();
Objective-C Message Forwarding
Objective-C消息转发
In Objective-C it‘s very legal (and may even be an intentional design decision) to send messages to objects to which they don‘t know how to respond to. One reason Apple gives for this in their docs is to
simulate multiple inheritance which Objective-C doesn‘t natively support, or you may just want to abstract your design and hide another object/class behind the scenes that deals with the message. This is one thing that the runtime is very necessary for. It
works like so
在Objective-c中发送一个消息给一个你不知道它会怎么响应的对象是完全合法的(甚至有些是有是一种设计模式)。苹果在他们的文档中给出的一个原因是来模拟没有提供的多重继承。或者你想绝对化你的设计,并且隐藏消息实现。这是一件运行时非常需要的事情。它这样工作
1. The Runtime searches through the class cache and class dispatch table of your class and all the super classes, but fails to to find the
specified method
运行时检查累缓存和类的dipatch表还有他的父类的,但是没有找到特定方法
2. The Objective-C Runtime will call +
on your class. This gives you a chance to provide a method implementation and tell the runtime
(BOOL) resolveInstanceMethod:(SEL)aSEL
that you‘ve resolved this method and if it should begin to do it‘s search it‘ll find the method now. You could accomplish this like so... define a function…
运行时将会调用在你的类上调用 + (BOOL) resolveInstanceMethod:(SEL)aSEL 。这给了你一个机会去提供一个方法实现并且告诉运行时你如何解决这个方,并且如果它开始检查将会得到这个方法。你必须这样做:
void fooMethod(id obj, SEL _cmd) { NSLog(@"Doing Foo"); }
you could then resolve it like so using class_addMethod()
...
+(BOOL)resolveInstanceMethod:(SEL)aSEL { if(aSEL == @selector(doFoo:)){ class_addMethod([self class],aSEL,(IMP)fooMethod,"[email protected]:"); return YES; } return [super resolveInstanceMethod]; }
The "[email protected]:" in the last part of class_addMethod()
is
what the method is returning and it‘s arguments. You can see what you can put there in the Type
Encodings section of the Runtime Guide.
在函数最后的“[email protected]:”是这个函数方法的返回值和参数列表。你可以查看Type Encodings章节看看你能够在这里放些什么。
3. The Runtime then calls -
. What this does is give you a chance (since we couldn‘t resolve the method (see #2
(id)forwardingTargetForSelector:(SEL)aSelector
above)) to point the Objective-C runtime at another object which should respond to the message, also this is better to do before the more expensive process of invoking -
takes over. You could implement it like so
(void)forwardInvocation:(NSInvocation *)anInvocation
运行时然后调用方法 - (id)forwardingTargetForSelector:(SEL)aSelector。这样做给了你一个机会重定向运行时到另外一个可以响应该消息的对象。这比之行花费更多的要好。你可以这么做
- (id)forwardingTargetForSelector:(SEL)aSelector { if(aSelector == @selector(mysteriousMethod:)){ return alternateObject; } return [super forwardingTargetForSelector:aSelector]; }
Obviously you don‘t want to ever return self from this method or it could result in an infinite loop.
很明显你不会想在这个方法中返回self,这回导致一个死循环。
4. The Runtime then tries one last time to get a message sent to it‘s intended target and calls -
. If you‘ve never seen
(void)forwardInvocation:(NSInvocation *)anInvocationNSInvocation
,
it‘s essentially an Objective-C Message in object form. Once you have an NSInvocation you essentially can change anything about the message including it‘s target, selector & arguments. So you could do…
运行时将会至少尝试一次将消息发送到这个重定向的目标上。如果你从来没有见到错NSInvocation,这是一个很重要的Objective-C对象形式的消息。一旦你有了一个NSINvocation你可以改变任何东西包括他的目标,方法,参数。任何你可以做的。。。。
-(void)forwardInvocation:(NSInvocation *)invocation { SEL invSEL = invocation.selector; if([altObject respondsToSelector:invSEL]) { [invocation invokeWithTarget:altObject]; } else { [self doesNotRecognizeSelector:invSEL]; } }
by default if you inherit from NSObject it‘s -
implementation simply calls
(void)forwardInvocation:(NSInvocation *)anInvocation-doesNotRecognizeSelector:
which
you could override if you wanted to for one last chance to do something about it.
默认情况下继承NSobject的方法实现,只是简单的调用 -doesNotRecognizeSelector: 。你可以重载这个方法,如果你想获得一次机会做点什么。
Non Fragile ivars (Modern Runtime)
没有易损的实例
One of the things we recently gained in the modern runtime is the concept of Non Fragile ivars. When compiling your classes a ivar layout is made by the compiler that shows where to access your ivars in
your classes, this is the low level detail of getting a pointer to your object, seeing where the ivar is offset in relation to the beginning of the bytes the object points at, and reading in the amount of bytes that is the size of the type of variable you
are reading in. So your ivar layout may look like this, with the number in the left column being the byte offset.
一个我们最近从现代运行时中华uode的好处没有易碎的Ivars的概念。当编译你的类时,一个ivar将会被编译器制造依赖显示从哪里来进入你类的的ivars。看一下相对于类开始处的偏移量,然后这个bytes数量就是你的变量的大小。于是你的ivar就想这样
Here we have the ivar layout for NSObject and then we subclass NSObject to extend it and add on our own ivars. This works fine until Apple
ships a update or all new Mac OS X 10.x release and this happens
这里我们有一个NSObject的ivar,然后我们继承NSObject并且添加我们自己的ivar。这个在MAC OS X 10.x上工作良好。
Your custom objects get wiped out because we have an overlapping superclass. The only alternative that could prevent this is if Apple sticked
with the layout it had before, but if they did that then their Frameworks could never advance because their ivar layouts were frozen in stone. Under fragile ivars you have to recompile your classes that inherit from Apples classes to restore compatibility.
So what Happens under non fragile ivars?
你自定义的对象被擦除掉了,因为我们有一个父类。一个可选的方法来阻止这个发生的方式是如果苹果保持和原先的layout的联系,但是如果他们这样做的话他们的framewors将会永远被冻结。在易损的ivar上你必须重新编译你的类来从苹果的累继承以兼容。所以发生了什么?
Under Non Fragile ivars the compiler generates the same ivar layout as under fragile ivars. However when the runtime detects an overlapping
superclass it adjusts the offsets to your additions to the class, thus your additions in a subclass are preserved.
在Non fragile ivars下编译器产生了相同的ivar 与fragile ivars相比。无论怎样,如果运行时检测到一个父类适合你的类,那么你添加的东西将会被保留。
Objective-C Associated Objects
Objective-C 联合对象
One thing recently introduced in Mac OS X 10.6 Snow Leopard was called Associated References. Objective-C has no support for dynamically adding on variables to objects unlike some other languages that have
native support for this. So up until now you would have had to go to great lengths to build the infrastructure to pretend that you are adding a variable onto a class. Now in Mac OS X 10.6, the Objective-C Runtime has native support for this. If we wanted to
add a variable to every class that already exists like say NSView we could do so like this…
一个最近被添加进 Mac OS X 10.6 Snow Leopard 的东西是联合引用。Objective-c不支持动态添加成员变量,不像其他一些原生支持这个的语言。所以在次之前你必须构件一个非常大的数据结构来假装你正在个体一个类添加一个变量。现在在 Mac OS X 10.6 Snow Leopard
运行时原生支持这个。如果我们想要添加一个变量给每一个已经存在的类,比如NSView,我们可以这么做
#import < Cocoa/Cocoa.h> //Cocoa #include < objc/runtime.h> //objc runtime api’s @interface NSView (CustomAdditions) @property(retain) NSImage *customImage; @end @implementation NSView (CustomAdditions) static char img_key; //has a unique address (identifier) -(NSImage *)customImage { return objc_getAssociatedObject(self,&img_key); } -(void)setCustomImage:(NSImage *)image { objc_setAssociatedObject(self,&img_key,image, OBJC_ASSOCIATION_RETAIN); } @end
you can see in runtime.h the options for how to store the values passed to objc_setAssociatedObject()
.
/* Associated Object support. */ /* objc_setAssociatedObject() options */ enum { OBJC_ASSOCIATION_ASSIGN = 0, OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, OBJC_ASSOCIATION_COPY_NONATOMIC = 3, OBJC_ASSOCIATION_RETAIN = 01401, OBJC_ASSOCIATION_COPY = 01403 };
These match up with the options you can pass in the @property
syntax.
Hybrid vTable Dispatch
If you look through the modern runtime code you‘ll come across this (in objc-runtime-new.m)...
/*********************************************************************** * vtable dispatch * * Every class gets a vtable pointer. The vtable is an array of IMPs. * The selectors represented in the vtable are the same for all classes * (i.e. no class has a bigger or smaller vtable). * Each vtable index has an associated trampoline which dispatches to * the IMP at that index for the receiver class‘s vtable (after * checking for NULL). Dispatch fixup uses these trampolines instead * of objc_msgSend. * Fragility: The vtable size and list of selectors is chosen at launch * time. No compiler-generated code depends on any particular vtable * configuration, or even the use of vtable dispatch at all. * Memory size: If a class‘s vtable is identical to its superclass‘s * (i.e. the class overrides none of the vtable selectors), then * the class points directly to its superclass‘s vtable. This means * selectors to be included in the vtable should be chosen so they are * (1) frequently called, but (2) not too frequently overridden. In * particular, -dealloc is a bad choice. * Forwarding: If a class doesn‘t implement some vtable selector, that * selector‘s IMP is set to objc_msgSend in that class‘s vtable. * +initialize: Each class keeps the default vtable (which always * redirects to objc_msgSend) until its +initialize is completed. * Otherwise, the first message to a class could be a vtable dispatch, * and the vtable trampoline doesn‘t include +initialize checking. * Changes: Categories, addMethod, and setImplementation all force vtable * reconstruction for the class and all of its subclasses, if the * vtable selectors are affected. **********************************************************************/
The idea behind this is that the runtime is trying to store in this vtable the most called selectors so this in turn speeds up your app
because it uses fewer instructions thanobjc_msgSend
.
This vtable is the 16 most called selectors which make up an overwheling majority of all the selectors called globally, in fact further down in the code you can see the default selectors for Garbage Collected & non Garbage Collected apps...
static const char * const defaultVtable[] = { "allocWithZone:", "alloc", "class", "self", "isKindOfClass:", "respondsToSelector:", "isFlipped", "length", "objectForKey:", "count", "objectAtIndex:", "isEqualToString:", "isEqual:", "retain", "release", "autorelease", }; static const char * const defaultVtableGC[] = { "allocWithZone:", "alloc", "class", "self", "isKindOfClass:", "respondsToSelector:", "isFlipped", "length", "objectForKey:", "count", "objectAtIndex:", "isEqualToString:", "isEqual:", "hash", "addObject:", "countByEnumeratingWithState:objects:count:", };
So how will you know if your dealing with it? You‘ll see one of several methods called in your stack traces while your debugging. All of
these you should basically treat just like they are objc_msgSend()
for
debugging purposes...objc_msgSend_fixup
happens
when the runtime is assigning one of these methods that your calling a slot in the vtable.objc_msgSend_fixedup
occurs
when your calling one of these methods that was supposed to be in the vtable but is no longer in there objc_msgSend_vtable[0-15]
you‘ll
might see a call to something like objc_msgSend_vtable5
this
means you are calling one of these common methods in the vtable. The runtime can assign and unassign these as it wants to, so you shouldn‘t count on the fact that objc_msgSend_vtable10
corresponds
to -length
on
one run means it‘ll ever be there on any of your next runs.
Conclusion
I hope you liked this, this article essentially makes up the content I covered in my Objective-C Runtime talk to the Des
Moines Cocoaheads (a lot to pack in for as long a talk as we had.) The Objective-C Runtime is a great piece of work, it does a lot
powering our Cocoa/Objective-C apps and makes possible so many features we just take for granted. Hope I hope if you haven‘t yet you‘ll take a look through these docs Apple has that show how you can take advantage of the Objective-C Runtime. Thanks! Objective-C
Runtime Programming Guide Objective-C
Runtime Reference
参考文章:http://cocoasamurai.blogspot.com/2010/01/understanding-objective-c-runtime.html