2019-09-26

The Objective-C runtime is a runtime library that provides support for the dynamic properties of the Objective-C language, and as such is linked to by all Objective-C apps. Objective-C runtime library support functions are implemented in the Shared library found at/usr/lib/libobjc. A. d. ylib. (Objective – C Runtime)

The article begins with Apple Documentation’s definition of Runtime, which is both official and abstract. Personal understanding of Runtime is: in the narrow sense, Runtime uses procedural C language to implement object-oriented features, that is, to implement classes and objects; In a broad sense, Runtime implements the dynamic nature of Objective-C (the deep dynamic nature of Objective-C). Dynamic typing, Dynamic Binding, and Dynamic loading are the main Dynamic features. Accordingly, Runtime implements the Class and NSObject abstract types, the inheritance chain of classes, the response chain of methods, the dynamic method resolution and message forwarding process, and the dynamic loading of types/methods/attributes at runtime.

Dynamically loading a class library belongs to dyld (source code), but Runtime relies on dyLD’s API to implement dynamic features.

Classes and Objects

If you were to find the two most dynamic types in Objective-C, it would be Class (Class) and ID (object reference), which are the basis of Runtime’s object-oriented implementation. Jump to the runtime.h, objc.h headers with the #import

, #import

statements, where you can find the code for class and object definitions:

  • objc_classA structure represents a class. Among themsuper_classThe pointer to the parent class is used to implement the inheritance properties of the class,instanceSizeRecord how much memory is occupied by instances of the class,ivarsA list of member variables that describes the class,methodListsSaves the method list of the class,protocolsSaves a list of protocols that the class follows,cacheMethod cache is used to record recently used methods, other members can be temporarily ignored;
  • ClassIs a reference to a class;
  • objc_objectStructure represents an object that contains onlyisaPointer to an object’s class (new version runtimeisaPointers don’t have to simply point to the object’s class);
  • idSaid pointing toobjc_objectA pointer to a structure, or an object reference, is essentially the address of the object;

Important: #import

, #import

statements jump to runtime.h, objc/objc.h header files, are old versions of Runtime code. Where there is a difference between the old and new code processing logic, there is a special declaration.

