This article is a detailed compilation of Cocoa’s Runtime system, which brings Objective-C to life with its flexible, dynamic features. The main contents are as follows:

  • The introduction

  • Introduction to the

  • Interact with the Runtime

  • The Runtime term

  • The message

  • Dynamic method analysis

  • forward

  • Robust instance variables (Non Fragile ivars)

  • Objective-C Associated Objects

  • conclusion

The introduction

I used to think that OBJC was particularly convenient to use, and I only knew simple documentation and calls when faced with a large number of APIs in Cocoa. Remember when I first learned Objective-C, I thought of [receiver message] as a simple method call, ignoring the meaning of the phrase “send a message.” The receiver message is then translated by the compiler as:

objc_msgSend(receiver, selector)

If the message contains parameters, then:

objc_msgSend(receiver, selector, arg1, arg2, ...)

If the receiver of the message can find the corresponding selector, then the specific method of the receiver object is executed directly. Otherwise, the message will either be forwarded, or the corresponding implementation of the selector will be dynamically added to the receiver temporarily, or it will simply crash.

Now you can see that the receiver message is really not a simple method call. Because this is only determined at compile time to send a Message to the receiver, how the Receive responds to that message depends on what happens at run time.

The Objective-C Runtime informs the nature of its dynamic language, a depth of knowledge that is less commonly used to write code, but that is something every ObjC programmer needs to know.

Introduction to the

Because Objc is a dynamic language, it always finds ways to defer some of the decisions from the compile connection to the run time. This means that a compiler alone is not enough; a runtime system is needed to execute the compiled code. That’s what the Objective-C Runtime system is all about, and it’s a cornerstone of the entire OBJC Runtime framework.

There are actually two versions of Runtime: “Modern” and “Legacy.” Our current version of Objective-C 2.0 is the Modern version of Runtime, which can only run 64-bit applications after iOS and OS X 10.5. Older 32-bit applications in OS X still use the (earlier) Legacy version of the Runtime system in Objective-C 1. The big difference between the two versions is that when you change the layout of an instance variable of a class, in the earlier version you had to recompile its subclasses, whereas in the current version you didn’t.

Runtime is basically written in C and assembly, which shows Apple’s efforts to make dynamic systems more efficient. You can download the open source code maintained by Apple here. Apple and GNU each maintain an open source version of the Runtime, and the two versions strive to be consistent.

Interact with the Runtime

Objc interacts with the Runtime system at three different levels: through Objective-C source code, through methods defined by the NSObject class of the Foundation framework, and through direct calls to Runtime functions.

Objective – C source code

For the most part, you just write your Objc code, and the Runtime system works in the background automatically.

Remember from the introduction that the execution of messages uses data structures and functions created by the compiler for dynamic language features. The classes, methods, and protocols in OBJC are defined by data structures in Runtime, which will be covered later. (e.g. objc_msgSend and its parameter list ID and SEL)

The method of NSObject

Most of the classes in Cocoa inherit from the NSObject class, which naturally inherits its methods. The most special exception is NSProxy, an abstract superclass that implements some methods related to message forwarding. You can inherit it to create a clone of another class or create a virtual class that doesn’t exist. In plain English, the leader presents himself to everyone but leaves the work to the backroom boy.

Some methods in NSObject act as abstract interfaces, such as the description method that requires you to override it and provide a description for the class you define. NSObject also has methods that get information about a class at run time and check for properties such as the class that returns an object; IsKindOfClass: and isMemberOfClass: check whether the object is in the specified class inheritance system; RespondsToSelector: checks whether the object can respond to the specified message; ConformStoProtocol: A method that checks whether an object implements the specified protocol class; MethodForSelector: Returns the address of the specified method implementation.

A function of the Runtime

The Runtime system is a dynamic shared library with a common interface that consists of a series of functions and data structures. The header files are in the /usr/include/objc directory. Many functions allow you to implement the same functionality in OBJC repeatedly in plain C code. There are some methods that form the basis of the NSObject class, but you don’t usually use these functions directly when you’re writing OBJC code, unless you’re doing some bridging or low-level debug work with OBJC. Detailed documentation of the Runtime function is available in the Objective-C Runtime Reference.

The Runtime term

Remember the objc_msgSend method from the introduction, which actually looks like this:

id objc_msgSend ( id self, SEL op, … ) ;

The following sections will expand on some of the terms that actually correspond to data structures.

SEL

