2019-10-17

In the previous six articles, you introduced the implementation of objective-C classes and objects by analyzing runtime source code. This article mainly discusses the following issues: object-oriented objective-C language code, how to parse into C language code. You can get more details about Runtime’s object-oriented implementation by exploring the process.

The compilation environment used in this article is Mac OS X 10.14 + Apple LLVM 10.0.1 (Clang-1001.0.46.4). The new version of Mac OS X 10.15 uses LLVM 11.x. X, and the Runtime itself should also be updated, so the actual operation may be slightly different from the description in this article, for example: When LLVM 11.x.x rewrite objective-c, the __DATA segment will be stored in __DATA and __DATA_CONST segments, respectively, depending on whether they are constants. A data segment contains a data section. A data segment contains a data section. A data segment contains a data section. Since I didn’t think of a proper translation name, they are generally called data segments. Usually, data segments with all uppercase names are data segments and data sections with all lowercase names.

Note: When the previous six articles introduced Runtime implementation classes and objects, the protocol was not covered separately because the process of using, implementing, saving, and loading a protocol is very similar to that of a class. If you want to know the implementation details, you can see the source code: protocol_t, protocol_list_t data structure definition, objc_getProtocol(…) Function implementation, class_conformsToProtocol(…) Function implementation, and _read_images(…) The code associated with loading protocol information in a function.

A, thinking

Objective-c projects are usually compiled using the xcodeBuild command line tool, Clang as the front-end compiler is responsible for preprocessing and compiling language independent intermediate code, and LLVM as the back-end compiler is responsible for compiling and linking platform related binary files. Among them, in the Clang preprocessing phase, the objective-C language code in the project is transformed into C language code. You can run the clang-rewrite-objc command to perform this process. The main contents of the transformation are as follows:

  • Type: Objective-C class, classification, protocol, member variable, method, classification and other types are converted to C structure types.
  • Data: Objective-C metadata defined by objective-C types is converted to C structure data and recorded as variables and stored in data segments;
  • Functions: Objective-C class methods, block implementation logic encapsulated as C functions.

Design objective-C written demo program, save as main.m source file. The code basically covers the basic elements of Objective-C object-oriented (except protocol), including classes, member variables, methods, properties, classification definition, object construction, object member variables, property access, method invocation. The code for main.m is as follows:

#import <Foundation/Foundation.h>

#pragma mark - TestClass class definition
@interface TestClass : NSObject {
    NSString* stringIvar;
}

@property(strong, nonatomic, getter=stringPropertyGetter, setter=stringPropertySetter:) NSString* stringProperty;

-(NSString*)printContent:(NSString*)content;

@end

@implementation TestClass

+(void)load{
    NSLog(@"");
}

-(NSString*) printContent:(NSString*)content {
    NSLog(@"Print content: %@", content);
}

@end

#pragma mark - TestClass specifies the class definition for TestCategory
@interface TestClass (TestCategory)

@property(strong) NSString* stringCategoryProperty;

-(NSString*)printCategoryContent:(NSString*)content;

@end

@implementation TestClass (TestCategory)

-(NSString*) printCategoryContent:(NSString*)content {
    NSLog(@"Print category content: %@", content);
}

@end

#pragma mark - Main entryInt main(int argc, char * argv[]) {@autoreleasepool {// Build an instance of TestClass*testObj = [[TestClass alloc] init]; // Access the property value of the TestClass object NSString* strProp =testObj.stringProperty; // Call the TestClass method [testObj printContent:@"Something"]; }}Copy the code

Run the clang-rewrite-objc main.m command to convert objective-C language code to C language code. In the current directory, the C++ source file of main. CPP is generated. The source code for main. CPP is the breakthrough to answer the question raised at the beginning. Main.cpp has hundreds of thousands of lines in total, and you only need to focus on a few hundred critical lines.

2. Data structure

Because the objc_class structure type is private, you need to define the isomorphic _objc_class structure to expose the class’s data structure. For storing class data, you only need to define the isomorphic structure of class_ro_t, not class_rw_t. Because these isomorphic structures are defined to describe the compile-time decision data of the class, whereas the data in class_rw_t is run-time decision data, there is no need to save the class_rw_t data of the class at compile time. The code for defining all isomorphic structure types is as follows.