/* struct objc_object *id; Struct objc_object {Class _Nonnull isa; // Object class}; Struct objc_class *Class; struct objc_class { Class _Nonnull isa; // Metaclass Class _Nullable super_class; // Const char * _Nonnull name; // Class name long version; long info; long instance_size; Struct objc_ivar_list * _Nullable ivars; Struct objc_method_list * _Nullable * _Nullable methodLists; // Struct objc_cache * _Nonnull cache; // Cache struct objc_protocol_list * _Nullable Protocols; // List of protocols to follow};Copy the code

The objc_object structure has only one pointer isa member, which means that an objc_object occupies only 8 bytes of memory, but it does not mean that the object occupies only 8 bytes of memory. When a class’s alloc or allocWithZone method is called to build an object, Runtime allocates contiguous memory space of the class’s instanceSize size to hold the object. The first eight bytes of the memory block are written to the address of the class, and the rest of the space is used to hold other member variables. The final ID returned by the build method is actually a pointer to the first address of the memory block.

Note: This is the handling of the build object in the old version of Runtime; it is slightly different in the new version of Runtime. The reason is that the new version of Runtime redefines the ISA pointer data structure, and that instanceSize is no longer a fixed value after the introduction of the Non-Fragile Instance variable mechanism, which will be covered in a future article.

1.1 the inheritance chain

The super_class member of objc_class establishes the inheritance structure of the class, and since the super_class of the class points to a single class, this is why Objective-C is a single inheritance language. At the top of the class inheritance chain is the root class, usually NSObject, whose super_class points to NULL.

An object has a type. The type of a class is meta class. The ISA pointer to object_class points to a metaclass. Metaclasses are also stored in objc_class structs, which means that metaclasses are also classes. Metaclasses also have inheritance, with root Meta classes at the top of the inheritance chain. The root metaclass is the metaclass of the root class, and the super_class of the root metaclass points to the root class. There are two ways to determine whether objc_class is a metaclass:

  • According to theversionOf all metaclassesversionA value of6(Yes in the new runtime7), non-metaclasses are0;
  • metaclassisaPointers to the root metaclass, including the root metaclass itself.

At this point, the inheritance structure of Runtime is summarized as shown in the figure below.

Member variables

Objc_ivar structures represent class member variables.

  • objc_ivarIn theivar_nameIs the name of a member variable;
  • ivar_typeCode the type of a member variable (The official documentation), which represents the data type of the member variable as a string, for example:The '@'Represents that a member variable holds a reference to an object and can be used@encode()Query type encoding with type as parameter;
  • offsetIs the offset in the instance memory block for the member variable.
struct objc_ivar {
    char * _Nullable ivar_name;
    char * _Nullable ivar_type;
    int ivar_offset;
#ifdef __LP64__
    int space;
#endif
} 
Copy the code

The objc_ivar_list structure represents a list of member variables for a class. The array ivar_list of the objc_ivar structure in objc_ivar_list is used to hold all the member variables of the class. Ivar_count is the length of the list of member variables.

struct objc_ivar_list {
    int ivar_count;
#ifdef __LP64__
    int space;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1];
} 
Copy the code

2.1 Layout of member variables

The process of building a list of member variables for a class, including determining the ivar layout. The layout of member variables is to define which area of the memory space occupied by an object to store which member variables, specifically to determine the instanceSize of the class, memory alignment bytes, and offset of member variables. The member variable layout of all classes on the inheritance chain of the class together constitutes the object memory layout. The relationship between the layout of member variables and the layout of object memory can be expressed by a formula: the object memory layout of the class = the object memory layout of the parent class + the member variable layout of the class. The calculation of the layout of member variables is as follows:

  • The offset of a member variableoffsetMust be greater than or equal to the parent classinstanceSize;
  • The layout of member variables follows the same guidelines as the alignment of structures. The alignment bytes of a class must be greater than or equal to the alignment bytes of its parent class. For example, 4 bytesintThe starting memory address of a member variable of type must be a multiple of 4 and occupy 8 bytesidThe starting memory address of a member variable of type must be a multiple of 8;
  • instanceSizeIs calculated byClass instanceSize = Superclass instanceSize + Memory occupied by class member variables in the instance + aligned fill bytes.instanceSizeMust be an integer multiple of the number of aligned bytes of the class;

The definition of Class NSObject contains an ISA member of type Class, so the actual 8 bytes of memory space for the ISA pointer also falls under the category of object memory layout.

Here’s a concrete example: Define a TestObjectLayout class that inherits NSObject with the following code:

@interface TestObjectLayout : NSObject{
    bool bo;
    int num;
    char ch;
    id obj;
}
@end

@implementation TestObjectLayout

@end
Copy the code

The calculation process of the layout of its member variables is as follows:

  • instanceSizeInitialized to the parent classinstanceSizeAnd is aligned by the number of aligned bytes of the parent class. The parent classNSObjectContains onlyisaA member variable,isaIt takes 8 bytesoffsetZero, so the superclassinstanceSizeIs 8, aligned by 8 bytes;
  • instanceSizeInitialize, add member variables according to alignment rules, and updateinstanceSize.boByte alignment (noteboolType occupies 1 byte space, not 1 bit), with an offset of 8,instanceSizeUpdated to 16;
  • numAlign by 4 bytes, offset by 12,instanceSizeStill to 16;
  • chByte aligned, offset 16,instanceSizeUpdate to 24;
  • objAligned by 8 bytes, offset by 24,instanceSizeUpdate to 32. Finally confirmedinstanceSizeIs 32 bytes, aligned by 8 bytes.

From the above calculation formula of object memory layout, it can be seen that calculating the object memory layout of a class is actually a process of recursively calculating the layout of all class member variables on the inheritance chain from the root class (also can be regarded as recursion from class to the root class). Assuming that the start memory address of the TestObjectLayout object is 0x100BB134000, the memory layout of the object can be calculated according to the preceding steps, as shown in the figure below:

The reason for calculating the layout of class member variables is that the size of memory space to be allocated for the object needs to be determined during the construction of an object, and the construction object only returns the first memory address of the object, and by combining the offset of the member variable with ivar_type, You can easily locate the memory space that holds the member variable through the object address.

Note: The new version of Runtime has changed the location of the member variable list.

Three, methods,

Objc_method represents a structure method, where:

  • SELThe type ofmethod_nameMethod name;
  • String typemethod_typesRepresents the type encoding of the method, which describes the return value type of the method and the parameter types of the method;
  • IMPThe type ofmethod_impRepresents the implementation of the method.
Struct objc_method {SEL method_name; Char *method_types; // Method type encoding IMP method_IMP; } /* struct objc_selector *SEL; // struct objc_selector *SEL; Typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull,...) ;Copy the code

The objc_method_list structure represents a list of methods. The structure of objc_method_list is similar to objc_ivar_list

struct objc_method_list {
    struct objc_method_list *obsolete;

    int method_count;
#ifdef __LP64__
    int space;
#endif
    /* variable length structure */
    struct objc_method method_list[1];
} 
Copy the code

