Objective-C Runtime for C-Schnicks. Part 3

    image

    Hello. Today I will continue to tell you about the internal device of Objective-C Runtime, and specifically about its implementation at the C language level.

    In previous articles, we have dealt with you in detail with selectors and the mechanism for sending messages to objects and classes. Today I would like to finish with the messages and talk about the principles of operation of some of the built-in features of the Objective-C language.

    For those with whom we are not familiar, I suggest first reading the first and second parts, and I ask those who are interested and interested in cat.

    A little more about objc_msgSend ()


    The documentation lies to us impossibly 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 calling a method, the compiler can generate a call to one of the functions (presented above, author's note ) to process the method call, depending on the destination, return value, and argument list.


    In fact, the list of these functions is much wider, and their number varies for each platform. 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


    , but for arm64 it’s just like this:

    .quad   _objc_msgSend
    .quad   _objc_msgSendSuper
    .quad   _objc_msgSendSuper2


    Functions with the suffix "stret" are used for those methods that return variables of a complex type (structure), while functions without such a suffix return values ​​of simple types. The following code can be used as an example:

    #import 
    #import 
    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; // returns a whole struct
    }
    + (struct TestStruct *)anotherMethod {
      struct TestStruct * s = malloc(sizeof(struct TestStruct));
      return s; // returns just a pointer
    }
    @end
    int main(int argc, const char * argv[]) {
      // objc_msgSend_stret()
      struct TestStruct s = [TestClass someMethod];
      // objc_msgSend()
      struct TestStruct * ps = [TestClass anotherMethod];
      return 0;
    }


    Functions with the suffix "Super" are used when calling methods of the parent classes, for example:

    #import 
    #import 
    @interface TestClass : NSObject
    @end
    @implementation TestClass
    - (NSString *)description {
      // objc_msgSendSuper2()
      return [super description];
    }
    @end
    int main(int argc, const char * argv[]) {
      TestClass * myObj = [[TestClass alloc] init];
      [myObj description];
      return 0;
    }


    And finally, functions with the suffix “fpret” are used where you need to return a simple type, but which does not fit in the processor register, such as “long double”:

    #import 
    #import 
    @interface TestClass : NSObject
    @end
    @implementation TestClass
    + (long double)someMethod {
      return 0.0;
    }
    @end
    int main(int argc, const char * argv[]) {
      // objc_msgSend_fpret()
      [TestClass someMethod];
      return 0;
    }


    Of course, you can always tell something more about the Objective-C message mechanism, but I would like to finish it right now. Therefore, if you have any questions, you can always find the answers to them in the source code of Objective-C Runtime. Now let's move on.

    We apply the acquired knowledge and draw conclusions



    You can talk for a long time on the topic of whether we are wasting time or are really doing business. However, for example, now implementing a remote procedure call on Objective-C is a fairly simple task for us. At least, you must agree, you immediately imagined how to implement this:

    1. Get the method name over the network
    2. Take the selector
    3. Call the desired function
    4. PROFIT !!!!


    However, our business is to understand further and not ask unnecessary questions, except for one thing: is it possible to add methods to classes directly at runtime? Of course you can!

    Those who already have experience 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 
    @interface NSObject (APObjectMapping)
    + (NSMutableDictionary *)objectMapping;
    - (instancetype)initWithDictionary:(NSDictionary *)dictionary;
    - (NSDictionary *)mapToDictionary;
    @end


    Obviously, at run time, new methods will simply be added to the dispatch table of the NSObject class. This means that 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 class_addMethod function :

    #import 
    #import 
    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 is the specification of the arguments to our function. In this case, "v" means that the type of the return value is void, "@" is the first parameter of the function of the type "object", and ":" is the second parameter of the function of the type "selector". For example, if our function took another parameter of type int, then its specification would look like this: “v @: i”.

    To summarize



    The message mechanism is the heart of the Objective-C language. It is this mechanism that provides in a language of the most ordinary C level all the charms that we are so used to in Java, C #, Python, etc. Having dealt with this mechanism, we began to understand how certain features of Objective-C, such as categories, work. We also, if desired, can understand how the protocols are arranged .

    With this, I suggest temporarily ending up with the message engine and sorting out the other basic mechanisms of my favorite language, which will be discussed in future articles of this series.

    Also popular now: