preface

Read the following blog posts to get a better sense of coherence

1. IOS Basic Exploration – Object Principle (PART 1)

2. IOS Basic Exploration ii – Object Principle (Middle)

3. IOS Basic Exploration iii – Object Principle (2)

Isa analyzes the metaclass

1, the metaclass

Object isa & ISA_MASK gets Shiftcls (class). What do I get if I continue to analyze shiftcls (class) according to the process of analyzing objects?

2. Root metaclass

Isa & ISA_MASK of the class gets the metaclass of the object. What does isa & ISA_MASK of the metaclass get? Can unlimited nesting dolls always open up memory?

The metaclass ISA & ISA_MASK gets a new memory address: $3, which points to NSObject, but does not have the same address as $4(root class: NSObject), but the same memory address as the $5 metaclass ($6). Apple calls $6 the root metaclass and $3 the object’s root metaclass.

The isa & ISA_MASK of the root metaclass $3 gets the same memory address: $4 as the root metaclass $3 itself, indicating that the memory address of the class is not infinite.

3. Isa link diagram

Apple’s official link chart:

  • isa: a pointer to the class definition of which the object is an instance
  • superclass: Returns the class object of the recipient’s parent class

There are two link diagrams in the figure:

Isa link diagram:

  1. Subclass: Subclass object ISA -> Subclass isa-> submetaclass ISA -> root metaclass ISA -> root metaclass ISA itself

  2. SuperClass: SuperClass object ISA -> SuperClass isa-> supermetaclass ISA -> root metaclass ISA -> root metaclass ISA itself

  3. Rootclass: Rootclass object isa -> Rootclass isa -> root metaclass isa -> root metaclass isa itself

Superclass inheritance chain:

  1. Classes: subclass -> parent -> root -> nil

  2. Metaclass: Subclass metaclass -> parent metaclass -> root metaclass -> root class -> nil

Apple’s official graphics are a little hard to understand, but they’ll be easier to follow if illustrated

Isa link diagram:

Superclass inheritance chain:

Second, the structure of class

Objc_class is a structure, so the essence of Class is a structure pointer type. How is the Class structure objc_class defined?

1. Objc_class definition

struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
    ......
};
Copy the code

Struct: Objc_class contains four member variables:

  1. Class ISA: Hide parameter ISA,
  2. Class superclass: the parent class
  3. cache_t cache: Used to be cache Pointers and virtual function tables
  4. class_data_bits_t bits: Class_rw_t and custom RR /alloc flag data

Superclass is the parent of a class, which is easy to understand. What are cache_t cache and class_data_bits_t bits?

Cache_t definition

struct cache_t { private: explicit_atomic<uintptr_t> _bucketsAndMaybeMask; union { struct { explicit_atomic<mask_t> _maybeMask; #if __LP64__ uint16_t _flags; #endif uint16_t _occupied; }; explicit_atomic<preopt_cache_t *> _originalPreoptCache; }; . }Copy the code

The cache_t structure contains two member variables:

  1. explicit_atomic<uintptr_t> _bucketsAndMaybeMask: indicates the first address of the buckets
  2. A consortiumunionThe union is mutually exclusive, self without other
  • _maybeMask: Current cache count
  • _flags: The number of buckets that can be stored in the cache. The default value is 0
  • _occupied: The number of buckets that can be stored in the cache. The default value is 0
  • _originalPreoptCache: initial cache (note that the union is mutually exclusive, if there is a value, the struct of the same level is empty)

3, class_datA_bits_t definition

struct class_data_bits_t { friend objc_class; // Values are the FAST_ flags above. uintptr_t bits; private: bool getBit(uintptr_t bit) const { return bits & bit; } // Atomically set the bits in `set` and clear the bits in `clear`. // set and clear must not overlap. void setAndClearBits(uintptr_t set, uintptr_t clear) { ASSERT((set & clear) == 0); uintptr_t newBits, oldBits = LoadExclusive(&bits); do { newBits = (oldBits | set) & ~clear; } while (slowpath(! StoreReleaseExclusive(&bits, &oldBits, newBits))); } void setBits(uintptr_t set) { __c11_atomic_fetch_or((_Atomic(uintptr_t) *)&bits, set, __ATOMIC_RELAXED); } void clearBits(uintptr_t clear) { __c11_atomic_fetch_and((_Atomic(uintptr_t) *)&bits, ~clear, __ATOMIC_RELAXED); } public: class_rw_t* data() const { return (class_rw_t *)(bits & FAST_DATA_MASK); } void setData(class_rw_t *newData) { ASSERT(! data() || (newData->flags & (RW_REALIZING | RW_FUTURE))); // Set during realization or construction only. No locking needed. // Use a store-release fence because there may be concurrent // readers of data and data's contents. uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData; atomic_thread_fence(memory_order_release); bits = newBits; } // Get the class's ro data, even in the presence of concurrent realization. // fixme this isn't really safe without a compiler barrier at least // and probably a memory barrier when realizeClass changes the data field const class_ro_t *safe_ro() const { class_rw_t *maybe_rw = data(); if (maybe_rw->flags & RW_REALIZED) { // maybe_rw is rw return maybe_rw->ro(); } else { // maybe_rw is actually ro return (class_ro_t *)maybe_rw; }}... }Copy the code

The class_data_bits_t structure contains two member variables:

  1. class_rw_t* data(): The current class is inThe runtimeInformation about attributes, methods, and protocols to be followed in the added class
  2. const class_ro_t *safe_ro(): The current class is inCompile timeThe identified properties, methods, and protocols to follow

4. Class_ro_t definition

struct class_ro_t{ uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; #ifdef __LP64__ uint32_t reserved; #endif union { const uint8_t * ivarLayout; Class nonMetaclass; }; explicit_atomic<const char *> name; // With ptrauth, this is signed if it points to a small list, but // may be unsigned if it points to a big list. void *baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; const uint8_t * weakIvarLayout; property_list_t *baseProperties; . }Copy the code
  • ro:read-onlyRead-only property

5, class_rw_t definition

struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; uint16_t witness; #if SUPPORT_INDEXED_ISA uint16_t index; #endif class_ro_t_authed_ptr<const class_ro_t> ro; method_array_t methods; property_array_t properties; protocol_array_t protocols; char *demangledName; uint32_t version; Class firstSubclass; Class nextSiblingClass; . }Copy the code

Rw: read-write Indicates the read-write attribute

6, class_ro_t and class_rw_t are different

Each class creation contains two structures, class_ro_t and class_rw_T. The difference is that class_ro_T stores information about attributes, methods, and protocols that are determined at compile time. Class_rw_t stores information about attributes, methods, and protocols that are complied with in classes added at run time. Rather, a realizeClassapi call to runtime generates a class_rw_t structure that contains class_ro_t and updates the data section pointing to the address of the class_rw_T structure.

Before calling realizeClass:

After the realizeClass call:

Third, the value of class

Define class Person:

1. Property list

Get the Person class attribute:

2. List of instance methods

Get the Person instance method (also called the object method) :

Method preperty_list_t = preperty_list_t; method list_t = preperty_list_t; WWDC2020 explains that member variables are stored in class_ro_t, and copyclass_ro_t into class_rw_T after the class calls the realizeClass method, which requires fetching the data stored in class_ro_T.

Member variable list

Get the Person member variable:

According to WWDC2020, it is true that member variables of a class are retrieved from the class_ro_T structure contained in the class_rw_T structure, but why are member variables of a class stored in class_RO_T and class attributes stored in class_rw_T? What’s the difference between a member variable and an attribute?

  • Member variables: The definition or implementation of a class, in{}All variables declared inMember variables. Member variables are used toInside the class(No setter and getter methods are generated), a variable that does not require contact with the outside world.
  • The instance variables:Special member variablesInstance variables are essentially member variables, but instances are class specific. Instance refers to the declaration of a class in which a member variable is of typeClass typesThe member variables of this class areThe instance variables.
  • attribute: is a mechanism in the OC language, used in OC@propertyTo declare a property, actually@propertyIs a syntactic sugar that is automatically generated by the compilersetterandgetterMethods.

Define the Person class:

Low-level implementation :(in the object principle of this article, there is a detailed description of how to view the underlying source file)

Properties in source files are member variables with _ and automatically generate setter and getter methods.

List of class methods

By getting the instance method of the class (object method), we find that object methods are stored in the class, and only object methods are stored in the class. Instance methods are stored in classes. Are class methods stored in metaclasses?

Get the Person class method:

Gets the list of metaclass methods stored in the class_rw_t structure of the metaclass, to distinguish instance methods from class method duplication problems.