📜 ⬆️ ⬇️

Objective-C Runtime for Si-Schnick. Part 1



When I first met Objective C, he impressed me with an ugly and illogical language. At that time, I already had a fairly strong base in C / C ++ and x86 assembler, as well as I was familiar with other high-level languages. The documentation wrote that Objective C is an extension of the C language. But, no matter how hard I tried, I still failed to apply my experience in developing applications for iOS.

Today, he still seems to me ugly. But once having plunged into the depths of the Objective-C Runtime, I fell in love with it. Studying the Objective-C Runtime allowed me to find those thin strings that connect Objective C with his “father” - a magnificent and unsurpassed language C. This is the very case when love turns disadvantages into advantages.
')
If it is interesting to you to look at Objective C not just as a set of operators and basic frameworks, but to understand its low-level device, I ask for cat.

Small clarification
In my articles, I will often confuse Objective C, Objective-C Runtime, iOS SDK, iOS, iPhone, etc. Not because I do not understand the difference between them, but because it will be easier to explain the essence of things, without inflating articles to a comprehensive manual on C and BSD-based systems. Therefore, a big request to write comments with clarifications in terminology only where it really is of fundamental importance.

A bit about “method calls”


Let's take a look at our usual design:

[myObject someMethod]; 

This is usually called the "call method". Meticulous iOS developers call it “send a message to an object,” which is certainly right. Because whatever “methods” and whatever objects you call, ultimately such a construction will be converted by the compiler into the most common function call objc_msgSend:

 objc_msgSend(myObject, @selector(someMethod)); 

Thus, everything that the construction we take does - just calls the objc_msgSend function.



From the name you can understand that there is some kind of message sending. We will talk about this function later, because already with the information we have in our hands we are confronted with an unknown for us @selector () construction, which I propose to deal with first of all.

Meet the selectors


Having looked in the documentation , we learn that the signature of the objc_msgSend (...) function has the following form:
 id objc_msgSend ( id self, SEL op, ... ); 


Once an ordinary C function, it takes an argument of type SEL as a parameter, which means we can learn more about this type of SEL if we want.

Based on the documentation , we learn that there are two ways to get a selector (for us, an object of type SEL):

  1. At compile time: SEL aSelector = @selector (methodName);
  2. At runtime: SEL aSelector = NSSelectorFromString (@ "methodName");


Well, we are interested in exactly runtime, so again, from the documentation, we have the following information:

 SEL NSSelectorFromString ( NSString *aSelectorName ); 

To create a selector, NSSelectorFromString passes aSelectorName to the sel_registerName function as a UTF-8 string and returns the value obtained from the function being called. Notice also that if the selector does not exist, the newly registered selector will be returned.


Now this is more interesting and closer to our Si-worldview. Wakes up interest to dig a little deeper.

Here I, of course, understand that I’m already pretty tired of you with my links to the documentation, but in no other way. Therefore, we again read the documentation for the method sel_registerName and, lo and behold, this function takes the most ordinary C-string as an argument!

 SEL sel_registerName ( const char *str ); 

Well, this is the maximum level we can go down to based on the documentation. All that is written about this function is that it registers a method in Objective-C Runtime, converts the name of the method into a selector, and returns it.

