We have analyzed the bottom of the class from two directions in iOS basic principle – class exploration and analysis (1), and obtained the bitmap of the class. In this article, we explore the internal structure of a class. What is the structure of a class?

The structure of the class

Objective-c objects are essentially structures, and the bottom layer is objc_class. From the definition of the objc_class structure (ignoring all methods in the class), the structure code is as follows:

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;
    class_data_bits_t bits;
}
Copy the code

As can be seen from the code structure, classes are mainly composed of ISA, superclass, cache and bits. Isa has been analyzed in the last article. Superclass is the parent class, cache is the cache, and bits is the place where all data about a class is stored

Class structure diagram

In this paper, we mainly analyze the class data stored in bits. To analyze bits, we can obtain the bits data by reading the memory address. By obtaining the first address of the class, we can obtain the bits address by translating it. We know that both ISA and superclass are Pointers to structures and occupy 8 bytes of memory, but it’s not clear how much memory the cache occupies. Let’s look at the structure of cache_t

Cache_t structure

Cache_t objc-runtime-new.h cache_t objc-runtime-new.h cache_t objc-runtime-new.h cache_t is defined in objC4-818.2. Static variables are stored in the global area and do not occupy the body of the structure, so we end up with the following cache_t structure:

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

Uintptr_t = _bucketsAndMaybeMask; uintptr_t = preopt_cache_t; uintptr_t = preopt_cache_t; _bucketsAndMaybeMask uses 8 bytes of the uintptr_t modifier, _maybeMask uses 4 bytes of the Mask_T modifier corresponding to the Uint32_t, _occupied and _flags uses 2 bytes. Originalpreoptcache is a pointer to the preopt_cache_t * structure, occupying 8 bytes. Since union is mutually exclusive, only one of the cache_t structures occupies a total of 8 + 8 = 16 bytes.

Bits data analysis

According to the class_datA_bits_t definition, we can get the data of class_rw_T, which contains the attributes, methods, and protocols of the class.

// list of methods const method_array_t methods() 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)->methods; } else { return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()}; }} // Property list 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}; }} // Protocol list const protocol_array_t protocols() 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)->protocols; } else { return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols}; }}Copy the code

Below we have the actual code LLDB debugging to verify.

LLDB debugging

Start by defining the LGPerson class, which contains properties, methods, and protocols.

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol LGPersonDelegate <NSObject>

- (void)say666;

@end

@interface LGPerson : NSObject<LGPersonDelegate>

@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;
@property (nonatomic, strong) NSString *hobby;

- (void)saySomething;

- (void)say666;

@end

NS_ASSUME_NONNULL_END
Copy the code

Next, use LLDB debugging to see where the properties, method stores are in the class, and add a breakpoint to run the code.

  1. P /x lgPerson. class, get the first address of the LGPerson class0x00000001000087b8
  2. According to the previous calculation, to obtain the bits address, the first address translation is required8 plus 8 plus 16 is 32Byte, which is plus0x20.0x00000001000087b8 + 0x20 = 0x00000001000087d8
  3. p (class_data_bits_t *)0x00000001000087d8The outputclass_data_bits_t
  4. p $1->data()getclass_rw_t.p *$2seeclass_rw_tThe results of
  5. p $3.properties()Gets the attributes of the class, as you can seeproperty_list_tThe structure of thelist
  6. P $4.list, p $5.ptrYou can getproperty_list_t *Address of structure$6
  7. p *$6The outputproperty_list_tType, can count the number of attributes
  8. And then you can output the data one by one,p $7.get(0)Get the corresponding value of the property list. You can see that all the properties in the class we defined are available.

The following legend is the actual outputThe same can be done in a classMethods listandAgreement list Get () returns nothing because methods have no description method, so you need to call.big().

Agreement list

The protocol list is logically consistent with properties and methods, except that a special call is required to obtain the protocol.

conclusion

Through the structure analysis of the class above, we can see the internal composition of the class, the method of cache cache class, the attributes, methods and protocols of bits storage class, etc. Through memory translation, we can obtain bits data, and then understand the corresponding internal structure. LLDB debugging verifies the above analysis.