preface

In the first three articles, we explored the underlying principles associated with OC-like objects from the following perspectives.

  • Object’s alloc flow
  • Object memory alignment
  • Nature of object

From this article, we enter into the chapter of OC class, this is the first preliminary study on OC class. This article will explore from the following perspectives, let’s start together.

  • The underlying structure of class objC_class is analyzed
  • Class isa chain and inheritance chain
  • Class data class_rw_T analysis (attributes and instance methods)

The underlying structure of a classobjc_classAnalysis of the

Class is a pointer to objc_class. What is objc_class? What is its internal structure? Let’s explore that now.

Struct objc_class = struct objc_class;

Struct objc_class (runtime.h, objc-runtime.h, objc-runtime.new.h, objc-runtime.h, objc-runtime.new.h) Objc-runtimenew. h is the current objc-runtimenew. h.

Objc_class inherits from objc_Object. Its members include ISA(in objc_Object), superclass, cache, and bits. The rest are methods and are not stored in this structure.

// Class ISA; // In objc_object Class superclass; // Parent pointer cache_t cache; // formerly cache pointer and vtable class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flagsCopy the code

Let’s analyze the size and significance occupied by these members respectively:

  • ISA: The Classl type, which is an objc_class pointer, takes 8 bytes and represents the class to which ISA points
  • Superclass: belongs to the same type as ISA and therefore occupies 8 bytes. This represents the inherited parent class
  • Cache: Is a cache_t structure that represents a class’s cached data, such as cached methods. This will be explored in a later chapter. In this article, we will analyze the size of the cache, which is defined in the source code as shown below

You can see that most of cache_t isstaticThe modified members and functions are stored in the static global area and the code area, respectively, with only the actual members taking up space_bucketsAndMaybeMaskAnd an anonymous Commons, so analyze the size of the two members.

_bucketsAndMaybeMask is of the explicit_atomic

type, which is the same size as the generic uintptr_t, that is, 8 bytes. The other member is the pool, which contains a structure member and a pointer, both 8 bytes in size, so this pool member is 8 bytes in size. Therefore, the cache_t cache occupies 16 bytes.

  • Bits: Bits stores class data, including methods, attributes, and protocols. It contains only one uintptr_t bits inside; Member, so the occupation size is 8 bytes.

This is the initial analysis of the internal structure of objC_class. In the next section, we will look at ISA and Superclass in detail.

2. Isa relation chain and inheritance relation chain

There are two members of objc_class that represent the pointing relationship of the class ISA and superclass. What is the difference between these two Pointers? We’ll explore that in this section.

2.1 Isa relationship chain

2.1.1 Metaclass appearance

In exploring the nature of an object, we have already found the class that the object belongs to through its ISA, and we will continue exploring in the same way. First, create a tartGE in libobJC source project. Here, for convenience, select the Command Line Tool under macOS and create a class BPPerson. The test method and results in main.m are as follows:

The observation shows that step A in the figure is the process of finding the class of the object Person through ISA. The same operation is done between A and B, but on the BPPerson class instead. But surprisingly, the BPPerson class points to a memory address that returns a BPPerson. However, it is clear that the two BpPersons are not the same thing, since their addresses are different, and the BPPerson with address 0x00000001000081C8 printed in step C is the real class. What about the BPPerson at address 0x00000001000081A0? Is there more than one BPPerson in memory?

To verify this problem, we write a method that compares the resulting Class to verify it as follows:

void compareBPPersonClass(int a) { Class class1 = [BPPerson class]; Class class2 = [BPPerson alloc].class; Class class3 = object_getClass([BPPerson alloc]); NSLog(@"\n\nclass1 = %@ = address %p,\nclass2 = address %p,\nclass3 = %@ = address %p,\n", class1, class1, class2, class3, class3); }Copy the code

The print result is as follows:

From this result, we find that the memory address printed by BPPerson is the same, that is, there is only one copy of BPPerson in memory. At runtime we found another piece of memory also called BPPerson, and while we can’t confirm what it is, we can look at the Mach-O file to see if there are two BPPerson symbols. Open the project’s Mach-O using the MachOView tool and search for BPPerson in the Symbol Table, resulting in the following:

