
Objective-C Runtime. Theory and practical application
- Tutorial
- Recovery mode
In this post, I want to address a topic that many novice iPhone developers often have a vague idea of: 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 the lectures that we at Coalla use to train employees.
Objective-C was conceived as an add-on to the C language, adding to it support for an object-oriented paradigm. In fact, in terms of syntax, Objective-C is a fairly small set of keywords and control structures over regular C. Runtime, the runtime library, provides that set of functions that breathe life into the language, realizing its dynamic capabilities and ensuring the functioning of OOP .
Functions and structure Runtime-libraries are defined in several header files:
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 represents a similar structure:
A class in Objective-C is a full-fledged object and it also has an isa-pointer to a "class of a class", the so-called metaclass in terms of Objective-C. Similarly, C-structures are defined for other entities of the language:
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 conditionally divided into several groups (the purpose of functions, as a rule, is obvious from their name):
Consider an example using the Runtime library. In one of our projects, the data model is plain old Objective-C objects with some set of properties:
For the convenience of debugging, I would like it to print information about the state of object properties when printing to the log, rather than something like that
The method defined in the general superclass of model objects receives a list of all the properties of the object using the function
2013-05-04 15: 54: 01.992 Test [40675: 11303] COConcreteObject: {
name = Foo;
quantity = 10;
title = bar;
}
The method invocation system in Objective-C is implemented by sending messages to an object. Each method call is translated into the corresponding function call
The call
Here you can dynamically indicate your implementation of the called method. If for some reason this mechanism does not suit you, you can use message forwarding .
One of the features of categories in Objective-C is that the method defined in the category completely overrides the base class method. Sometimes we need not override, but expand the functionality of the existing method. Suppose, for example, for some reason we want to pledge all adding elements to an array
We overload the method
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 a library class
Out of the box, this code will not work, you will get an exception during program execution. This problem can be circumvented using the associative links functionality:
You can use any object as an associative array, associating other objects with it using a function
Now you have a basic understanding of what Objective-C Runtime is and how it can be useful to the developer in practice. For those who want to know the possibilities of the library deeper, I can advise the following additional resources:
Let's try to understand the basic functions of this library. The material is based on the lectures that we at Coalla use to train employees.
What is a runtime?
Objective-C was conceived as an add-on to the C language, adding to it support for an object-oriented paradigm. In fact, in terms of syntax, Objective-C is a fairly small set of keywords and control structures over regular C. Runtime, the runtime library, provides that set of functions that breathe life into the language, realizing its dynamic capabilities and ensuring the functioning of OOP .
Basic data structures
Functions and structure Runtime-libraries are defined in several header files:
objc.h
, runtime.h
and message.h
. First, let's look at the file objc.h
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 represents 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 a 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 conditionally divided into several groups (the purpose of functions, as a rule, is obvious from their name):
- Manipulation classes:
class_addMethod
,class_addIvar
,class_replaceMethod
- The creation of new classes
class_allocateClassPair
,class_registerClassPair
- Introspection:
class_getName
,class_getSuperclass
,class_getInstanceVariable
,class_getProperty
,class_copyMethodList
,class_copyIvarList
,class_copyPropertyList
- Manipulating objects:
objc_msgSend
,objc_getClass
,object_copy
- Work with associative links
Example 1. Introspection of an object
Consider an example using the Runtime library. In one of our projects, the data model is plain old Objective-C objects with some set of properties:
@interface COConcreteObject : COBaseObject
@property(nonatomic, strong) NSString *name;
@property(nonatomic, strong) NSString *title;
@property(nonatomic, strong) NSNumber *quantity;
@end
For the convenience of debugging, I would like it to print information about the state of object properties when printing to the log, rather than something like that
. Since the data model is quite ramified, with a large number of different subclasses, it is undesirable to write a separate method for each class description
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 the properties of the object using the function
class_copyPropertyList
. Then the property values are collected in NSDictionary
, which is used when building a string representation of the object. This algorithm only works with properties that are Objective-C objects. Type checking is done using the function property_getAttributes
. The result of the method’s work looks something like this: 2013-05-04 15: 54: 01.992 Test [40675: 11303] COConcreteObject: {
name = Foo;
quantity = 10;
title = bar;
}
Messages
The method invocation system in Objective-C is implemented by sending messages to an object. Each method call is translated into 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 sought in the so-called class dispatch table. Since this process can be quite lengthy, a method cache is associated with each class. After the first call of any method, the search result of 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 search was not achieved in the hierarchy, the dynamic search mechanism comes into play - one of the special methods is called: resolveInstanceMethod
or resolveClassMethod
. Overriding these methods is one of the last possibilities 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 indicate your implementation of the called method. If for some reason this mechanism does not suit you, you can use message forwarding .
Example 2. Method Swizzling
One of the features of categories in Objective-C is that the method defined in the category completely overrides the base class method. Sometimes we need not override, but expand the functionality of the existing method. Suppose, for example, for some reason we want to pledge all adding elements to an array
NSMutableArray
. Standard language tools will not do this. 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 method
load
- this is a special callback that, if defined in the 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:
. Pay attention to the "recursive" call in logAddObject:
- this is the appeal 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 a library class
UITableView
- a link to the "stub" that will be displayed 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 program execution. This problem can be circumvented using the associative links functionality:
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 a function
objc_setAssociatedObject
. For its operation, a key is required, by which you can then retrieve the object you need back using a call objc_getAssociatedObject
. At the same time, you cannot use the copied key value - it must be exactly the object (in the example, a pointer) that was passed in the call objc_setAssociatedObject
.Conclusion
Now you have a basic understanding of what Objective-C Runtime is and how it can be useful to the developer in practice. For those who want to know the possibilities of the library deeper, I can advise the following additional resources:
- Objective-C Runtime Programming Guide - Apple documentation
- Mike Ash Blog - Objective-C Development Guru Articles