The second argument to the objc_msgSend function is of type SEL, which is the type of selector represented in Objc (the selector class in Swift). A selector is a method selector, which can be understood as the ID that distinguishes the method, and whose data structure is SEL:

typedef struct objc_selector *SEL;

It’s just a C string that maps to a method. You can get a method selector of type SEL using the Objc compiler command @selector() or the Runtime sel_registerName function.

Different kinds of the same name of the method of the selector is the same, even if the method same name but different variable type can also lead to they have the same method selector, and Objc method named sometimes take parameter type (NSNumber a heap of abstract factory method take xie), have a lot of long method in Cocoa.

id

The first argument to objc_msgSend is a familiar one of type id, which is a pointer to an instance of the class:

typedef struct objc_object *id;

What about objc_object:

struct objc_object { Class isa; };

The objc_object structure contains an ISA pointer that can be used to trace the class to which the object belongs.

Class

The reason isa isa pointer is because Class is actually a pointer to an objc_class structure:

typedef struct objc_class *Class;

And objc_class is the melon we touched, which has a lot of stuff in it:

struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #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;

You can see that at runtime a class is also associated with its superclass pointer, class name, member variables, methods, cache, and associated protocols. Objc_ivar_list and objc_method_list are the list of member variables and the list of methods, respectively:

struct objc_ivar_list {
 int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
 int space                                                  OBJC2_UNAVAILABLE;
#endif
 /* variable length structure */
 struct objc_ivar ivar_list[1]                      OBJC2_UNAVAILABLE;
}

struct objc_method_list {
 struct objc_method_list *obsolete        OBJC2_UNAVAILABLE;

 int method_count                                    OBJC2_UNAVAILABLE;
#ifdef __LP64__
 int space                                                    OBJC2_UNAVAILABLE;
#endif
 /* variable length structure */
 struct objc_method method_list[1]       OBJC2_UNAVAILABLE;
}

If you’re not particularly good at C, the objc_ivar_list structure stores the list of objc_ivar arrays, and the objc_ivar structure stores information about individual member variables of the class. Similarly, the objc_method_list structure stores a list of objc_method arrays, and the objc_method structure stores information about a method of the class.

Finally, there is the objc_cache, which as the name implies is a cache, and which plays an important role in objc_class, which we’ll talk about later.

An ObjC Class is also an object, and to handle the relationship between classes and objects, the runtime library creates something called a metaclass. When you send a message like NSObject Alloc, you are actually sending the message to a Class Object. The Class Object must be an instance of a metaclass, which is also an instance of the root metaclass. When you say a subclass of NSObject, your class will point to NSObject as its superclass. But all metaclasses ultimately refer to the root metaclass as its superclass. All metaclass method lists have class methods that can respond to messages. So when the [NSObject Alloc] message is sent to a class object, objc_msgSend() looks in its metaclass for a method that can respond to the message, and if it finds one, makes a method call on the class object.

Method

Method is a type that represents a Method in a class.

typedef struct objc_method *Method;

While objc_method is mentioned in the above list of methods, it stores the method name, method type, and method implementation:

struct objc_method {
 SEL method_name                             OBJC2_UNAVAILABLE;
 char *method_types                          OBJC2_UNAVAILABLE;
 IMP method_imp                                OBJC2_UNAVAILABLE;
}
  1. The method name type is SEL, and previously mentioned methods with the same name will have the same method selector even if they are defined in different classes.

  2. The method type method_types is a char pointer that actually stores the parameter types and return value types of the method.

  3. Method_imp points to the implementation of the method, which is essentially a function pointer, as discussed in more detail later.

Ivar

An Ivar is a type that represents instance variables in a class.

typedef struct objc_ivar *Ivar;

Objc_ivar is also mentioned in the list of member variables above:

struct objc_ivar {
 char *ivar_name                              OBJC2_UNAVAILABLE;
 char *ivar_type                                 OBJC2_UNAVAILABLE;
 int ivar_offset                                     OBJC2_UNAVAILABLE;
#ifdef __LP64__
 int space                                            OBJC2_UNAVAILABLE;
#endif
}

PS:OBJC2_UNAVAILABLE is a black magic that Apple uses in Objc to restrict the running version of the system. If you are interested, you can check the source code.

IMP

The definition of IMP in objc.h is:

typedef id (*IMP)(id, SEL, …) ;