// struct _prop_t {const char *name; const char *attributes; }; Struct _protocol_t; // Struct _objc_method {struct objc_selector * _cmd; const char *method_type; void *_imp; }; Struct _protocol_t {void * isa; // NULL const char *protocol_name; const struct _protocol_list_t * protocol_list; // super protocols const struct method_list_t *instance_methods; const struct method_list_t *class_methods; const struct method_list_t *optionalInstanceMethods; const struct method_list_t *optionalClassMethods; const struct _prop_list_t * properties; const unsigned int size; // sizeof(struct _protocol_t) const unsigned int flags; // = 0 const char ** extendedMethodTypes; }; // struct _ivar_t {unsigned long int *offset; // pointer to ivar offset location const char *name; const char *type; unsigned int alignment; unsigned int size; }; // The class_ro_t data for the class. // Struct _class_ro_t {unsigned int flags; unsigned int instanceStart; unsigned int instanceSize; unsigned int reserved; const unsigned char *ivarLayout; const char *name; const struct _method_list_t *baseMethods; const struct _objc_protocol_list *baseProtocols; const struct _ivar_list_t *ivars; const unsigned char *weakIvarLayout; const struct _prop_list_t *properties; }; // struct _class_t {struct _class_t *isa; struct _class_t *superclass; void *cache; void *vtable; struct _class_ro_t *ro; }; Struct _category_t {const char *name; struct _class_t *cls; const struct _method_list_t *instance_methods; const struct _method_list_t *class_methods; const struct _protocol_list_t *protocols; const struct _prop_list_t *properties; };Copy the code

Note: The latest version of Runtime contains the preoptimize option that seems to support compile-time generation of class_rw_t. This part of the code is obscure and not mainstream, so you can check out the source code if you’re interested.

Object oriented element definition

3.1 Class implementation

The OBJC_CLASS_$_TestClass variable that defines the _class_t struct type represents the TestClass class. Bits holds the address of the _OBJC_CLASS_RO_$_TestClass variable (described in detail in 3.2). _OBJC_CLASS_RO_$_TestClass is class_ro_t data for class TestClass.

The OBJC_METACLASS_$_TestClass variable that defines the _class_t struct type represents the metaclass of class TestClass. Bits holds the address of the _OBJC_METACLASS_RO_$_TestClass variable (described in detail in 3.2). _OBJC_METACLASS_RO_$_TestClass is class_ro_t data for the metaclass of TestClass.

Static void OBJC_CLASS_SETUP_$_TestClass(void) This static function is used to initialize data for the TestClass and metaclath.

Note: __attribute__ is used in code to specify compile Attributes: Function Attributes, Variable Attributes, Type Attributes. In this case, obviously, the variable attributes as a modifier. Unused indicates that the variable may not be used. Section specifies the data segment to which the variable will be stored. The parameter is the data segment name. (For data section reference: Linux object file)

// Defines an empty method buffer extern"C"__declspec(dllimport) struct objc_cache _objc_empty_cache; . / / import OBJC_METACLASS_$_NSObjectThe metaclass variable extern"C" __declspec(dllimport) struct _class_t OBJC_METACLASS_$_NSObject; / / define OBJC_METACLASS_$_TestClassThe variable represents the extern metaclass of class TestClass"C" __declspec(dllexport) struct _class_t OBJC_METACLASS_$_TestClass __attribute__ ((used, section ("__DATA,__objc_data"))) = {
	0, // &OBJC_METACLASS_$_NSObject,
	0, // &OBJC_METACLASS_$_NSObject,
	0, // (void *)&_objc_empty_cache,
	0, // unused, was (void *)&_objc_empty_vtable,
	&_OBJC_METACLASS_RO_$_TestClass}; / / import OBJC_CLASS_$_NSObjectClass variables extern"C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSObject; / / define OBJC_CLASS_$_TestClassThe variable represents TestClass extern"C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_TestClass __attribute__ ((used, section ("__DATA,__objc_data"))) = {
	0, // &OBJC_METACLASS_$_TestClass,
	0, // &OBJC_CLASS_$_NSObject,
	0, // (void *)&_objc_empty_cache,
	0, // unused, was (void *)&_objc_empty_vtable,
	&_OBJC_CLASS_RO_$_TestClass}; // Initialize OBJC_CLASS_$_TestClassVariable that initializes the TestClass operation static void OBJC_CLASS_SETUP_$_TestClass(void) {// initialize the metaclass OBJC_METACLASS_ of TestClass$_TestClass.isa = &OBJC_METACLASS_$_NSObject;
	OBJC_METACLASS_$_TestClass.superclass = &OBJC_METACLASS_$_NSObject;
	OBJC_METACLASS_$_TestClass.cache = &_objc_empty_cache; // Initialize the TestClass OBJC_CLASS_$_TestClass.isa = &OBJC_METACLASS_$_TestClass;
	OBJC_CLASS_$_TestClass.superclass = &OBJC_CLASS_$_NSObject;
	OBJC_CLASS_$_TestClass.cache = &_objc_empty_cache;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CLASS_SETUP[] = {
	(void *)&OBJC_CLASS_SETUP_$_TestClass};Copy the code

