原文作者:COCOA SAMURAI
链接:http://cocoasamurai.blogspot.jp/2010/01/understanding-objective-c-runtime.html
理解Objective-C Runtime
Objective-C的运行系统是最初时,人们一般都引入到 Cocoa/ Objective-C的Objective-C中的被忽略的功能之一。这样做的原因是,虽然Objective-C的(语言),很容易在只有几个小时的回暖,初来乍到可可花大部分的时间绕所述Cocoa框架他们的头和适应它是如何工作。然而,运行时的东西,每个人都至少应该知道它是如何工作的一些细节超出知道这样的代码[target doMethodWith:varl]; 被转换为[objc_msgSend:(target,@selector(doMethodWiht:),var)];由编译器。知道什么是Objective-c运行正在做将帮助你获得的Objective-c本身,以及如何您的应用程序运行的更加深入的理解。我想让Mac/iPhone 开发人员将获得一些东西,不管你的经验水平。
Objective-C Runtime是开源的,并可随时从http://opensource.apple.com获取信息。其实检查的Objective-C是我经历弄清楚它是如何工作,超出了对此事的阅读文档苹果第一途径之一。您可以下载运行时的当前版本(如本,书写)的Mac OS X 10.6.2这里objc4-437.1.tar.gz
动态和静态语言:Objective-C是一种面向运行时的语言,这意味着当它是可能的它推迟关于什么实际上从编译和链接时执行时它实际上在运行时执行的决定。这给你一个很大的灵活性,您可以根据您的需要或您甚至可以故意掉的方法实现,等等。这需要使用它可以内省对象的运行时间,看看他们做的和不要什么消息重定向到合适的对象响应和调用方法适当。如果我们对比这一门语言C , 在C中方法开始与main() 方法,然后从那里几乎是以下你的逻辑和你写的代码执行功能的自上而下的设计。AC结构不能将请求转发给执行功能到其他目标。几乎你有像这样的程序。
#include <stdio.h>
int main(int argh, const char **argv[]){
printf("Hello World!");
return 0;
}
一个编译器解析,优化,然后将您的优化代码汇编
.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!"
然后用库相链接在一起,并产生一个可执行文件。这和Objective-C的对比,而这个过程是类似的,编译器生产的代码依赖于Objective-C的运行时库的存在。当我们都开始引入到Objective-C的告诉我们,(在一个简单层面)会发生什么我们的Objective-C的支架代码是一样的东西...
[self doSomethingWithVar:var1];
被翻译成...
objc_msgSend(self,@selector(doSomethingWithVar:),var1);
但除了这点我们真的不知道多少,直到很久以后什么运行时正在做什么.
什么是Objective-c运行时?
Objective-C的运行时是一个运行库,它主要是写在C&汇编程序库,增加了面向对象的能力,C创建的Objective-C。这意味着它在加载类的信息,做了所有方法调度,转发的方法等。Objective-C运行本质上创建了所有的支撑结构,使与Objective-C的面向对象编程的可能。
Objective-c的运行时的术语
所以我们进一步,让我们把一些术语的所以我们都在同一个页面上一切。2运行时至于Mac和iPhone开发者都在关注有2个运行时:现代运行与遗产运行现代运行时:涵盖了所有64位Mac OS X的应用程序和所有的iPhone OS应用程序:涵盖一切(all 32 bit Mac OS X Apps)的方法有两种基本类型的方法.实例方法(begin with a '-' like -(void)doFoo。即对对象实例操作和类方法(begin with a '+' like + (id)alloc。他们是一个。代码分组执行一个小任务一样。
-(NSString *)movieTitle
{
return @"Futurama: Into the Wild Green Yonder";
}
选择在Objectie-C选择器本质上是一个C数据结构充当平均值来确定你想要一个对象执行一个Objctive-C的方法。在运行时他们时这样的...
typedef struct objc_selector *SEL;
并用这样的...
SEL aSel = @selector(movieTitle);
信息
[target getMovieTitleForObject:obj];
一个Objective-C的消息时2括号"[]",由目标的您要发送信息,你想让它执行的方法和任何参数要发送它的一切。类似的C函数调用而A Objective-C的消息是不同的。你将消息发送到一个对象的事实并不意味着它会执行它。该对象可以检查邮件的发件人是基于谁上决定执行不同的方法或将消息转发到不同的目标对象。类如果你看一下在运行时的一类,你会遇到这样..
typedef struct objc_class *Class;
typedef struct objc_object{
Class isa;
}*id;
这里有几件事情怎么回事。我们有一个Objective-C类一个结构和对象的结构。所有objc_object已是定义为一个ISA类指针,这就是我们所说的术语“isa指针”。这isa指针是所有的Objective-C运行时需要检查的对象,看看它的类,然后开始看它是否响应,当你短信的对象选择器。,最后我们看到了ID指针。默认情况下,该ID指针没有告诉我们的Objective-C对象除了他们是Objective-C的对象。当你有一个ID指针,然后你可以问这个对象为它的课,看它是否响应的方法等,然后采取更具体,当你知道对象是什么,你指向。你可以看到这个问题,以及对在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本身呗设计成与Objective-C运行兼容,所以他们作为处理对象,这样他们可以响应消息像-retain,-release,-copy等IMP(方法实现)
typedef id (*IMP) (id self,SEL _cmd,...)
IMP的是函数指针的方法实现,编译器会为您生成。如果你的新的Objective-C,你不需要处理这些直接与直到很久以后,但是这是Objective-C的运行时如何调用你的方法,因为我们很快就会看到。Objective-C类那么,在Objectve-C类?在Objective-C的基本实现一类的样子。
@interface MyClass : NSObject {
//vars
NSInteger counter;
}
//methods
-(void)doFoo;
@end
但运行时有比这更保持跟踪
#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
我们可以看到一个类有一个参考它的父类,它的名字,实例变量,方法,高速缓存和它声称坚持协议。响应消息时,该消息类或它的实例运行时需要此信息
因此,类定义对象,但对象是自己?这个怎么用
前面是我说的,在Objective-C类本身也是对象,以及和运行时涉及此通过创建元类。当您发送这样的消息[NSObject alloc]你实际上是将消息发送到类对象,而该类对象必须是元类本身是根元类的实例的实例。而如果你说从NSObject的子类,类分NSObject的,因为它的超类。但是,所有的元类指向根元类作为其超类。所有元类只是有消息他们的方法列表类的方法,他们做出回应。所以,当你发送消息给类对象像[NSObject alloc]然后objc_msgSend()实际上看起来通过元类来看看它响应那么如果它找到一个方法,操作类对象上。为什么我们从苹果类的子类所以,当你开始Cocoa开发初期,教程都说做事情像NSObject的子类,并开始编码,然后东西,你只需从苹果类继承享受了很多好处。你甚至不知道,一件事发生了,你是设置你的对象了与Objective-C运行工作。当我们分配我们的一个类的实例,它的工作是这样的...
MyObject *object = [[MyObject alloc]init];
这被执行的第一消息是 +alloc,如果你看了苹果文档,它说:"新的实例的实例ISA变量初始化为描述类的数据结构,内存中所有其他的实例变量设置为0."因此,通过从苹果类的继承,我们不但继承了一些伟大的属性,但我们继承到内存中相匹配的结构,运行时希望(与指向我们班一个isa指针)轻松地分配和创造我们的物体的能力与的大小我们班的
那么什么是类缓存? ( objc_cache *cache )
当Objective-C运行按照检查的对象它的isa指针,它可以找到一个实现许多方法的对象。然而,你可能只调用其中的一小部分,这是没有意义的搜索类调度表的所有选择每次做了查找时间。因此,该类实现缓冲,无论何时你搜索通过调度类表,发现它把那到它的缓存相应的选择。所以,当objc_msgSend()看起来通过类的选择将搜索类缓存第一。此操作的理论,如果你调用一个消息在类一次,你很可能稍后再打电话就可以了同样的消息。因此,如果我们考虑到这一点,这意味着,如果我们有一个子类NSObject的叫MyObject来,然后运行下面的代码
MyObject *obj = [[MyObject alloc] init];
@implementation MyObject
-(id)init {
if(self = [super init]){
[self setVarA:@”blah”];
}
return self;
}
@end
发生以下情况(1)[myObject alloc]最先被执行。为MyObject类没有实现页头,所以我们将无法找到+alloc中的类,并按照超类的指针指向NSObject的(2)问NSObject的,如果它响应+alloc 和它的作用。+alloc检查接收器类是为MyObject和分配存储内存块大小,并初始化它的isa指针到MyObject来类的,现在我们有一个实例,最后我们把+alloc 中的NSObject的类缓存类对象(3)到现在为止我们发送邮件类,但现在我们给它只是调用一个实例消息-init或我们指定的初始化。当然,我们班响应该消息,因此- (ID)初始获得的投入缓存(4)然后self= [super init]被调用。超级成为一个神奇的关键字指向对象的父类,所以我们去的NSObject并调用它的init方法。这样做是为了确保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;
}
现在如果你是Cocoa 你感觉会打印什么可能
NSMutableArray
NSMutableArray
NSArray
NSArray
MyObject
MyObject
但是下面是打印结果
obj1 class is __NSPlaceholderArray
obj2 class is NSCFArray
obj3 class is __NSPlaceholderArray
obj4 class is NSCFArray
obj5 class is MyObject
obj6 class is MyObject
这是因为在Object-C有一个潜在的+alloc 可以返回一个类的对象,然后-init返回另一个类的对象。
在objc_msgSend()会发生什么?
其实在很多是发生在objc_msgSend(),比如说,这样的代码
[self printMessageWithString:@"Hello World!"];
它实际上到翻译器上翻译成...
objc_msgSend(self,@selector(printMessageWihtString),@"Hello World!");
从那里,我们跟随目标对象isa指针来查找,看看对象(或任何它的超类)来选择响应@selector(printMessageWithString :)。假设我们发现在类调度表中选择,或者它的缓存我们遵循的函数指针并执行它。因此objc_msgSend()永远不会返回,它开始执行然后跟随一个指向你的方法,然后你的方法返回,从而看起来像objc_msgSend()返回。比尔·布姆加纳走 进更多的细节(第一部分,第二部分和第三部分)上objc_msgSend()比我会在这里。但是总结一下,他说什么你会看到在看的Objective-C运行时代码... 1.检查被忽略的选择器及短期CIRCUT -显然,如果我们在垃圾收集,我们可以忽略呼叫下运行-retain,-释放等2.检查零的目标。不像其他的语言在Objective-C消息传递零是完全合法和有你想要的一些正当的理由。假设我们有一个非零的目标,我们去... 3.然后我们需要找到IMP的类,所以我们先搜索类缓存它,如果找到则按照指针跳到功能4.如果在IMP是不在缓存中,然后在类分派表旁边搜索,但如果它的发现有跟随指针,跳转到指针5.如果在缓存或类分派表中未找到该IMP那么我们跳到发现转发机制这意味着最终你的代码被编译成C函数转化。因此,一个方法,你写这样说...
-(int)doComputeWithNum:(int)aNum
将被转化成...
INT aClass_doComputeWithNum(aClass *self,SEL _cmd,int aNum)
和Objective-C的运行时通过调用函数指针这些方法调用你的方法。现在,我说你不能直接调用这些方法翻译,但是Cocoa框架确实提供了一个方法来获得在指针...
//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);
通过这种方式,你可以得到的功能直接访问,并在运行时直接调用它,甚至用它来 规避运行时的活力,如果你确实需要确保执行的具体方法。这是相同的方式Objective-C的运行时调用你的方法,但使用objc_msgSend()。
Objective-C的消息转发
在Objective-C是非常合法的(甚至可能是故意设计决定)将消息发送到对象,他们不知道该如何应对。其中一个原因苹果在他们的文档给出了这是模拟多重继承其中的Objective-C本身不支持,或者你可能只是想抽象的设计,并隐藏该消息涉及幕后另一个对象/类。这是一件事,运行时间是非常必要的。它的工作原理是这样1.运行时会自动搜索你的类和所有超类的三级缓存和类分派表,但未能找到指定的方法2. Objective-C的运行时将调用+(BOOL)resolveInstanceMethod :( SEL)ASEL在你的类。这给你一个机会,提供一个方法的实现,并告诉你已经解决了这个方法的运行时间和是否应该开始做它的搜索现在会找到方法。你可以做到这一点,像这样......定义一个函数...
void fooMethod(id obj, SEL _cmd)
{
NSLog(@"Doing Foo");
}
那么你可以解决它像这样使用class_addMethod()...
+(BOOL)resolveInstanceMethod:(SEL)aSEL
{
if(aSEL == @selector(doFoo:)){
class_addMethod([self class],aSEL,(IMP)fooMethod,"v@:");
return YES;
}
return [super resolveInstanceMethod];
}
的“V @:”中的最后部分class_addMethod()是该方法返回和它的参数。你可以看到你可以在把那里类型的编码的运行指南的部分。3.运行然后调用- (ID)forwardingTargetForSelector:(SEL)aSelector。这样做是给你一个机会,(因为我们无法解析方法(参见上面的#2)),以指向另一个对象的Objective-C运行时,应响应消息,也这是在好转之前更多的事情要做调用的昂贵的过程- (无效)forwardInvocation:(NSInvocation的*)anInvocation接管。你可以实现它像这样
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
显然,你不希望永远从这个方法返回的自我,也可能导致一个无限循环。4.运行时将尝试最后一次来获取发送到它的预期目标,并呼吁消息- (无效)forwardInvocation:(NSInvocation的*)anInvocation。如果你从来没有见过NSInvocation的,它本质上是在对象形式的Objective-C的消息。一旦你有一个NSInvocation的本质,你可以改一下 ,包括它的目标,选择和参数的任何消息。所以,你可以做...
-(void)forwardInvocation:(NSInvocation *)invocation
{
SEL invSEL = invocation.selector;
if([altObject respondsToSelector:invSEL]) {
[invocation invokeWithTarget:altObject];
} else {
[self doesNotRecognizeSelector:invSEL];
}
}
默认情况下如果你继承NSObject -(void)forwardInvocation:(NSInvocation *)anInvocation实现简单地调用-doesNotRecognizeSelector:你可以覆盖一个最后的机会如果你想做点什么。
Non Fragile ivars (Modern Runtime)
我们最近的一件事了在现代运行时非脆弱的实例变量的概念。当编译类ivar布局是由编译器显示访问你的类实例变量,这是低水平的细节得到对象的指针,看到在ivar抵消与字节对象的开始点,和阅读量的大小的字节类型的变量你阅读。所以你ivar布局看起来像这样,左栏中的数字是字节偏移量。
这里我们有ivar布局NSObject然后我们NSObject子类扩展它,并添加自己的实例变量。这个没问题,直到苹果船舶更新或新Mac OS X 10。x版本和发生这种情况
您的自定义对象得到消灭了,因为我们有一个重叠的超类。唯一的选择,可以防止这种情况如果苹果之前的布局,但是,如果他们这么做了,那么他们的框架不可能进步,因为他们的ivar布局冻在石头上的。在脆弱的实例变量,你必须重新编译类继承苹果类恢复兼容性。所以在非脆弱的实例变量,会发生什么?
在非脆弱的实例变量编译器生成相同的ivar布局下脆弱的实例变量。然而当运行时检测到一个重叠的超类,它调整偏移量添加到类,因此您添加在子类中被保留。
objective - c相关联的对象
最近有一件事在Mac OS X 10.6中引入的雪豹被称为引用相关联。objective - c不支持动态添加变量对象与一些其他语言的本地支持。所以直到现在你将不得不竭尽全力构建基础设施来假装你是在一个类添加一个变量。现在在Mac OS X 10.6中,objective - c运行时的原生支持。如果我们想要添加一个变量到每一个已经存在的类比如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
你可以看到在运行时。h选项如何存储的值传递给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
};
这些匹配的选项可以通过@ property语法。
Hybrid vTable Dispatch
如果你看看现代运行时代码你会遇到这种(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.
**********************************************************************/
背后的想法是,运行时尝试存储在这vtable最叫选择器这反过来加速你的应用比objc_msgSend因为它使用更少的指令。这种vtable 16最被称为选择器组成一个overwheling多数选择器被称为全球,事实上进一步在代码中你可以看到垃圾收集的默认选择器和非垃圾收集应用程序…
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:",
};
所以你将如何知道你处理吗?你会看到几个方法之一被称为堆栈跟踪调试。基本上所有的这些你应该对待他们一样objc_msgSend()用于调试目的……objc_msgSend_fixup当运行时分配这些方法之一是你的vtable调用插槽。objc_msgSend_fixedup发生当你调用一个方法应该是在vtable但是不再是objc_msgSend_vtable(0-15)你可能会看到调用类似objc_msgSend_vtable5这意味着你vtable调用其中一个常见的方法。运行时可以分配和unassign这些想要,所以你不应该指望objc_msgSend_vtable10对应长度在一个运行意味着它永远会在你的下一个运行。
结论:
我希望你喜欢这,这篇文章基本上构成内容我在objective - c运行时跟覆盖Des Moines Cocoaheads(很多包的演讲。)objective - c运行时是一个伟大的作品,它很多驱动我们的可可/ objective - c应用程序,使可能的很多功能我们只是理所当然。希望我希望如果你还没有通过这些文档你看一下苹果公司,向您展示如何利用objective - c运行时。谢谢!objective - c运行时编程指南 objective - c运行时参考
Posted by Colin Wheeler at 3:53 PM
Labels: CocoaHeads, Objective-C, Objective-C Runtime
网友评论