Notice in the class definition that the method list methodLists are of type struct objc_method_list * _Nullable * _Nullable, so the method list of the class is stored as a two-dimensional array, which is an array of arrays.

Note: The method list data structure of the new version of Runtime has changed considerably.

3.1 Response chain of the method

Classes contain instance Methods and class methods, which are kept in completely different places. Instance methods are stored in the class’s method list, and class methods are stored in the metaclass’s method list.

The object that receives the message is different when calling instance methods and class methods. Instance methods receive the message from an instance, and class methods receive the message from a class, for example: [someObj init] and [NSObject alloc]. This is because the two methods are kept in completely different places, instance methods in the “class” method list, and class methods in the “metaclass” method list. So what’s the difference between the flow of runtime response instance methods and class methods? It can be represented by the following figure, where:

  • A subclassSubClasscontainssubClassInstanceMethodExample method, the orange line in the figure indicates the response process of the message;
  • The root classRootClasscontainsrootInstanceMethodExample method, the magenta line in the figure indicates the response process of the message;
  • The root classRootClasscontainsrootClassMethodClass method, the blue line in the figure shows the response process of the message;

To sum up, the basic response chain of Runtime based on inheritance structure has the same structure: 1. Find the class of the object based on the ISA pointer of the object receiving the message; 2. 2. Search for classes that respond to the message from the object’s class down the inheritance chain of its superclasses, up to the root class; 3. If there is no class in the inheritance chain that can respond to the message, the message forwarding process starts.

Attribute, Protocol, and classification

The runtime.h header exposes very little information about attributes, protocols, classifications, and so on, but these can be identified as metadata that the class needs to keep. Here is a simple collection of the published code.

// Struct objc_property *objc_property_t; typedef struct { const char * _Nonnull name; // Const char * _Nonnull value; } objc_property_attribute_t; // Attribute properties // protocol related data structures#ifdef __OBJC__
@class Protocol;
#else
typedef struct objc_object Protocol;
#endifstruct objc_protocol_list { struct objc_protocol_list * _Nullable next; long count; __unsafe_unretained Protocol * _Nullable list[1]; }; // Struct objc_category *Category; struct objc_category { char * _Nonnull category_name; // Class name char * _Nonnull class_name; Struct objc_method_list * _Nullable instance_methods; Struct objc_method_list * _Nullable class_methods; // Struct objc_protocol_list * _Nullable Protocols; // Protocol list}Copy the code

Five, the summary

As mentioned more than once before, the runtime code referenced in this article is the old version of the code. The reason to start with the old version of the code is that the new version of the code is still the old version of the main architecture, but some of the data structure and implementation details are optimized. Analysis of the old version of the interface file is enough to have a general understanding of the Runtime framework, which is conducive to the huge Runtime source code project, selective, targeted learning; In addition, starting from the old code with defects can help deepen the reason why the new version should be optimized, and learn from some experience methods of code optimization.