3.1.1 Save the class_ro_t of the class

The class_ro_t data used to hold the TestClass class and metaclass is as follows. The focus is on class_ro_t of class TestClass. The metaclass contains relatively little metadata, mainly class methods:

  • flags: the flag is set to0, the focus is onRO_METABit is0Is not a metaclass;
  • instanceStartThe instance offset is set to the first member variablestringIvarThe offset of. Among them,__OFFSETOFIVAR__(struct TestClass, stringIvar), used to obtainTestClassIn the structurestringIvarThe offset of a member;
  • instanceSize: The instance size isTestClass_IMPLThe space occupied;
  • reserved:0;
  • ivarLayout:0In the class realizing stage;
  • name: a class called"TestClass";
  • baseMethods: save_OBJC_$_INSTANCE_METHODS_TestClassThe address of the array,_OBJC_$_INSTANCE_METHODS_TestClassAn array holds a list of the basic methods of the class;
  • baseProtocols:0.TestClassNo agreement has been inherited;
  • ivars: save_OBJC_$_INSTANCE_VARIABLES_TestClassThe address of the array,_OBJC_$_INSTANCE_VARIABLES_TestClassAn array holds a list of class member variables;
  • weakIvarLayout:0In the class realizing stage;
  • properties: save_OBJC_$_PROP_LIST_TestClassThe address of the array,_OBJC_$_PROP_LIST_TestClassAn array holds a list of the basic properties of the class;

The method list of class_ro_t of the TestClass metaclass holds the load class methods implemented in TestClass. It also proves that class methods are stored in the metaclass’s base method list and instance methods are stored in the class’s base method list.

static struct _class_ro_t _OBJC_METACLASS_RO_$_TestClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	1, sizeof(struct _class_t), sizeof(struct _class_t), 
	(unsigned int)0, 
	0, 
	"TestClass",
	(const struct _method_list_t *)&_OBJC_$_CLASS_METHODS_TestClass, 0, 0, 0, 0}; // Calculate the offset of the MEMBER in the TYPE structure#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)

