📜 ⬆️ ⬇️

Metaclasses in Objective-C

This article is a translation of the article What is a meta-class in Objective-C?

The translation is copyright. Minor additions mainly relate to Apple's documentation and are added solely for the convenience of dear readers. When copying a translation of an article reference to the original translation is required. Let's respect joint work.


Small introduction

Let's try to consider such a concept of Objective-C as a metaclass .
Like any newcomer who chose to learn Objective-C, one detail became curious to me.
If a class is also an object at the same time, and we can send messages to it that call the class method (before the type of the return value is "+"), then what is such an object class?
')
To begin with, let us remember how the class method differs from the instance method.

@interface MyClass : NSObject + (void)aClassMethod; //  - (void)anInstanceMethod; //  @end ................................................. [MyClass aClassMethod]; //   + (void)aClassMethod; MyClass *object = [[MyClass alloc] init]; //   MyClass [object anInstanceMethod]; //   - (void)anInstanceMethod; 


As you can see, to call a class method, you do not need to create a separate instance of the class. When sending a message, it is enough to specify the class name.

Creating a class manually at run-time

So, each class in the Objective-C language has its corresponding metaclass. Let's look at metaclasses in runtime.
Let's create a class manually in runtime.
The following code creates a subclass of RuntimeErrorSubclass of the NSError superclass and adds the report method to it .

 Class newClass = objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0); class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:"); objc_registerClassPair(newClass); 


The report method added using the class_addMethod function uses the ReportFunction function ( imp is a pointer to a function that implements the new method. The function must take at least two parameters, self and _cmd ), which has the following definition:

 void ReportFunction(id self, SEL _cmd) { NSLog(@"This object is %p.", self); NSLog(@"Class is %@, and super is %@.", [self class], [self superclass]); Class currentClass = [self class]; for (int i = 1; i < 5; i++) { NSLog(@"Following the isa pointer %d times gives %p", i, currentClass); currentClass = object_getClass(currentClass); } NSLog(@"NSObject's class is %p", [NSObject class]); NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class])); } 


Outwardly, everything looks quite simple. In just three steps, we have created a class.
1. Created a class using objc_allocateClassPair
2. Added a method to it using class_addMethod (If you wish, you can add variables using class_addIvar )
3. Register a class using objc_registerClassPair

At the same time, the question arises: What is a “class pair”? Of course, if you look at the Apple documentation and look at the description of the objc_allocateClassPair function, you can see that it creates not only a class, but also our mysterious metaclass. Here are a couple of classes.
But in order to gain insight into the concept of metaclass, it will require familiarity with some of the basics.

When a data structure can be viewed as an object

Every object has a class. This is the fundamental concept of object-oriented programming, but in Objective-C it is also the most important part of the data structure. Any data structure that has a pointer to a class can be viewed as an object.

In Objective-C, an object class is defined using the isa pointer.

In general, the basic definition of an object in Objective-C might look like this:

 typedef struct objc_object { Class isa; } *id; 


This means that any data structure that starts with a pointer to a class can be treated as an object.

The most important feature of objects in Objective-C is the ability to send messages to objects.

 [@"stringValue" writeToFile:@"/file.txt" atomically:YES encoding:NSUTF8StringEncoding error:NULL]; 


This works because when you send a message to an Objective-C object (like NSCFString above), the runtime follows a pointer to the isa class, which leads to the object class (to the NSCFString class as in our case). The class contains a list of methods that can be applied to all objects of this class and a pointer to the superclass (or superclass) to view inherited methods. The runtime scans the list of class and super class methods to find a method that matches the message selector (in the above case, writeToFile: atomically: encoding: error on NSString ). The runtime then calls the function ( IMP ) for this method.

The important point is that the Class determines which messages can be sent to the object.

What is a metaclass?

As you already know, classes in Objective-C are also objects. This means that you can send messages to the class.

 NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding]; 


In this case, the defaultStringEncoding message is sent to the NSString class.

This works because every class in Objective-C is also an object. This means that the class structure also starts with a pointer to the isa class, so its structure is similar to the structure of the object discussed above, and the next field of the structure must be a pointer to the superclass (or nil for base classes).