It’s just a function pointer, which is generated by the compiler. When you launch an ObjC message, the piece of code it will eventually execute is specified by this function pointer. The function pointer IMP points to the implementation of this method. Now that we have an entry to execute a method on an instance, we can bypass the messaging phase and execute the method directly, as discussed later. You’ll notice that IMP points to a method of the same type as objc_msgSend, with both id and SEL types as arguments. Each method name corresponds to a SEL type method selector, and the method implementation corresponding to SEL in each instance object must be unique, and the unique method implementation address can be determined by a group of ID and SEL parameters. And vice versa.

Cache

In Runtime. H, Cache is defined as follows:

typedef struct objc_cache *Cache

Remember before objcclass structure in a struct objc_cache * cache, what is it the cache, see first objccache implementation:

struct objc_cache {
 unsigned int mask /* total = mask + 1 */       OBJC2_UNAVAILABLE;
 unsigned int occupied                                    OBJC2_UNAVAILABLE;
 Method buckets[1]                                          OBJC2_UNAVAILABLE;
};

Cache optimizes the performance of method calls. In general, whenever an instance object receives a message, it does not directly traverse the list of methods of the class that ISA points to to find methods that respond to the message, because that would be inefficient. Instead, it looks in Cache first. The Runtime system stores the called method in its Cache (theoretically, if a method is called, it may be called again in the future), making the lookup more efficient the next time. This is similar to the principle that the CPU should bypass main memory to access the Cache first, and I’m guessing Apple is trying to improve the Cache hit rate.

The message

After all this preparation, here comes the news. Messages are sent in Objc by enclosing the receiver and the message in brackets ([]), and the message is not bound to the method implementation until run time.

Objc_msgSend function

Objc_msgSend was mentioned a little bit in the introduction. It looks like objc_msgSend returns data. In fact, objc_msgSend never returns data but instead returns data after your method is called. The message sending steps are described in detail below:

  1. Check if this selector is to be ignored. Mac OS X developers, for example, don’t care about retain and release functions with garbage collection.

  2. Check if the target is a nil object. The ObjC feature is to allow any method to be executed on a nil object without crashing because it will be ignored.

  3. If you have passed both of these, then start looking for the class’s IMP, first in the cache, and then jump to the corresponding function.

  4. If the cache cannot be found, find a method to publish it.

  5. If you can’t find the sub publication, go to the sub publication of the superclass, and keep looking until you find the NSObject class.

  6. If you can’t find it yet, you’ll need to start with dynamic method resolution, which will be discussed later. PS: Publishing is a list of methods in a Class. It connects the method selector to the method implementation. The compiler will call one of the four methods objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, or objc_msgSendSuper_stret, depending on the situation. If the message is to the superclass, the function with the name “Super” is called; If the return value of the message is a data structure rather than a simple value, then the function with the name “stret” is called. Perform and combine exactly four ways.

Hidden parameters in the method

We often use the self keyword in methods to refer to the instance itself, but never wonder why self gets the object that calls the current method. The contents of self are secretly passed in dynamically while the method is running.

When objc_msgSend finds the corresponding implementation of a method, it calls the method implementation directly and passes all the arguments in the message to the method implementation. It also passes two hidden arguments:

— the object that receives the message (that is, what self points to) — the method selector (what _cmd points to)

They are hidden because they are not declared in the definition of the source method. They are inserted into the implementation when the code is compiled. Although these parameters are not explicitly declared, we can refer to them in the source code. In the following example, self refers to the receiver object, and _cmd refers to the selector of the method itself:

- strange
{
 id  target = getTheReceiver();
 SEL method = getTheMethod();
 if ( target == self || method == _cmd )
     return nil;
 return [target performSelector:method];
}

Of the two arguments, self is the more useful. In effect, it is the way to access the instance variable of the message receiver object in the method implementation. When the super keyword in the method receives a message, the compiler creates an objc_super structure:

struct objc_super { id receiver; Class class; };

This structure specifies the definition of a particular superclass to which messages should be passed. But receiver is still self itself, which is important to note, because when we want to get the superclass from [super class], the compiler simply passes the id pointer to self and the SEL of the class to objc_msgSendSuper. Because the class method is only found in the NSObject class, and then the class method calls object_getClass(), which in turn calls objc_msgSend(objc_super->receiver, @selector(class)), The first argument we pass is an id pointer to self, the same as calling [self class], so we always get the type of self.

Get method address

We mentioned in the IMP section that you can bypass the message binding and simply get the address of the method and invoke the method. This is rarely used, except in extreme cases where you need to call a method repeatedly and repeatedly, and it is more efficient to call the method directly without flooding the message.

