📜 ⬆️ ⬇️

Objective-C Runtime. Theory and practical application

In this post I want to turn to a topic about which many novice iPhone developers often have a vague idea: Objective-C Runtime. Many people know that it exists, but what are its capabilities and how to use it in practice?
Let's try to understand the basic functions of this library. The material is based on lectures that we use at Coalla to train employees.

What is Runtime?


Objective-C was conceived as an add-on to the C language, adding support for the object-oriented paradigm to it. In fact, from the point of view of syntax, Objective-C is a rather small set of keywords and control structures over ordinary C. It is Runtime, the runtime library, provides that set of functions that breathe life into a language, realizing its dynamic capabilities and ensuring the functioning of OOP .

Basic data structures

Functions and structures of the Runtime library are defined in several header files: objc.h , runtime.h and message.h . First, look at the objc.h file and see what the object is from the point of view of Runtime:

 typedef struct objc_class *Class; typedef struct objc_object { Class isa; } *id; 

We see that the object in the process of the program is represented by the usual C-structure. Each Objective-C object has a reference to its class — the so-called isa-pointer. I think everyone saw it when viewing the structure of objects while debugging applications. In turn, the class also has a similar structure:
')
 struct objc_class { Class isa; }; 

A class in Objective-C is a full-fledged object and it also has an isa-pointer to a “class of class”, the so-called metaclass in terms of Objective-C. Similarly, C-structures are defined for other entities of the language:

 typedef struct objc_selector *SEL; typedef struct objc_method *Method; typedef struct objc_ivar *Ivar; typedef struct objc_category *Category; typedef struct objc_property *objc_property_t; 


Runtime Library Functions

In addition to defining the basic structures of the language, the library includes a set of functions that work with these structures. They can be divided into several groups (the function assignment is usually obvious from their name):

Example 1. Introspection of an object

Consider an example of using the Runtime library. In one of our projects, the data model is a plain old Objective-C object with a certain set of properties:

 @interface COConcreteObject : COBaseObject @property(nonatomic, strong) NSString *name; @property(nonatomic, strong) NSString *title; @property(nonatomic, strong) NSNumber *quantity; @end 

For debugging convenience, I would like the information on the state of the properties of the object to be printed to the log instead of something like <COConcreteObject: 0x71d6860> . Since the data model is quite extensive, with a large number of different subclasses, it is undesirable to write for each class a separate description method in which to manually collect the values ​​of its properties. Objective-C Runtime comes to the rescue:

 @implementation COBaseObject - (NSString *)description { NSMutableDictionary *propertyValues = [NSMutableDictionary dictionary]; unsigned int propertyCount; objc_property_t *properties = class_copyPropertyList([self class], &propertyCount); for (unsigned int i = 0; i < propertyCount; i++) { char const *propertyName = property_getName(properties[i]); const char *attr = property_getAttributes(properties[i]); if (attr[1] == '@') { NSString *selector = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]; SEL sel = sel_registerName([selector UTF8String]); NSObject * propertyValue = objc_msgSend(self, sel); propertyValues[selector] = propertyValue.description; } } free(properties); return [NSString stringWithFormat:@"%@: %@", self.class, propertyValues]; } @end 

The method defined in the general superclass of model objects receives a list of all object properties using the class_copyPropertyList function. Then property values ​​are collected in NSDictionary , which is used when constructing the string representation of the object. This algorithm works only with properties that are Objective-C objects. Type checking is performed using the property_getAttributes function. The result of the method looks like this:

2013-05-04 15: 54: 01.992 Test [40675: 11303] COConcreteObject: {
name = Foo;
quantity = 10;
title = bar;
}

Messages


The system for invoking methods in Objective-C is implemented by sending messages to the object. Each method call is translated to the corresponding function call objc_msgSend :

 //   [array insertObject:foo atIndex:1]; //    Runtime- objc_msgSend(array, @selector(insertObject:atIndex:), foo, 1); 

The call objc_msgSent initiates the process of searching for the implementation of the method corresponding to the selector passed to the function. The implementation of the method is looked up in the so-called class dispatch table. Since this process can be quite long, a cache of methods is associated with each class. After the first call of any method, the result of the search for its implementation will be cached in the class. If the implementation of the method is not found in the class itself, then the search continues up the inheritance hierarchy in the superclasses of this class. If, however, the result is not reached in the hierarchy search, the dynamic search mechanism comes into resolveInstanceMethod - one of the special methods is called: resolveInstanceMethod or resolveClassMethod . Redefining these methods is one of the last ways to influence Runtime:

 + (BOOL)resolveInstanceMethod:(SEL)aSelector { if (aSelector == @selector(myDynamicMethod)) { class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:"); return YES; } return [super resolveInstanceMethod:aSelector]; } 

Here you can dynamically specify your implementation of the called method. If this mechanism for some reason does not suit you, you can use forward forwarding messages.

Example 2. Method Swizzling

One of the features of categories in Objective-C is that the method defined in the category completely overlaps the base class method. Sometimes we need not to override, but to expand the functionality of the existing method. Suppose, for example, for some reason we want to log all additions of elements to the NSMutableArray array. Standard language tools do this not work. But we can use a technique called method swizzling:

 @implementation NSMutableArray (CO) + (void)load { Method addObject = class_getInstanceMethod(self, @selector(addObject:)); Method logAddObject = class_getInstanceMethod(self, @selector(logAddObject:)); method_exchangeImplementations(addObject, logAddObject); } - (void)logAddObject:(id)aObject { [self logAddObject:aObject]; NSLog(@"  %@   %@", aObject, self); } @end 

We overload the load method — this is a special callback that, if defined in a class, will be called during the initialization of this class — before any of its other methods are called. Here we swap the implementation of the base method addObject: and our method logAddObject: Note the “recursive” call to logAddObject: - this is the call to the overloaded implementation of the main method.

Example 3. Associative links

Another known limitation of categories is the impossibility of creating new instance variables in them. Suppose, for example, you need to add a new property to the library class UITableView - a link to the “stub”, which will be shown when the table is empty:

 @interface UITableView (Additions) @property(nonatomic, strong) UIView *placeholderView; @end 

Out of the box this code will not work, you will get an exception during the execution of the program. This problem can be circumvented using the functionality of associative links:

 static char key; @implementation UITableView (Additions) -(void)setPlaceholderView:(UIView *)placeholderView { objc_setAssociatedObject(self, &key, placeholderView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } -(UIView *) placeholderView { return objc_getAssociatedObject(self, &key); } @end 

You can use any object as an associative array, associating other objects with it using the objc_setAssociatedObject function. Its operation requires a key by which you can then retrieve the object you need back using the objc_getAssociatedObject call. In this case, you cannot use the copied key value - it must be exactly the object (in the example, a pointer) that was passed in the objc_setAssociatedObject call.

Conclusion


Now you have a basic idea of ​​what an Objective-C Runtime is and how it can be useful for a developer in practice. For those who want to learn the possibilities of the library deeper, I can advise the following additional resources:

Source: https://habr.com/ru/post/177421/


All Articles