static struct _class_ro_t _OBJC_CLASS_RO_$_TestClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	0, __OFFSETOFIVAR__(struct TestClass, stringIvar), sizeof(struct TestClass_IMPL), 
	(unsigned int)0, 
	0, 
	"TestClass",
	(const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_TestClass,
	0, 
	(const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_TestClass,
	0, 
	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_TestClass};Copy the code

3.2 Object implementation

The data type of an object of class TestClass is defined as the TestClass_IMPL structure. That is, the structure of the memory blocks allocated to build TestClass objects is laid out according to the memory structure of TestClass_IMPL.

  • TestClass_IMPLThe first member of the ivar layout is the parent class that needs to save the data, becauseTestClassinheritanceNSObjectSo is theNSObject_IMPL, or else****_IMPL.
  • TestClass_IMPLOther members of the class, which holds data for members of the class definition;
. struct NSObject_IMPL { Class isa; }; . typedef struct objc_object NSString; . struct TestClass_IMPL { struct NSObject_IMPL NSObject_IVARS; NSString *stringIvar; NSString *_stringProperty; };Copy the code

3.2.1 Building Objects

TestClass* testObj = [[TestClass alloc] init]; Convert to C code as follows. It looks like a lot of code, but the principle is simply to send an Alloc message to the TestClass (class method to the class), and then an init message to the built object (instance method to the object).

TestClass* testObj = ((TestClass *(*)(id, SEL))(void *)objc_msgSend)((id)((TestClass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TestClass"), sel_registerName("alloc")), sel_registerName("init"));
Copy the code

3.3 Member Variables

The list of TestClass member variables is stored in the _OBJC_$_INSTANCE_VARIABLES_TestClass variable. Here we define an isomorphic structure of ivar_list_t to hold a list of member variables of a fixed length of 2. The first element holds the iVAR_t structure of the stringIvar member variable, and the second element holds the _stringProperty member variable. Take stringIvar as an example:

  • offset: Specifies the offset of a member variable. The type isunsigned long int *The initial value is set toOBJC_IVAR_$_TestClass$stringIvarThe address of the variable where the initial value stored isTestClassIn the structurestringIvarThe offset of a member;
  • name: Specifies the name of a member variable"stringIvar";
  • type: marks the type of a member variable"@\"NSString\""
  • alignment: to3, because the alignment is 8 bytes;
  • size: to8, because it takes 8 bytes;
extern "C" unsigned long int OBJC_IVAR_$_TestClass$stringIvar __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct TestClass, stringIvar);
extern "C" unsigned long int OBJC_IVAR_$_TestClass$_stringProperty __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct TestClass, _stringProperty);

static struct /*_ivar_list_t*/ {
	unsigned int entsize;  // sizeof(struct _prop_t)
	unsigned int count;
	struct _ivar_t ivar_list[2];
} _OBJC_$_INSTANCE_VARIABLES_TestClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_ivar_t),
	2,
	{{(unsigned long int *)&OBJC_IVAR_$_TestClass$stringIvar."stringIvar"."@\"NSString\"", 3, 8},
	 {(unsigned long int *)&OBJC_IVAR_$_TestClass$_stringProperty."_stringProperty"."@\"NSString\"", 3, 8}}};Copy the code

Note: The purpose of setting the offset to extern (external reference global variable) is to support non-Fragile Instance variable, and when the runtime class is in the class recognition stage, You need to dynamically adjust the ivar layout of the class according to the instanceSize of the parent class. In this case, you may need to modify the offset of the member variable.

3.3.1 Accessing member variable values

In the Runtime source code reading 5 (Properties), which introduces properties, the question was raised about how to get the value of an object’s member variable at Runtime. The answer can be found in the code. The object’s address self and the OBJC_IVAR_$_TestClass$stringIvar variable that records the offsets of the object’s member variables are efficiently fetched. (*(NSString **)((char *)self + OBJC_IVAR_$_TestClass$_stringIvar)).

3.4 Method List

The list of basic methods for TestClass is stored in the _OBJC_$_INSTANCE_METHODS_TestClass variable. A isomorphic structure for method_list_t is defined to hold a list of methods. The list has a fixed length of 3. Note that it does not contain the classified method list, because the classified method list does not belong to class_ro_t. Method list:

  • The first element is savedprintContent:Member variablemethod_tStructure;
  • The second element is savedstringPropertyGetterMember variable;
  • The second element is savedstringPropertySetter:Member variable;

Take printContent: as an example:

  • _cmd: Method name"printContent:"
  • _type: Set the type encoding to"@ 24 @ 0:8 @ 16";
  • _impMethod: theIMPTo point to_I_TestClass_printContent_Function pointer to;

The base method list of the metaclasses of TestClass is stored in the _OBJC_$_CLASS_METHODS_TestClass variable, and contains only the load class methods implemented in TestClass.

static __NSConstantStringImpl __NSConstantStringImpl__var_folders_qh_b2s8cgys01g3jxc0vq1y5ks80000gn_T_main_adaf3a_mi_0 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"Print content: %@"17}; static __NSConstantStringImpl __NSConstantStringImpl__var_folders_qh_b2s8cgys01g3jxc0vq1y5ks80000gn_T_main_d92eec_mi_0 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"", 0}; static void _C_TestClass_load(Class self, SEL _cmd) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_qh_b2s8cgys01g3jxc0vq1y5ks80000gn_T_main_d92eec_mi_0); } static NSString * _I_TestClass_printContent_(TestClass * self, SEL _cmd, NSString *content) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_qh_b2s8cgys01g3jxc0vq1y5ks80000gn_T_main_adaf3a_mi_0, content); } static NSString * _I_TestClass_stringPropertyGetter(TestClass * self, SEL _cmd) {return (*(NSString **)((char *)self + OBJC_IVAR_$_TestClass$_stringProperty)); }
static void _I_TestClass_stringPropertySetter_(TestClass * self, SEL _cmd, NSString *stringProperty) { (*(NSString **)((char *)self + OBJC_IVAR_$_TestClass$_stringProperty)) = stringProperty; }