The NSObject class has a methodForSelector: instance method that you can use to get the IMP for a method selector, for example:

void (*setter)(id, SEL, BOOL);
int i;
setter = (void (*)(id, SEL, BOOL))[target
 methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
 setter(targetList[i], @selector(setFilled:), YES);

When a method is called as a function, we need to specify the two hidden arguments mentioned in the previous section. The above example calls the function 1000 times. You can try sending the setFilled: message directly to the target 1000 times to see how long it takes.

PS: MethodForSelector: The method is provided by Cocoa’s Runtime system, not a feature of Objc itself.

Dynamic method analysis

You can provide the implementation of a method dynamically. For example, we could use the @dynamic keyword to modify a property in the class’s implementation file:

@dynamic propertyName;

This means that we will dynamically provide the access method for this property, which means that the compiler will no longer generate the setPropertyName: and propertyName: methods for us by default, instead of requiring us to provide them dynamically. We can add an instance method implementation and a class method implementation by overloading the resolveInstanceMethod: and resolveClassMethod: methods, respectively. This is because when the Runtime system cannot find a method to execute in the Cache and method section (including the superclass), it will call resolveInstanceMethod: or resolveClassMethod: to give the programmer a chance to dynamically add the method implementation. We need to add a specific method implementation to a specific class using the class_addMethod function:

void dynamicMethodIMP(id self, SEL _cmd) { // implementation .... } @implementation MyClass + (BOOL)resolveInstanceMethod:(SEL)aSEL { if (aSEL == @selector(resolveThisMethodDynamically))  { class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:"); return YES; } return [super resolveInstanceMethod:aSEL]; } @end

The above example for resolveThisMethodDynamically method to add the implementation content, namely dynamicMethodIMP method with the code. Where “v@:” refers to the return value and parameter, this symbol refers to Type Encoding PS: Dynamic method parsing is performed before the message forwarding mechanism is immersed. If respondsToSelector: or instancesRespondToSelector: method is executed, the dynamic method the parser will be the first to provide the method to give a selector corresponding IMP. If you want the method selector to be passed to the forward mechanism, ask resolveInstanceMethod: to return NO.

forward

redirect

Before the message forwarding mechanism implementation, the Runtime system will give us the opportunity to once removed, by overloading – (id) forwardingTargetForSelector: (SEL) aSelector method to replace the message receiver for other objects:

- (id)forwardingTargetForSelector:(SEL)aSelector
{
 if(aSelector == @selector(mysteriousMethod:)){
     return alternateObject;
 }
 return [super forwardingTargetForSelector:aSelector];
}

After all, it takes more time to forward a message, so it’s a good idea to take this opportunity to redirect the message to someone else, but never return self, because that will loop forever.

forwarding

The message forwarding mechanism is triggered when the dynamic method resolution returns NO without processing. At this point the ForwardInvocation method will be executed and we can override this method to define our forwarding logic:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
 if ([someOtherObject respondsToSelector:
         [anInvocation selector]])
     [anInvocation invokeWithTarget:someOtherObject];
 else
     [super forwardInvocation:anInvocation];
}

The only argument to the message is an object of type NSInvocation – the object that encapsulates the original message and the message’s arguments. We can implement the ForwardInvocation: ‘method to do some default processing for unhandled messages, and we can forward the message to another object for processing without throwing an error.

One thing to notice here is where did the anInvocation come from? Actually in forwardInvocation: before the message is sent, the Runtime system will send object methodSignatureForSelector: news, and to return to the method signature is used to generate NSInvocation object. So we rewrite forwardInvocation: at the same time also to rewrite methodSignatureForSelector: method, otherwise will be throw exceptions.

When an object is unable to respond to a message because it has no method implementation, the runtime system notifies the object through the ForwardInvocation: message. Each object inherits the ForwardInvocation: method from the NSObject class. Method of NSObject, however, simply call the doesNotRecognizeSelector:. By implementing our own ForwardInvocation: method, we can forward messages to other objects in that method implementation.

ForwardInvocation: The method acts as a distribution hub for unrecognized messages and forwards them to different receivers. Or it can act as a shipping station and send all messages to the same receiving object. It can translate one message into another, or simply “eat” some messages, so there is no response and no error. ForwardInvocation: The method can also provide the same response for different messages, depending on the implementation of the method. What this method provides is the ability to link disparate objects into a message chain.

Note: The ForwardInvocation method is only invoked if the message cannot be properly responded to in the receiving object. So, if we want an object to forward the value message to another object, then that object cannot have the value method. Otherwise, the ForwardInvocation: will never be invoked.

Forwarding and multiple inheritance

Forwarding is similar to inheritance and can be used to add some effect of multiple inheritance to Objc programming. An object forwards a message as if it borrowed or “inherited” a method from another object. This allows two classes under different branches of the inheritance system to “inherit” each other’s methods,

Surrogate Objects

Forwarding not only simulates multiple inheritance, but also enables lightweight objects to represent heavyweight objects. Behind the weak women are strong men. After all, women pass their problems on to men. Here are some examples of how to use them. See the official documentation.

Forwarding and inheritance

Although forwarding is a lot like inheritance, the NSObject class does not confuse the two. Methods like respondsToSelector: and iskindofClass: only take into account inheritance systems, not forward chains. If you intend to “cheat” the Warrior into thinking that the Warrior has inherited the Diplomat’s negotiate, you will have to re-implement respondsToSelector: and iskindofClass: to add to your forwarding algorithm:

- (BOOL)respondsToSelector:(SEL)aSelector { if ( [super respondsToSelector:aSelector] ) return YES; else { /* Here, test whether the aSelector message can * * be forwarded to another object and whether that * * object can respond to it.  Return YES if it can. */ } return NO; }

In addition to respondsToSelector: and isKindOfClass: instancesRespondToSelector: should also be written in a forwarding algorithm. If a protocol is used, conformsToProtocol: will also be added to this list. Similarly, if an object forward it accepts any remote message, it must give a methodSignatureForSelector: to return accurate description methods, this method will be forwarded message final response. Such as an object to its replacement object forwarding messages, it needs as follows for methodSignatureForSelector: :

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { NSMethodSignature* signature = [super methodSignatureForSelector:selector]; if (! signature) { signature = [surrogate methodSignatureForSelector:selector]; } return signature; }

Robust instance variables (Non Fragile ivars)

In the current version of Runtime, the biggest feature is robust instance variables. When a class is compiled, the layout of instance variables is formed, which indicates where to access instance variables of the class. Starting at the head of the object, the instance variables are shifted in turn according to the space they occupy: the superclass followed by the instance variables of our own class looks nice. But imagine if Apple had updated the NSObject class that day and released a new version of the system, it would have been tragic: our custom class had been crossed twice, because that area overlapped with the superclass. Only Apple could save us by changing the superclass layout to the old one, but it also made it impossible for them to extend their framework because the member variable layout was fixed. We need to recompile the classes inherited from Apple to restore compatibility in the Fragile ivars environment. So what happens under a robust instance variable? Under robust instance variables, the compiler generates the same layout of instance variables as before, but when the Runtime system detects a partial overlap with the superclass, it adjusts the displacement of your newly added instance variables, so that your newly added members in the subclass are protected. Note that for robust instance variables, don’t use sizeof(someClass). Instead, use class_getInstancesize ([someClass]). Also do not use offsetOf (SomeClass, SomeIvar), instead use ivar_getOffset(class_getInstanceViable ([SomeClass class], “SomeIvar”)) instead.

Objective-C Associated Objects

After OS X 10.6, the Runtime system made Objc support for dynamically adding variables to objects. There are three functions involved:

void objcsetAssociatedObject ( id object, const void *key, id value, objcAssociationPolicy policy );

id objc_getAssociatedObject ( id object, const void *key );

void objc_removeAssociatedObjects ( id object );

These methods dynamically add, get, or delete associated values to an object as key-value pairs. Where the association policy is a set of enumerated constants:

enum {
OBJC_ASSOCIATION_ASSIGN  = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC  = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC  = 3,
OBJC_ASSOCIATION_RETAIN  = 01401,
OBJC_ASSOCIATION_COPY  = 01403
};

These constants correspond to the policy of referring to associated values, which is the reference counting mechanism for OBJC memory management.

conclusion

We made our classes inherit NSObject not only because Apple solved the complicated memory allocation problem for us, but because it enabled us to use the Runtime system for convenience. Perhaps when we write code, we seldom think about what happens behind a simple receiver message, and only think about it as a method or function call. Understanding the details of the Runtime system allows us to write more powerful code using the messaging mechanism, such as Method Swizzling.

The original reference: Yang Xiao Yu blog The original link: http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/