📜 ⬆️ ⬇️

Objective-C What is actually a method and self? + runtime

How do self and _cmd appear in the method? How does the dispatch table and categories work? What is a meta class? How many methods does your classes actually have at ARC and at MRC? How does swizzling work?
Interesting? Welcome under the cut!

ATTENTION!

This article is not designed for novice developers ... I apologize for not considering many things that the Objective-C developer should know.
')


There are class methods, there are class instance methods. Let us temporarily forget that the class has methods, we will definitely return to it later - there will be less confusion when reading the article.
We will not pay additional attention to how the method is searched for in Objective-C, there are suitable articles for this, even Wikipedia is sufficient.

So we begin.



The method is searched for by dispatch table at isa, going down. That is why all methods in Objective-C are virtual, including private.

And therefore we can turn to the method, knowing its selector.

The key in the dispatch table is SEL (selector, detailed parsing ), and the IMP value (implementation, the most common C function)

Method is a function? More on that later.

According to the picture, the table of the child class does not include the table of the parent class, but uses composition. Let's check it in practice:

Getting the dispatch table class
... typedef struct objc_method *Method; ... struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; } 

Human.h
 #import <Foundation/Foundation.h> @interface Human : NSObject @property (copy, nonatomic) NSString *name; @end 

Human.m
 #import "Human.h" @implementation Human @end 

main.m
 #import <Foundation/Foundation.h> #import <objc/runtime.h> #import "Human.h" void printAllMethodClass(Class clazz) { unsigned int count; Method *methods = class_copyMethodList(clazz, &count); for (int i = 0; i < count; i++) { Method method = methods[i]; SEL sel = method_getName(method); NSLog(@"%@", NSStringFromSelector(sel)); } } int main(int argc, const char * argv[]) { @autoreleasepool { printAllMethodClass([Human class]); } return 0; } 

Conclusion
2015-11-14 22: 02: 03.744 TestingRuntime [71448: 6200105] .cxx_destruct
2015-11-14 22: 02: 03.746 TestingRuntime [71448: 6200105] setName:
2015-11-14 22: 02: 03.746 TestingRuntime [71448: 6200105] name

Comment
the setName and name methods are generated since we declared the property name


Great, we were able to get a table of methods of the Human class and made sure that the parent table is used by the composition. However, among our methods, .cxx_destruct was found (ARC is added when there are fields, this is where the release occurs), but this is not the topic of this article.
We understand further in the dispatch table. How do categories work? They extend the class table. And how does this happen? When do we use include / import? No, it is not.

The effect of the category on the dispatch table
Human + FooMethod.h
 #import "Human.h" @interface Human (FooMethod) @end 

Human + FooMethod.m
 #import "Human+FooMethod.h" @implementation Human (FooMethod) - (void)fooMethod { NSLog(@"i send msg fooMethod"); } @end 

main.m
 #import <Foundation/Foundation.h> #import "Human.h" int main(int argc, const char * argv[]) { @autoreleasepool { Human *human = [[Human alloc] init]; [human performSelector:@selector(fooMethod)]; } return 0; } 

Conclusion
2015-11-14 22: 24: 20.862 TestingRuntime [71509: 6208985] i send msg fooMethod



Why our program did not fall, and the method was called? Because at this moment the “fooMethod” method is already present in the dispatch table. I note that the code never uses the inclusion of the file "Human + FooMethod.h". This means that the category works on the entire project, and not only in the files where we included it, using include / import. And what will happen if a collision occurs in the table? Indefinite behavior, and no matter how we use the categories in the code.

Now expand the table with your hands. Yes, we add a method in runtime and convert a regular function into a method.

Make the function method
Human.h
 #import <Foundation/Foundation.h> @interface Human : NSObject @property (copy, nonatomic) NSString *name; @end 

main.m
 #import <Foundation/Foundation.h> #import <objc/runtime.h> #import "Human.h" void methodInRuntime(Human *self, SEL _cmd) { NSLog(@"name self %@", self.name); } int main(int argc, const char * argv[]) { @autoreleasepool { class_addMethod([Human class], @selector(methodInRuntime), (IMP)methodInRuntime, "@@:"); Human *human = [[Human alloc] init]; human.name = @"ajjnix"; [human performSelector:@selector(methodInRuntime)]; } return 0; } 