static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[3];
} _OBJC_$_INSTANCE_METHODS_TestClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	3,
	{{(struct objc_selector *)"printContent:"."@ 24 @ 0:8 @ 16", (void *)_I_TestClass_printContent_},
	{(struct objc_selector *)"stringPropertyGetter"."@ @ 0:8 16", (void *)_I_TestClass_stringPropertyGetter},
	{(struct objc_selector *)"stringPropertySetter:"."v24@0:8@16", (void *)_I_TestClass_stringPropertySetter_}}
};

static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_CLASS_METHODS_TestClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"load"."v16@0:8", (void *)_C_TestClass_load}}
};
Copy the code

3.4.1 Method invocation

[testObj printContent:@”Something”]; Convert to C code as follows. It’s a long string again, but the principle is simple: send a printContent: message to the testObj object, passing the argument “Something”. This string constant is stored in the __cfString data segment.

static __NSConstantStringImpl __NSConstantStringImpl__var_folders_qh_b2s8cgys01g3jxc0vq1y5ks80000gn_T_main_adaf3a_mi_2 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"Something", 9}; ((NSString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)testObj, sel_registerName("printContent:"), (NSString *)&__NSConstantStringImpl__var_folders_qh_b2s8cgys01g3jxc0vq1y5ks80000gn_T_main_adaf3a_mi_2);
Copy the code

3.5 attributes

The list of basic properties for TestClass is stored in the _OBJC_$_PROP_LIST_TestClass variable. Since a class declares a property to specify at sign synthesize by default, it automatically generates the member variables associated with the property, as well as getter and setter methods associated with the property. So, TestClass defines the stringProperty property and generates the _stringProperty variable and its corresponding data in the list of member variables in 3.4. And the stringPropertyGetter method that generates the property in the 3.5 method list and the function _I_TestClass_stringPropertyGetter and setter that the IMP points to Method stringPropertySetter: and its IMP to the function _I_TestClass_stringPropertySetter_.

The property list has a fixed length of 1 and contains only the _prop_t body that represents the stringProperty property:

  • name: The property name is set to"stringProperty";
  • attributes: The feature is set to"T@\"NSString\",&,N,GstringPropertyGetter,SstringPropertySetter:,V_stringProperty";
static struct /*_prop_list_t*/ {
	unsigned int entsize;  // sizeof(struct _prop_t)
	unsigned int count_of_properties;
	struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_TestClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_prop_t),
	1,
	{{"stringProperty"."T@\"NSString\",&,N,GstringPropertyGetter,SstringPropertySetter:,V_stringProperty"}}};Copy the code

3.5.1 Accessing Properties

The code to access the property in _I_TestClass_stringPropertyGetter and _I_TestClass_stringPropertySetter_ is automatically generated by Runtime by recording the memory offset of the associated member variable. It operates directly on the memory associated with member variables and is therefore highly efficient. NSString* strProp = testobj.stringProperty; Is converted to the following C code. The principle is to get the name of the getter method through the attribute of the attribute, and call the getter method directly.

NSString* strProp = ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)testObj, sel_registerName("stringPropertyGetter"));
Copy the code

3.6 classification

The categories are saved in _OBJC_$_CATEGORY_TestClass_$_TestCategory

  • name: classification of"TestClass"(Fixme: not sure why set to class name);
  • cls:0;
  • instance_methods: Saves the instance method list_OBJC_$_CATEGORY_INSTANCE_METHODS_TestClass_$_TestCategoryThe address of the array,_OBJC_$_CATEGORY_INSTANCE_METHODS_TestClass_$_TestCategoryThe array to saveTestCategoryA list of instance methods defined by the category;
  • class_methods:0Because theTestCategoryThe classification does not contain class methods;
  • protocols:0Because theTestCategoryNot complying with any agreement;
  • properties:0, the property list is saved_OBJC_$_PROP_LIST_TestClass_$_TestCategoryThe address of the array,_OBJC_$_PROP_LIST_TestClass_$_TestCategoryThe array to saveTestCategoryA list of attributes defined by the category;

