[target
doMethodWith:var1];
gets translated into 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.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#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…
[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.
-(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-(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…
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…
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
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)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
@interface MyClass : NSObject { //vars NSInteger counter; } //methods -(void)doFoo; @endbut 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; #endifWe 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.
[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.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.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 codeMyObject *obj = [[MyObject alloc] init]; @implementation MyObject -(id)init { if(self = [super init]){ [self setVarA:@”blah”]; } return self; } @endthe following happens
[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
[MyObject alloc]
将会第一个执行。MyObject类没有实现alloc,于是我们查找 +alloc
失败在这个类中,然后我们跟踪着他的父类指针 NSObject
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放入这个类的缓存中-init
or
our designated initializer. Of course our calss responds to that message so -(id)init
get‘s
put into the cacheself = [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…#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
NSMutableArray NSMutableArray NSArray NSArray MyObject MyObjectbut 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 MyObjectThis 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.objc_msgSend()
.
Lets say we have code like this…[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()比我在这里。但是总结起来,他说的和你在运行时看到的。-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…-(int)doComputeWithNum:(int)aNumwould 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…
//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()
.+
(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…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.-
(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 { 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.
-
(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…-(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.#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); } @endyou 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./*********************************************************************** * 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 that objc_msgSend_vtable10
corresponds
to -length
on
one run means it‘ll ever be there on any of your next runs.
参考文章:http://cocoasamurai.blogspot.com/2010/01/understanding-objective-c-runtime.html
Understanding the Objective-C Runtime
原文地址:http://blog.csdn.net/jiushijie1207/article/details/46696689