In principle, this is enough for us to understand how the @selector () construction works. And if not enough, you can see the source code of this function, it is available directly on the Apple website . First of all, the function is interesting in this file.

 static SEL __sel_registerName(const char *name, int lock, int copy) { 

However, the moment with type SEL remains unclear. All I managed to find is that it is a pointer to the objc_selector structure:

 typedef struct objc_selector *SEL; 

I did not find the source code for the objc_selector structure. Somewhere there were mentions that this is a normal C-string, but this type is completely transparent and I should not go into the details of its implementation, because Apple can change it at any time. But for us, this is not the answer to the question. Therefore, all we have to do is arm ourselves with our beloved LLDB and get this information on our own.

For this we will write a simple code:

 #import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { SEL mySelector = NSSelectorFromString(@"mySelector"); return 0; } 

And add a breakpoint on the line “return 0;”.



By simple manipulations with LLDB in Xcode, we learn that the mySelector variable is ultimately a regular C-string.

So what is this objc_selector structure that turns into a string in a strange way? If you try to create an object of type objc_selector, it is unlikely that you will succeed. The point is that the objc_selector structure simply does not exist. Apple developers used this hack so that C-lines are not compatible with SEL objects. Why? Because the mechanism of selectors can change at any time, and abstraction from the concept of C-lines will allow you to avoid trouble with the further support of your code.

Then it is logical to conclude that we can write the following code, which should work:

 #import <Foundation/Foundation.h> #import <objc/runtime.h> @interface TestClass : NSObject - (void)someMethod; @end @implementation TestClass - (void)someMethod { NSLog(@"Hello from method!"); } @end int main(int argc, const char * argv[]) { TestClass * myObj = [[TestClass alloc] init]; SEL mySelector = (SEL)"someMethod"; objc_msgSend(myObj, mySelector); return 0; } 


But such code falls with the following explanation:

2015-02-18 14: 03: 23.152 ObjCRuntimeTest [4756: 1861470] *** NSForwarding: warning: selector (0x100000f6d) for the message 'someMethod' doesn’t match the selector for the runtime (0x100000f82) - abort
2015-02-18 14: 03: 23.178 ObjCRuntimeTest [4756: 1861470] - [TestClass someMethod]: unrecognized selector sent to instance 0x1002069c0


Objective C Runtime told us that he does not know about the selector we tried to operate on. And we already know why - we have to register the selector using the sel_registerName () function.

Here I ask to pay attention that I gave exactly two lines of error output. The fact is that when you simply operate the selector that you received using @selector (someMethod), and send a message to some object, then you get only the error "unrecognized selector sent to instance". But in our case, we were told before that Objective C Runtime does not know such a selector. Based on this, we can conclude that selectors have no relation to objects. That is, if two objects of completely different classes have a method:

 - (void)myMegaMethod; 

, then to call this method for both objects the same selector will be used, which you have registered at runtime using the

 SEL myMegaSelector = @selector(myMegaMethod); 

What does "registered selector" mean? In order not to go into the details of the sel_registerName () implementation, I will explain it this way: you pass a C-string to this function, and in return, it returns a copy of this line to you. Why copy? Because it copies the identifier transferred by you to its memory area that is more understandable to it, and gives you a pointer to a string that is located in this very “clear” memory area for it. That is this area of ​​memory, we will talk to you later.

Summarizing


We have read a lot of documentation, played with the debugger, but now we should summarize this matter in our Si-shnyh heads.

Now we can make a "method call" exclusively using the C language:

 SEL mySelector = sel_registerName("myMethod"); objc_msgSend(myObj, mySelector); 

That is, we did not lie: Objective-C is really compatible with the C language, being its extension.

With this, I would like to finish the first part and allow you to enjoy this pleasant feeling of a small, but still an understanding of the principles of the Objective-C Runtime.

Update

Thank you for noticing the 1101_debian user about how we came to the conclusion that the selector is a regular C-string. I will try to explain.

We arrived at this conclusion based on the output of the “print mySelector” command in the LLDB console. This can be seen in the screenshot. LLDB told us that this is exactly the string. If it were not a string, then LLDB would have output our selector accordingly. For example, if we run the code:

 #import <Foundation/Foundation.h> struct MyStruct { char * someString; }; typedef struct MyStruct* MyNewType; int main(int argc, const char * argv[]) { MyNewType someObj = malloc(sizeof(struct MyStruct)); someObj->someString = "fakeSelector"; SEL myRealSelector = (SEL)NSSelectorFromString(@"myMethodIsJustString"); SEL myCStringSelector = (SEL)"myCStringSelector"; SEL myFakeSelector = (SEL)someObj; return 0; } 


Then in the result we get the following output in the debugger (note the values ​​of our variables):



But this is the topic of another article that is not related to the Objective-C Runtime, but to the development in C, the presentation of data in memory and the use of debuggers.

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


All Articles