When initializing, set:

  • cls: point toOBJC_CLASS_$_TestClass;
static struct _category_t _OBJC_$_CATEGORY_TestClass_$_TestCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"TestClass",
	0, // &OBJC_CLASS_$_TestClass,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_TestClass_$_TestCategory,
	0,
	0,
	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_TestClass_$_TestCategory}; static void OBJC_CATEGORY_SETUP_$_TestClass_$_TestCategory(void ) {
	_OBJC_$_CATEGORY_TestClass_$_TestCategory.cls = &OBJC_CLASS_$_TestClass;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
	(void *)&OBJC_CATEGORY_SETUP_$_TestClass_$_TestCategory};Copy the code

The method of classification definition is stored in

static NSString * _I_TestClass_TestCategory_printCategoryContent_(TestClass * self, SEL _cmd, NSString *content) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_qh_b2s8cgys01g3jxc0vq1y5ks80000gn_T_main_adaf3a_mi_1, content);
}

static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_TestClass_$_TestCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"printCategoryContent:"."@ 24 @ 0:8 @ 16", (void *)_I_TestClass_TestCategory_printCategoryContent_}}
};
Copy the code

The attributes of the class definition are stored in the _OBJC_$_PROP_LIST_TestClass_$_TestCategory variable.

static struct /*_prop_list_t*/ {
	unsigned int entsize;  // sizeof(struct _prop_t)
	unsigned int count_of_properties;
	struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_TestClass_$_TestCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_prop_t),
	1,
	{{"stringCategoryProperty"."T@\"NSString\",&"}}};Copy the code

Load objective-C elements

Add the classes defined in the source file to the L_OBJC_LABEL_CLASS_$list. The list is of length 1, because only one class, TestClass, is defined. The element holds the address of OBJC_CLASS_$_TestClass.

The defined category is added to the L_OBJC_LABEL_CATEGORY_$list, which is 1 in length because only one category, TestCategory, is defined. The element holds the address of _OBJC_$_CATEGORY_TestClass_$_TestCategory.

static struct _class_t *L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= {
	&OBJC_CLASS_$_TestClass}; static struct _class_t *_OBJC_LABEL_NONLAZY_CLASS_$[] = { &OBJC_CLASS_$_TestClass}; static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
	&_OBJC_$_CATEGORY_TestClass_$_TestCategory}; static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };Copy the code

4.1 Lazy loading of classes

In addition to defining variables that hold class elements and class elements, the above code also contains _OBJC_LABEL_NONLAZY_CLASS_$, which holds the address of TestClass. This list is used to hold nonlazy load classes (Objective-C: What is a lazy class?). . A non-lazy load class is a class that implements a load method. These classes need to be class realizing and the load method implemented immediately when the image is loaded. Runtime defines the _getObjc2NonlazyClassList function to get the non-lazy load class stored in the __objc_NLclslist segment of the mirror.

In contrast, a lazy load class simply represents a class that does not implement the load method, but is not truly a lazy load. Strictly speaking, lazy loading of a class means, The resolution of the class (including loading the static metadata class_ro_t from the image, class realizing the dynamic metadata class_rw_t generated by the class, and class loading the load method to initialize the class) is delayed until the binary file is officially loaded. Previously, Runtime only knew this was a class and marked it as a Future Class. In general, binaries such as iOS App, Framework, static Library, etc., developed and compiled by developers, are statically loaded. The classes defined by them are statically loaded during the application loading phase. Only. TBD,. Dylib, or the framework that encapsulates. TBD,. Dylib (usually only Apple’s own framework allows the encapsulation of dynamic libraries) support dynamic loading into memory

Note: the non-lazy load class list _OBJC_LABEL_NONLAZY_CLASS_$is not added to __objc_nlclslist in main. CPP obtained with clang-rewrite-objc main.m. But judging from the data section of main.o printed in 4.3, it is actually added. The concept of mirrors will be covered in more detail in the next article, but suffice it to say that mirrors hold objective-C metadata defined in the source file.

