
Hello. Today I will continue to tell you about the internal structure of the Objective-C Runtime, and specifically about its implementation at the C language level.
In previous articles, we have dealt with the selectors and the mechanism for sending messages to objects and classes in detail. Today I would like to finish with messages and to tell about the principles of work of some built-in opportunities of the Objective C language.
')
To those with whom we are not familiar yet, I suggest to read the
first and
second parts for a start, and I ask those who have read and become interested under the cat.
More about objc_msgSend ()
The documentation undoubtedly lies to us that there are as many as four objc_msgSend () functions:
- objc_msgSend ()
- objc_msgSend_stret ()
- objc_msgSendSuper ()
- objc_msgSendSuper_stret ()
I will translate a piece of this documentation:
When it comes to a method call, the compiler can generate a call to one of the functions (presented above, note of the author ) to handle the method call, depending on the recipient, the return value and the argument list.
In fact, the list of these functions is much wider, and their number differs for each of the platforms. For example, for i386 this list looks like this:
.long _objc_msgSend .long _objc_msgSend_fpret .long _objc_msgSend_stret .long _objc_msgSendSuper .long _objc_msgSendSuper_stret
, and for arm64 only this way:
.quad _objc_msgSend .quad _objc_msgSendSuper .quad _objc_msgSendSuper2
Functions with the “stret” suffix are used for those methods that return variables of a complex type (structure), while functions
without such a suffix return values ​​of simple types. As an example, the following code:
#import <Foundation/Foundation.h> #import <objc/runtime.h> struct TestStruct { long firstValue; long secondValue; long thirdValue; }; @interface TestClass : NSObject @end @implementation TestClass + (struct TestStruct)someMethod { struct TestStruct * s = malloc(sizeof(struct TestStruct)); return *s;
Functions with the suffix "Super" are used when calling methods of parent classes, for example:
#import <Foundation/Foundation.h> #import <objc/runtime.h> @interface TestClass : NSObject @end @implementation TestClass - (NSString *)description {
And, finally, functions with the “fpret” suffix are used where you need to return a simple type, but which does not fit in the processor register, such as “long double”:
#import <Foundation/Foundation.h> #import <objc/runtime.h> @interface TestClass : NSObject @end @implementation TestClass + (long double)someMethod { return 0.0; } @end int main(int argc, const char * argv[]) {
Of course, you can always tell something else about the mechanism of Objective C messages, but I would like to finish with him right now. Therefore, if you have any questions, you can always find out the answers to them in the
source code Objective C Runtime. And now let's move on.
We apply the knowledge and draw conclusions
You can talk for a long time on whether we are wasting time or actually doing business. However, for example, now the implementation of a remote procedure call on Objective-C for us is quite a task. At least, you will agree, you immediately mentally presented how to implement it:
- Get the method name over the network
- Take the selector
- Call the desired function
- PROFIT !!!!
However, our business is to understand further and not to ask unnecessary questions, except one thing: is it possible to add methods to classes right at runtime? Sure you may!
Those who already have experience in developing applications in Objective-C immediately remembered such a mechanism as categories. Categories allow you to extend the functionality of any classes without even having their source files. For example,
in this way, you can add new methods to the NSObject class:
#import <Foundation/Foundation.h> @interface NSObject (APObjectMapping) + (NSMutableDictionary *)objectMapping; - (instancetype)initWithDictionary:(NSDictionary *)dictionary; - (NSDictionary *)mapToDictionary; @end
Obviously, at runtime, new methods will simply be added to the dispatch table of the NSObject class. So all other classes inherited from NSObject will also receive the methods we added. Beauty and more!
Let's try to do the same without using categories, exclusively using the C language. To do this, we use the function
class_addMethod :
#import <Foundation/Foundation.h> #import <objc/runtime.h> void appleSecret(id self, SEL _cmd) { NSLog(@"Tim Cook is so gay..."); } int main(int argc, const char * argv[]) { class_addMethod([NSObject class], @selector(appleSecret), (IMP)appleSecret, "v@:"); NSObject * myObj = [[NSObject alloc] init]; [myObj performSelector:@selector(appleSecret)]; return 0; }
With the first three parameters of the class_addMethod () function, everything is clear, but the fourth one is the specification of the arguments of our function. In this case, "v" means that the type of the return value is void, "@" is the first parameter of a function of type "object", and ":" is the second parameter of a function of type "selector". For example, if our function took another parameter of type int, then its specification would look like this: "v @: i".
Summing up
The message mechanism is the heart of Objective C. It is this mechanism that provides in the language of the most ordinary C level all the charms that we are used to in Java, C #, Python, etc. Having dealt with this mechanism, we began to understand how certain Objective C features, such as categories, work. We also can, if desired, understand how the
protocols are arranged.
With this, I propose to temporarily end up with the message engine and understand the other basic mechanisms of my favorite language, which will be discussed in the next articles of this cycle.