Types



The minimum limit to the method is to pass an object and a selector that symbolizes self and _cmd ( what is this? )

So the method is the function to which the object and the selector are passed. Does it have any practical significance?

Now we know that self is a variable and that the block captures self as a normal external variable (topic of a separate article). And for this, we can create a variable in the block called self (which sometimes has to be done when using macros, where self is used internally).

A logical question arises: “Can we fake self and _cmd when called?” Yes, we can. As can be seen in the code above, IMP is a simple function that can be brought to any necessary form and transfer to it whatever we want.

What else can we do in runtime? Use private ivar, add classes, properties, methods, delete, receive all class methods and other things. But the article is not about how to use runtime, but about methods.

We come to the concept of swizzling, which is a substitution.

swizzling
Human.h
 #import <Foundation/Foundation.h> @interface Human : NSObject - (NSString *)fname; - (NSString *)lname; - (void)swizzling; @end 

Human.m
 #import "Human.h" #import <objc/runtime.h> @implementation Human - (NSString *)fname { return @"first name"; } - (NSString *)lname { return @"last name"; } - (void)swizzling { Method mfname = class_getInstanceMethod([self class], @selector(fname)); Method mlname = class_getInstanceMethod([self class], @selector(lname)); method_exchangeImplementations(mfname, mlname); } @end 

main.m
 #import <Foundation/Foundation.h> #import "Human.h" int main(int argc, const char * argv[]) { @autoreleasepool { Human *human = [[Human alloc] init]; NSLog(@"my fname:%@", [human fname]); NSLog(@"my lname:%@", [human lname]); [human swizzling]; NSLog(@"my fname:%@", [human fname]); NSLog(@"my lname:%@", [human lname]); } return 0; } 

Conclusion
2015-11-15 19: 53: 28.307 TestingRuntime [72180: 6349571] my fname: first name
2015-11-15 19: 53: 28.309 TestingRuntime [72180: 6349571] my lname: last name
2015-11-15 19: 53: 28.309 TestingRuntime [72180: 6349571] my fname: last name
2015-11-15 19: 53: 28.309 TestingRuntime [72180: 6349571] my lname: first name



We can also add our methods to the table and make the necessary actions. Now there is no magic for us when we figured out what methods are.

What about isa? After all, everything goes here, can we change the class of an object? We can.

Class change in runtime, without changing the address of the object
Human.h
 #import <Foundation/Foundation.h> @interface Human : NSObject - (void)humanMethod; @end @interface Human1 : Human - (void)humanMethod1; @end @interface NoHuman : NSObject @property (copy, nonatomic) NSString *foo; - (void)noHumanMethod; @end 

Human.m
 #import "Human.h" @implementation Human - (void)humanMethod { NSLog(@"humanMethod"); } @end @implementation Human1 - (void)humanMethod1 { NSLog(@"humanMethod1"); } @end @implementation NoHuman - (void)noHumanMethod { NSLog(@"noHumanMethod with property foo:%@", self.foo); } @end 

main.m
 #import <Foundation/Foundation.h> #import "Human.h" #import <objc/runtime.h> int main(int argc, const char * argv[]) { @autoreleasepool { Human *human = [[Human alloc] init]; NSLog(@"ptr %p, isa = %@", human, NSStringFromClass([human class])); [human performSelector:@selector(humanMethod)]; object_setClass(human, [Human1 class]); NSLog(@"ptr %p, isa = %@", human, NSStringFromClass([human class])); [human performSelector:@selector(humanMethod)]; [human performSelector:@selector(humanMethod1)]; object_setClass(human, [NoHuman class]); NSLog(@"ptr %p, isa = %@", human, NSStringFromClass([human class])); NoHuman *noHuman = (NoHuman *)human; noHuman.foo = @"fo o"; [noHuman noHumanMethod]; } return 0; } 