4.2 Code for obtaining data segments

The main code that defines object-oriented elements in main.cpp is the C language implementation that defines those elements and defines the metadata as static or global variables to be added to a specific data segment.

What is added must be read. Runtime uses the GETSECT macro to define a series of functions that read data from a specific data segment. For example, GETSECT(_getObjc2ClassList, classref_t, “__objc_classlist”) defines: The function _getObjc2ClassList is used to get the __objC_classList data segment, which is stored as classref_t. Therefore, if main. CPP is compiled into a mirror, runtime will get L_OBJC_LABEL_CLASS_$when it calls _getObjc2ClassList to get all the classes defined in the mirror. This includes, of course, the stored definition of the TestClass variable OBJC_CLASS_$_TestClass.

// Retrieve the external function of the data segment, Extern uint8_t * getSectionData (const struct mach_header_64 * MHP, const char * segName, const char *sectname, unsigned long *size); template <typename T> T* getDataSection(const headerType *mhdr, const char *sectname, size_t *outBytes, size_t *outCount) { unsigned long byteCount = 0; T* data = (T*)getsectiondata(mhdr,"__DATA", sectname, &byteCount);
    if(! data) { data = (T*)getsectiondata(mhdr,"__DATA_CONST", sectname, &byteCount);
    }
    if(! data) { data = (T*)getsectiondata(mhdr,"__DATA_DIRTY", sectname, &byteCount);
    }
    if (outBytes) *outBytes = byteCount;
    if (outCount) *outCount = byteCount / sizeof(T);
    return data;
}

#define GETSECT(name, type, sectname) \
    type *name(const headerType *mhdr, size_t *outCount) {              \
        return getDataSection<type>(mhdr, sectname, nil, outCount); \} \type *name(const header_info *hi, size_t *outCount) {               \
        return getDataSection<type>(hi->mhdr(), sectname, nil, outCount); / / \}function name                 content type     section name
GETSECT(_getObjc2SelectorRefs,        SEL,             "__objc_selrefs"); 
GETSECT(_getObjc2MessageRefs,         message_ref_t,   "__objc_msgrefs"); 
GETSECT(_getObjc2ClassRefs,           Class,           "__objc_classrefs");
GETSECT(_getObjc2SuperRefs,           Class,           "__objc_superrefs");
GETSECT(_getObjc2ClassList,           classref_t,      "__objc_classlist");
GETSECT(_getObjc2NonlazyClassList,    classref_t,      "__objc_nlclslist");
GETSECT(_getObjc2CategoryList,        category_t *,    "__objc_catlist");
GETSECT(_getObjc2NonlazyCategoryList, category_t *,    "__objc_nlcatlist");
GETSECT(_getObjc2ProtocolList,        protocol_t *,    "__objc_protolist");
GETSECT(_getObjc2ProtocolRefs,        protocol_t *,    "__objc_protorefs");
GETSECT(getLibobjcInitializers,       UnsignedInitializer, "__objc_init_func");
Copy the code

4.3 validation

First, compile the main.m source file with the clang -framework Foundation -o main.o main.m command, and output the main.o object file in the current directory. Otool -o -v main.o (-o print the Objective -c segment) otool -o -v main.o (-o print the Objective -c segment) The flag value of _OBJC_IMAGE_INFO is inconsistent with that of main. CPP. The data in the main.o object file compiled by Clang should prevail.