We find that at compile time, the system generates a METACLASS for us. So what does METACLASS do? In fact, in the previous exploration, we found that the object found its owning class through ISA, so the class found through ISA must also be its own owning class, but we call it METACLASS, namely METACLASS. In essence, a class is also an object, called a class object. The ISA of a class object points to its metaclass. In the next section, we will explore the isa chain of the class.

2.1.2 Exploration of ISA relationship chain

In the last section, we learned what a metaclass is. What does it matter if an object, a class, or a metaclass points to isa? In this section, we will continue our exploration from the previous Demo. Last time, we found that the isa of the class points to the metaclass. Where does the ISA of the metaclass point to? Here are the results:

  • A and B in the figure are the same as in the previous section, proving that the isa of the class refers to its metaclass;
  • Step C does operations A and B on the metaclass and finds that isa points to NSObject 0x000000010036A0F0.
  • But in step D, we see that the address of the NSObject class is 0x000000010036a140, that is, 0x000000010036A0F0 points to the metaclass of NSObject, called the root metaclass.
  • Step E continues with the root metaclass, and we find that the ISA reference to the root metaclass is still the root metaclass.

It is concluded that:

Object ISA -> class, class ISA -> metaclass, metaclass ISA -> root metaclass, root metaclass ISA -> root metaclass itself

Without considering subclasses, we create a new class, BPProgramer, that inherits from BPPerson. Continue to explore its ISA direction according to the above methods, and the results are as follows:

  • As with BPPerson, the ISA of BPProgramer points to its own metaclass
  • The BPProgramer metaclass isa points to the root metaclass and does not point to the BPPerson metaclass
  • C. The root metaclass’s ISA points to itself

2.2 Inheritance chain

After exploring isa’s pointing relationships, let’s explore the class’s inheritance chain.

2.2.1 Class Inheritance relationship

First, write a function based on BPProgramer and BPPerson as follows:

void compareSuperclass(void) {
    Class class1 = [BPProgramer class];
    Class superclass = class_getSuperclass(class1);
    Class sSuperclass = class_getSuperclass(superclass);
    Class ssSuperclass = class_getSuperclass(sSuperclass);
    NSLog(@"BPProgramer == %@\n superclass == %@\n sSuperclass == %@\n ssSuperclass == %@\n", class1, superclass, sSuperclass, ssSuperclass);
}
Copy the code

The execution result is as follows:

As can be seen from the result in the figure, the inheritance relationship of class is subclass -> parent class ->… -> root NSObject -> nil.

2.2.2 Inheritance of metaclasses

Class inheritance is that simple? Obviously not, the appeal doesn’t include metaclasses, is there anything special about metaclasses? Let’s write another metaclass method to explore, as follows:

Void compareMetaSuperclass(void) {// NSObject Class = object_getClass([NSObject alloc]); // NSObject metaClass = object_getClass(Class); // NSObject rootMetaClass Class rootMetaClass = object_getClass(metaClass); // NSObject rootMetaClass Class rootRootMetaClass = object_getClass(rootMetaClass); NSLog (@ "p class \ n \ n % % p metaClass p root metaClass \ n \ n % % p spikes metaClass", class, metaClass, rootMetaClass, rootRootMetaClass); // BPPerson Class pMetaClass = object_getClass(bpPerson.class); Class pmSuperClass = class_getSuperclass(pMetaClass); / / get the superclass NSLog BPPerson metaclass (@ "% @ - % p", pmSuperClass, pmSuperClass); // BPProgramer -> BPPerson -> NSObject Class proMetaClass = object_getClass(BPProgramer.class); Class promSuperClass = class_getSuperclass(proMetaClass); / / get the superclass NSLog BPProgramer metaclass (@ "% @ - % p", promSuperClass, promSuperClass); // NSObject Root Class special case Class nsuperClass = class_getSuperclass(nsobjject); NSLog(@"%@ - %p",nsuperClass,nsuperClass); // root metaClass -> NSObject Class = class_getSuperclass(metaClass); NSLog(@"%@ - %p",rnsuperClass,rnsuperClass); }Copy the code

The code execution result is as follows:

The address of NSObject and metaclass is printed in the result above, which can be used to analyze whether the metaclass inherits NSObject or NSObject metaclass.

  • The parent of BPPerson’s metaclass is NSObject’s metaclass
  • The parent of the BPProgramer metaclass is the BPPerson metaclass
  • The parent of the root metaclass is NSObject, and the parent of NSObject is nil

2.3 Isa relationship and inheritance relationship summary

In fact, Apple has clearly explained these relationships, as shown in the picture below:

Here’s a summary:

  • There is neither ISA nor inheritance between objects
  • Isa relationships: object ISA -> class, class ISA -> metaclass, metaclass ISA -> root metaclass, root metaclass ISA -> root metaclass itself
  • Class: subclass -> parent class -> root class (NSObject) -> nil; Metaclass: Subclass metaclass -> parent metaclass -> root metaclass -> root NSObject -> nil

Class_rw_t analysis of class data — attribute and instance method exploration

While the previous two sections explored the underlying structure of a class and the chain of pointing relationships (ISA and inheritance), this section explores the data stored in the underlying class structure. By data, I mean class attributes, methods, member variables, and so on.

3.1 class_rw_t analysis

When we looked at the underlying structure of a class in section 1, we learned that the data of a class is stored in class_datA_bits_t bits. The definition in libobJC source code is as follows:

Uintptr_t bits class_data_bits_t bits is a structure containing only one uintptr_t bits; , takes 8 bytes. The bits member is used as a function, and the following two functions are found:

This is obviously a pair of setter/getter functions that modify and fetch data according to bits. According to the return value and parameter type of the function, we enter class_rw_t, which is defined as follows:

class_rw_tIn addition to its members, we found several familiar functions, which are obviously where the class’s methods, properties, and protocols are stored.

3.2 Source code analysis of the underlying storage of class data

In the previous section, we explored where class_rw_t is used to store attributes, methods, and protocols. However, we haven’t explored how it is stored. This section uses attributes as an example to explore how it is stored in the source code.

In the source code, the function used to get properties is properties(), which is defined as follows:

Property_array_t const property_array_t properties() const {auto v = get_ro_or_rwe(); if (v.is<class_rw_ext_t *>()) { return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties; } else { return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties}; }}Copy the code

The function returns property_array_t, which is defined as follows: property_array_t

Property_array_t is inherited from list_array_tt, we continue to go in to see, found that its use is a c + + template template to define the list_array_tt, allows us to use generic types, introduced from outside the type, we need the code is as follows:

We can see in the comments that Element defined in the template is the base metadata type and List is the List type of metadata. List_array_tt defines an iterator that iterates through the List and a shared object that provides an access to the List interface.