Conclusion
2015-11-15 22: 40: 45.960 TestingRuntime [72469: 7427905] ptr 0x10020b8b0, isa = Human
2015-11-15 22: 40: 45.961 TestingRuntime [72469: 7427905] humanMethod
2015-11-15 22: 40: 45.962 TestingRuntime [72469: 7427905] ptr 0x10020b8b0, isa = Human1
2015-11-15 22: 40: 45.962 TestingRuntime [72469: 7427905] humanMethod
2015-11-15 22: 40: 45.962 TestingRuntime [72469: 7427905] humanMethod1
2015-11-15 22: 40: 45.962 TestingRuntime [72469: 7427905] ptr 0x10020b8b0, isa = NoHuman
2015-11-15 22: 40: 45.962 TestingRuntime [72469: 7427905] noHumanMethod with property foo: foo



At the beginning of the article, I asked to forget that the class has methods and that it is an object in Objective-C. So cancel it.

Indeed, a class is an object of a meta-class. It has its own methods, its own dispatch table, its isa. It also has its own entry point (+ initializer).

We can add a method in the same way to the class as we did before. Except for one moment, you need to get a meta-class.

Demonstration, various dispatch table, use of meta-class
Human.h
 #import <Foundation/Foundation.h> @interface Human : NSObject + (void)humanClassMethod; - (void)humanMethod; @end 

Human.m
 #import "Human.h" @implementation Human - (void)humanMethod { NSLog(@"humanMethod"); } + (void)humanClassMethod { NSLog(@"humanClassMethod"); } @end 

main.m
 #import <Foundation/Foundation.h> #import "Human.h" #import <objc/runtime.h> void printAllMethodClass(Class clazz) { unsigned int count; Method *methods = class_copyMethodList(clazz, &count); for (int i = 0; i < count; i++) { Method method = methods[i]; SEL sel = method_getName(method); NSLog(@"%@", NSStringFromSelector(sel)); } } int main(int argc, const char * argv[]) { @autoreleasepool { printAllMethodClass([Human class]); NSLog(@"\n\n\n"); Class metaClass = object_getClass([Human class]); printAllMethodClass(metaClass); } return 0; } 

Conclusion
2015-11-15 20: 20: 33.128 TestingRuntime [72303: 6360119] humanMethod
2015-11-15 20: 20: 33.129 TestingRuntime [72303: 6360119]

2015-11-15 20: 20: 33.129 TestingRuntime [72303: 6360119] humanClassMethod



It remains to secure the material, get the address of the method of the class instance and call it as a normal function with reduction to the desired type.

Method call as a function
Human.h
 #import <Foundation/Foundation.h> @interface Human : NSObject @property (copy, nonatomic) NSString *name; - (NSString *)fooMethodWithArg1:(NSString *)arg1 arg2:(NSString *)arg2; @end 

Human.m
 #import "Human.h" @implementation Human - (NSString *)fooMethodWithArg1:(NSString *)arg1 arg2:(NSString *)arg2 { return [NSString stringWithFormat:@"\nname:%@ \n_cmd:%@ \narg1:%@ \narg2:%@", self.name, NSStringFromSelector(_cmd), arg1, arg2]; } @end 

main.m
 #import <Foundation/Foundation.h> #import "Human.h" #import <objc/runtime.h> int main(int argc, const char * argv[]) { @autoreleasepool { SEL sel = @selector(fooMethodWithArg1:arg2:); Method method = class_getInstanceMethod([Human class], sel); IMP imp = method_getImplementation(method); #define funcWithArg1AndArg2(imp) ((NSString * (*)())imp) Human *human = [[Human alloc] init]; human.name = @"ajjnix"; NSString *result = funcWithArg1AndArg2(imp)(human, sel, @"Hello ", @"world"); NSLog(@"%@", result); NSLog(@"\n\n\n"); NSString *result1 = funcWithArg1AndArg2(imp)(human, @selector(fake_selector), @"Hello ", @"world"); NSLog(@"%@", result1); #undef funcWithArg1AndArg2 } return 0; } 

Conclusion
2015-11-17 12: 28: 29.821 TestingRuntime [73269: 8918838]
name: ajjnix
_cmd: fooMethodWithArg1: arg2:
arg1: Hello
arg2: world
2015-11-17 12: 28: 29.823 TestingRuntime [73269: 8918838]

2015-11-17 12: 28: 29.823 TestingRuntime [73269: 8918838]
name: ajjnix
_cmd: fake_selector
arg1: Hello
arg2: world



The article was not a small one, I hope I could explain what methods in Objective-C really are.
ps and finally a link to the documentation

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


All Articles