main.o:
Contents of (__DATA,__objc_classlist) section
00000001000010b8 0x1000012b8 _OBJC_CLASS_$_TestClass
           isa 0x100001290 _OBJC_METACLASS_$_TestClass
    superclass 0x0 _OBJC_CLASS_$_NSObjectcache 0x0 __objc_empty_cache vtable 0x0 data 0x100001210 (struct class_ro_t *) flags 0x0 instanceStart 8 instanceSize 24  reserved 0x0 ivarLayout 0x0 name 0x100000ef0 TestClass baseMethods 0x1000010d0 (struct method_list_t *) entsize 24 count 4 name 0x100000f60printCategoryContent:
		     types 0x100000f89 @24@0:8@16
		       imp 0x100000d10 -[TestClass(TestCategory) printCategoryContent:]
		      name 0x100000f0c printContent:
		     types 0x100000f89 @24@0:8@16
		       imp 0x100000c70 -[TestClass printContent:]
		      name 0x100000f1a stringPropertyGetter
		     types 0x100000f94 @16@0:8
		       imp 0x100000cb0 -[TestClass stringPropertyGetter]
		      name 0x100000f2f stringPropertySetter:
		     types 0x100000f9c v24@0:8@16
		       imp 0x100000cd0 -[TestClass stringPropertySetter:]
            baseProtocols 0x0
                    ivars 0x1000011c8
                    entsize 32
                      count 2
			   offset 0x100001288 8
			     name 0x100000f45 stringIvar
			     type 0x100000fa7 @"NSString"
			alignment 3
			     size 8
			   offset 0x100001280 16
			     name 0x100000f50 _stringProperty
			     type 0x100000fa7 @"NSString"
			alignment 3
			     size 8
           weakIvarLayout 0x0
           baseProperties 0x100001138
                    entsize 16
                      count 2
			     name 0x100000ec0 stringCategoryProperty
			attributes 0x100000ed7 T@"NSString",&
			     name 0x100000e47 stringProperty
			attributes 0x100000e56 T@"NSString",&,N,GstringPropertyGetter,SstringPropertySetter:,V_stringProperty
Meta Class
           isa 0x0 _OBJC_METACLASS_$_NSObject
    superclass 0x0 _OBJC_METACLASS_$_NSObject
         cache 0x0 __objc_empty_cache
        vtable 0x0
          data 0x100001180 (struct class_ro_t *)
                    flags 0x1 RO_META
            instanceStart 40
             instanceSize 40
                 reserved 0x0
               ivarLayout 0x0
                     name 0x100000ef0 TestClass
              baseMethods 0x100001160 (struct method_list_t *)
		   entsize 24
		     count 1
		      name 0x100000f07 load
		     types 0x100000f81 v16@0:8
		       imp 0x100000c40 +[TestClass load]
            baseProtocols 0x0
                    ivars 0x0
           weakIvarLayout 0x0
           baseProperties 0x0
Contents of (__DATA,__objc_classrefs) section
0000000100001278 0x1000012b8 _OBJC_CLASS_$_TestClass
Contents of (__DATA,__objc_catlist) section
Contents of (__DATA,__objc_imageinfo) section
  version 0
    flags 0x40 OBJC_IMAGE_HAS_CATEGORY_CLASS_PROPERTIES
Copy the code

Note that the above data does not contain the __objc_catList data segment for holding the class. This is because there are no methods called in the main.m code to the TestCategory class, so it will not be loaded. There are two ways to fix it:

  • inmainIn the function[testObj printCategoryContent:@"Something"];;
  • useclang -framework Foundation -all_load -o main.o main.mCommand compilation, add-all_loadThe compile option forces all Objective-C elements to be compiled;

Note: The __objc_NLCLslist segment holds the non-lazy Load class list and the __objC_NLcatList segment holds the non-lazy Load category list. Does not appear in the Objective -c metadata printed by the otool -o command. Otool -v -s __DATA __objc_nlclslist main. O command to check that the __objc_nlclslist segment does contain the address of the TestClass class. The objective-C metadata in the main. CPP generated by clang-rewrite-objc is slightly different from the objective-C metadata actually contained in the compiled object file main.o. Most likely because the Clang compiler has some other metadata processing logic between the rewrite Objective-C phase and the compile and generate object files phase.

Five, the summary

  • When converting objective-C elements to C elements, you need to define the isomorphic structure types of those elements’ structures in Runtime to expose their data structures. They are usually defined as static variables of these isomorphic structure types and stored in specific data segments;

  • Runtime defines functions that load objective-C metadata stored in an image by reading data from a specific segment. For example, _getObjc2ClassList is used to get metadata for all classes stored in the image’s __objc_ClassList segment. _getObjc2CategoryList Gets all the classified metadata held in the __objc_catList data segment of the mirror;

  • The in-memory structure of a class is the _class_t structure, which is isomorphic to the objc_class structure in Runtime. This structure contains metadata such as the class name, superclass, basic method list in class_ro_t, basic property list, and protocol list. Class_rw_t data is generated dynamically at run time;

  • The next article covers the application loading process.