protected: class iterator { const Ptr<List> *lists; const Ptr<List> *listsEnd; typename List::iterator m, mEnd; public: iterator(const Ptr<List> *begin, const Ptr<List> *end) : lists(begin), listsEnd(end) { if (begin ! = end) { m = (*begin)->begin(); mEnd = (*begin)->end(); }} /* Other functions of iterator */ public: union {Ptr<List> List; Uintptr_t arrayAndFlag; }; /* Other functions */Copy the code

As you can see from the code,iteratorprotectdBut its internal function ispublic, so it can also be accessed externally. In fact, the storage of attributes in the source code can be simplified as shown in the following figure: property_array_tIs the outermost List,property_array_tcontainsproperty_list_tAnd theproperty_list_tcontainsproperty_t

A template allows you to declare a general pattern for a class or function, so that some of the data members of a class or the parameters and return values of member functions can be of any type. The purpose of using templates is to enable programmers to write type-independent code. Refer to this tutorial C++ tutorial for details.

Returning to the definition of property_array_t, we find that we pass in Element of type property_t and List of type property_list_t. The two parts are defined as follows:

struct property_t { const char *name; // Attribute name const char *attributes; // Attributes of attributes such as types}; Struct property_list_t: entsize_list_tt<property_t, property_list_t, 0> {Copy the code

The definition of property_t is straightforward and doesn’t require much explanation, whereas property_list_t is an empty definition that inherits from entsize_list_tt.

We find that entsize_list_tt also defines a template, and we can also pass in the required type from outside. Inside entsize_list_tt, we find two functions, circled in red, that pass in the subscript I to retrieve the corresponding Element and return a pointer to Element.

Entsize_list_tt provides a get function to access elements, but if you don’t want to call the function, you can also get it by offsetting the address, but at a step of the Element type size.

Method_array_t and protocol_ARRAY_t also inherit from list_array_tt, So they are stored in much the same way as attributes, which seems to be the reason for using templates.

3.3 Reading properties and instance methods

We know how properties, methods and protocols are stored in the source code. In this section, we read the relevant data through the LLDB instruction to verify that the conclusion of the previous section is correct. First we add a few properties and methods to BPPerson and BPProgramer. The class definition code is shown below:

3.3.1 Reading Properties

Let’s first explore how to obtain properties, open the project and run the following code to debug breakpoints:BPProgramerFor example, perform the following steps to obtain the data. The specific steps and execution results are shown as follows:

Since the result is quite long, it is divided into two figures. Below, we analyze the significance of each step:

  • p/x BPProgramer.class
    • Prints the starting address of the BPProgramer class in hexadecimal
  • p/x 0x0000000100008428 + 0x20
    • Since bits is the fourth member, and the preceding isa, superclass, and cache take up 32 bytes, we need the first address of the class + 0x20(hex of 32) to get bits
  • p (class_data_bits_t *)0x0000000100008448
    • Strong casting yields the pointer address of bits
  • p *$2
    • Get the bits to which the pointer points
  • p $3.data()
    • Get class_rw_t from the data() function
  • p *$4
    • Fetch class_rw_t under the pointer
  • p $5.properties()
    • Retrieve the property, PROTOCOL_array_t
  • p $6.list
    • Get the list, RawPtr
  • p $7.ptr
    • The RawPtr obtained in the previous step further fetchthe property_list_t pointer
  • p *$8
    • Gets the content of the pointer, property_list_t
  • p
    9. g e t ( 0 ) and p 9. The get (0) and p
    9.get(1)

    • Get bpTitle and bpLanguage, respectively

*p $8; *p $8; *p $8; *p $8

The BPProgramer class also contains a member variable, programerAge, which is also not available through properties(), indicating that member variables are not stored in properties. We’ll explore where and how to get member variables in the next chapter.

3.3.2 Reading method

In fact, through the exploration of attributes, we have understood the general steps to obtain the data of the class, for the method is relatively easy to get started. But unlike attributes, method_t doesn’t take information like the method name directly. Instead, method_t has a structure big, as shown in the following figure:

After calling the get method, we need to call big again to get the method information. The specific steps and results of reading the method are shown as follows:

The steps of the acquisition method are analyzed as follows

  • Operations prior to P 5. PTR and P ∗5. PTR and P *5. PTR and P ∗6 are the same as obtaining attributes
  • After p *$6, the results show that there are five methods
  • Big ~ p7.get(0).big ~ p7.get(0).big ~ p7.get(0).big ~ p7.get(4).big BpLanguage], [BPProgramer setBpTitle:], [BPProgramer setBpLanguage:], [bpProgramer.cxx_destruct]. Except for the last destructor, the others are setter/getter methods for properties. At this point we get the BPProgramer method, but we find that the class also stores only its own methods, and methods inherited from the parent class are not stored. And we found no access to the class method + (void) discussWithProductManager; And the specific reasons will be explored in the next chapter.

conclusion

This is the basic exploration of the class, including the structure of the class, the isa pointing relationship and inheritance relationship, class attributes and methods to obtain, we simply summarize

  • The structure of the class
    • At the bottom of the class is the objc_class structure, which inherits from ObjC_Object and contains four members isa, superclass, Cache, and bits
  • Isa chain
    • Object ISA -> class, class ISA -> metaclass, metaclass ISA -> root metaclass, root metaclass ISA -> root metaclass itself
  • Inheritance chain
    • Classes: subclass -> parent -> root class (NSObject) -> nil;
    • Metaclass: Subclass metaclass -> parent metaclass -> root metaclass -> root NSObject -> nil
  • Class properties and methods
    • The class’s information is stored in class_data_bits_t, and its internal method data() returns a class_rw_T structure from which the methods, properties(), protocols() functions can be called, Gets information about methods, properties, and protocols. See section 3 for details on how to obtain it.

The above is a preliminary study on the class, and we will continue to explore the bottom of the class in the future. Welcome to continue to pay attention to it, and also hope that you can criticize and correct it.