(original - Mike Ash, taken from here )Many Cocoa developers have a rather vague idea of the Objective-C Runtime API. They know that it exists somewhere there (some do not even know it!), That it is important, and Objective-C is not functional without it, but usually all knowledge is limited to it.
Today I will talk about how Objective-C works at the Runtime level and how you can use it.
Objects
In Objective-C, we are constantly dealing with objects, but what exactly is an object? Let's try to build something that will help shed light on this question.
')
First, we all know that we refer to objects using pointers, for example,
NSObject * . We also know that we create them using
+ alloc . All we can learn about this from the documentation is that this happens by calling
+ allocWithZone:. Continuing our research chain, we find the
NSDefaultMallocZone , which is created using ordinary
malloc . And that's it!
But what are the created objects? Well, let's see:
#import <Foundation/Foundation.h> @interface A : NSObject { @public int a; } @end @implementation A @end @interface B : A { @public int b; } @end @implementation B @end @interface C : B { @public int c; } @end @implementation C @end int main(int argc, char **argv) { [NSAutoreleasePool new]; C *obj = [[C alloc] init]; obj->a = 0xaaaaaaaa; obj->b = 0xbbbbbbbb; obj->c = 0xcccccccc; NSData *objData = [NSData dataWithBytes:obj length:malloc_size(obj)]; NSLog(@"Object contains %@", objData); return 0; }
We built a hierarchy of classes, each of which contains variables, and filled them with quite obvious values. We then retrieved the data in a digestible form using
malloc_size to get the correct length and used
NSData to print everything in hex. Here's what we got at the exit:
2009-01-27 15:58:04.904 a.out[22090:10b] Object contains <20300000 aaaaaaaa bbbbbbbb cccccccc>
We see that the class has consistently filled the memory cells — first the variable A, then its successor B, and then C. Everything is simple!
But what about
20.3 million at the very beginning? They come before A, and therefore, most likely, belong to
NSObject . Let's look at the definition of
NSObject .
@interface NSObject { Class isa; }
As you can see, again some kind of variable. But what is this
Class ? We pass by the definition that
Xcode offers us and we get into
usr / include / objc / objc.h , in which we find the following:
typedef struct objc_class *Class;
Go ahead to /usr/include/objc/runtime.h and see:
struct objc_class { Class isa; #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 } OBJC2_UNAVAILABLE;
Thus,
Class is a pointer to a structure that ... Starts with another
Class.Let's see another class,
NSProxy @interface NSProxy { Class isa; }
And here he is. One more, id, behind which any object in Objective-C can hide
typedef struct objc_object { Class isa; } *id;
And again he. Obviously, every object in Objective-C must begin with
Class isa , even class objects. So what is it?
As the name and type imply, the isa variable indicates which class this or that object belongs to. Every object in Objective-C must begin with
isa , otherwise the runtime will not know what to do with it. All information about the type of each particular object is hidden behind this tiny pointer. The remaining piece of the object, in terms of runtime, is simply a huge BLOB, which does not give any information. Only classes can give this piece some meaning.
Classes
What then is actually contained in the classes? The answer to this question will help us to find the “inaccessible” fields of the structure (those after #if! __ OBJC2__, they are left here for compatibility with pre-Leopard, and you should not use them if you are developing the Floor Leopard and above, however, they help us understand what kind of information is hidden there). First comes
isa , allowing you to work with the class as with an object. Then there is a link to Class - the ancestor, in order not to break the hierarchy of classes. Next comes some basic class information. But the most interesting is at the end. This is a list of variables, a list of methods and a list of protocols. All this is available during the performance, and can be changed there!
I missed the cache, as it is not very interesting from the point of view of manipulation during execution, but it is worth telling about the role it plays in principle. Every time you send a message ([foo bar]), the Runtime searches for it in the list of object class methods. But since this is just a linear list, this process is quite long. A cache is a hash table that contains methods already called before. That is why the first method call can be much longer than all subsequent ones.By exploring runtime.h, you can discover many functions for accessing and modifying these elements. Each function begins with a prefix that shows what it is dealing with. Basic ones start with
objc_ , functions for working with classes on
class_ , and so on. For example, you can call
class_getInstanceMethod to find out information about a particular method, such as an argument list / return type. Or you can add a new method using
class_addMethod . You can even create entire classes with
objc_allocateClassPair during execution!
Practical application
There are many options for applying this Runtime meta-information, here are some of them.
- Automatic search for variables / methods. Apple has already implemented this in the form of Key-Value Coding: you specify a name, in accordance with this name you get a variable or method. You can do it yourself, if you are not satisfied with the Apple implementation ( note the translator - for example, like this Better key-value observing for Cocoa )
- Automatic registration / call subclasses. Using objc_getClassList you can get a list of classes already known to Runtime and, following the class hierarchy, find out which subclasses are inherited from this class. This gives you the opportunity to write subclasses that will be responsible for specific data formats, or something like that, and then give the superclass the opportunity to find them yourself, saving themselves from the tedious need to register them all by hand.
- Automatically call method for each class. This can be useful for unit-testing frameworks and similar things. Very similar to # 2, but with an emphasis on finding possible methods, rather than a class hierarchy
- Overloading methods during execution. Runtime provides you with a complete set of tools to change the implementation of class methods, without having to change anything in their source code.
- Bridging With the ability to dynamically create classes and view the required fields, you can create a bridge between Objective-C and another (fairly dynamic) language.
- And many many others! Do not limit yourself to the list presented above.
Conclusion
Objective-C is a powerful language, with a comprehensive Runtime API playing a key role in its dynamism. It may not be so pleasant to mess with all this C code, but the possibilities that it opens are truly enormous.
(note of the translator - for those who are no longer eager to play around with all this endless dynamism of Objective-C, but do not want to deal with runtime.h, Mike Ash put a project on GitHub - a wrapper over runtime.h, which provides full access to all the goodies described above, but in the usual Objective-C syntax.)