Although, depending on the version of the runtime environment, the structure of the classes may differ, but they all begin with a field of a pointer to the isa class and a pointer to the superclass.

 typedef struct objc_class *Class; struct objc_class { Class isa; Class super_class; /*      ... */ }; 


However, in order to call a Class method, a pointer to an Isa Class must point to a Class structure and this Class structure must contain a list of methods that the Class can call.

This leads us to the definition of a metaclass: a metaclass is a class for a Class object.

Simply put:
- When you send a message to an instance object of a Class, the message is viewed in the list of methods of the Class object.
- When you send a message to a Class (or rather, as we now know, to a Class object ), such a message is viewed in the list of metaclass methods .

The presence of metaclasses is mandatory, since they store class methods. There must be a unique metaclass for each Class, since each Class has a potentially unique list of Class methods.

What is the class for the metaclass?

The metaclass, like the classes discussed earlier, is also an object. This means that you can also invoke metaclass methods. In general, this means that the metaclass must also have a class.

All metaclasses use the base metaclass (metaclass of the highest class in its class hierarchy) as its class. This means that for all classes that inherit from NSObject (and these are most classes), the metaclasses have the metaclass NSObject as their class.

Following the rule that for all metaclasses that use the base metaclass of a Class as their class, any basic metaclass will be their own class (their isa pointer will point directly to them). This means that the NSObject isa pointer points to itself.

The hierarchy of classes and metaclasses.

As well as the Class, which has a pointer to the superclass using the super_class pointer, the metaclass has its own super_class pointer to the metaclass of the superclass.

A pointer to the superclass of the base metricum points to itself.

The result of this hierarchy is that all instances, classes, and metaclasses in the hierarchy are inherited from the hierarchy of the base class.

For all instances, classes and metaclasses in the hierarchy of NSObject, this means that all methods of the instance NSObject are also applicable for them.

Although it all looks pretty good as a test, Greg Parker offered an excellent diagram illustrating the hierarchy of instances, classes, and metaclasses.

image

Experimental confirmation.

In order to confirm the above, let's look at the output of the ReportFunction function, which we wrote at the beginning.
Our goal is to follow the isa pointer and write down what is found.

In order to use ReportFunction , we need to create an instance of a dynamically created class and call the report method

 id instanceOfNewClass = [[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil]; [instanceOfNewClass performSelector:@selector(report)]; [instanceOfNewClass release]; 


Since we do not have a declaration of the report method, we call it using the performSelector function, so the compiler does not give us a warning.

Next, the ReportFunction function follows the isa pointers and tells us which object is used as a class, which as a metaclass and metaclass class.

Obtaining an object class: The ReportFunction function uses the object_getClass function to traverse pointers to the isa class, since the isa pointer is a protected member of the class (you cannot directly access the isa pointer of other objects in our chain).
ReportFunction also does not use the class method's class method because the class method call does not return the metaclass, but instead returns the Class again (for example, [NSString class] will return the NSString class instead of the metaclass).
object_getClass returns a Class object whose exam is the object passed in as an argument.

 Class object_getClass(id object) 


The output of our program:
 This object is 0x10010c810. Class is RuntimeErrorSubclass, and super is NSError. Following the isa pointer 1 times gives 0x10010c600 Following the isa pointer 2 times gives 0x10010c630 Following the isa pointer 3 times gives 0x7fff71038480 Following the isa pointer 4 times gives 0x7fff71038480 NSObject's class is 0x7fff710384a8 NSObject's meta class is 0x7fff71038480 


After reviewing the received addresses:

Object: 0x10010c810.
Class: 0x10010c600.
Metaclass: 0x10010c630.
Metaclass class (NSObject metaclass) 0x7fff71038480.
The metaclass class of NSObject is the metaclass NSObject itself ( isa points to itself).

Address values ​​are in fact not very important, except for the fact that they show the metaclass hierarchy of the metaclass NSObject as discussed earlier.

findings

Metaclass is a class of a Class object. Each Class has its own unique metaclass (since each Class has its own unique list of methods).

Metaclass always ensures that the Class object has all the necessary variables and methods of the base Class, as well as all the methods of the Class. For classes inherited from NSObject, this means that all instance methods of the NSObject class and protocol methods of the NSObject are defined for all objects of derived classes and metaclasses.

All metaclasses use the base metaclass of the class NSObject as their class.

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


All Articles