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 doMethodWith:var1];
gets translated intoobjc_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開發者將可以從這裏獲取不少東西,忽略掉你的經驗。html
The Objective-C Runtime is Open Sourceobjective-c
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結構體不能直接的講一個函數運行在一個對象上。就像這個程序:
api
#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代碼app
[self doSomethingWithVar:var1];
gets translated to…less
被翻譯成
iphone
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 + (id)alloc
. Methods are just like C Functions in that they are a grouping of code that performs a small task like
因而在咱們更進一步以前,咱們先了解一些在這個頁面中出現的基本術語。對於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 alloc]
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 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 alloc]
then 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 whenobjc_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和Short Circuit,很明顯當咱們運行在有垃圾回收機制的狀況的時候咱們將會忽略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 usingobjc_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 + (BOOL) resolveInstanceMethod:(SEL)aSEL
on your class. This gives you a chance to provide a method implementation and tell the runtime 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,"v@:"); return YES; } return [super resolveInstanceMethod]; }
The "v@:" 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.
在函數最後的「v@:」是這個函數方法的返回值和參數列表。你能夠查看Type Encodings章節看看你可以在這裏放些什麼。
3. The Runtime then calls - (id)forwardingTargetForSelector:(SEL)aSelector
. What this does is give you a chance (since we couldn't resolve the method (see #2 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 - (void)forwardInvocation:(NSInvocation *)anInvocation
takes over. You could implement it like so
運行時而後調用方法 - (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 - (void)forwardInvocation:(NSInvocation *)anInvocation
. If you've never seen NSInvocation
, 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 - (void)forwardInvocation:(NSInvocation *)anInvocation
implementation simply calls -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 than objc_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 thatobjc_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
Posted by Colin Wheeler at 3:53 PM
Labels: CocoaHeads, Objective-C, Objective-C Runtime