In my last blog post, I got an overview of the structure of classes like iOS Low-level Exploration (top)

The structure of the class

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

class_data_bits

We mainly explore class_data_bits_t bits, which contain information about the class we care about. So how do we carry it? Take a look at the JPPerson class below

@interface JPPerson : NSObject
{
	int age;
}
@property (nonatomic.copy) NSString *name;
@property (nonatomic.copy) NSString *hobby;

- (void)sayHello;
+ (void)sayNB;
@end
@implementation JPPerson

- (instancetype)init{
	 if (self = [super init]) {
		  self.name = @"reno";
	 }
	 return self;
}
- (void)sayHello{
}
+ (void)sayNB{
}
@end
Copy the code

uselldbdebuggingx/4gxThe command is printedJPPersonMemory information for this classThe first is theisa; The second is thesuperclass.poIt is NSObject.JPPersonIs inheritedNSObject; And so on, the third one iscacheThe fourth isbits.

bits

We need to understandbitsThe inside of thedataInformation. How do I get it? Why don’t we just have an address? So now that we knowisa(initial address), is it ok to passThe pointer offset.Memory migrationGet it? So how many are we going to offset?

To get bits, Pointers in memory must be shifted to bits. I know isa is 8 bytes long and superclass is 8 bytes long, but cache_t? Let’s take a look at the internal structure

cache_t

Analysis to getcache_tIt’s 16, so plusisaandsuperclassIs the total32The length is one byte.

(lldb) x/4gx JPPerson.class
0x100008358: 0x0000000100008380 0x000000010036a140
0x100008368: 0x00000001003623c0 0x0000802800000000
(lldb) p/x 0x100008358+0x20
(long) $1 = 0x0000000100008378
(lldb) p (class_data_bits_t*)0x0000000100008378
(class_data_bits_t *) $2 = 0x0000000100008378
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101b06bc0
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000144
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
Copy the code

How embarrassing! Class_rw_t does not see the message we should see.

class_rw_t

Never wrong! Print information and inclass_rw_tThe source code inside the structure is the same ah? So why not? We just printed the structure information, the method inside is not found, we continue to look at the source, see if there is a way to print the property.Still really have method, you say coincidence coincidence! Hey heySo pretty boy, direct callproperties()Just do it!

properties

(lldb) p $3.properties()
(const property_array_t) $5 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008198
      }
      arrayAndFlag = 4295000472
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->properties()
(lldb) 
Copy the code

Call the properties() method to get property_array_t

property_array_t

class property_array_t : 
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    typedef list_array_tt<property_t, property_list_t, RawPtr> Super;

 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }
};
Copy the code

list_array_tt

list_array_tt<property_t, property_list_t, RawPtr>

Knowing the structure, we go down layer by layer

(lldb) p $3.properties()
(const property_array_t) $5 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008198
      }
      arrayAndFlag = 4295000472
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->properties()
(lldb) p $5.list
(const RawPtr<property_list_t>) $6 = {
  ptr = 0x0000000100008198
}
(lldb) p $6.ptr
(property_list_t *const) $7 = 0x0000000100008198
(lldb) p $7*
error: <user expression 9> :2:1: expected expression
;
^
(lldb) p *$7
(property_list_t) $8 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $8.get(0)
(property_t) $9 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $8.get(1)
(property_t) $10 = (name = "hobby", attributes = "T@\"NSString\",C,N,V_hobby")
(lldb) 
Copy the code

Who else? See, the JPPerson property has been printed out, and the information we want for the class has been successfully output! Oh, my damn charm!

methods()

With the properties printed above, it’s time to look at the methods

(lldb) p $3.methods()
(const method_array_t) $15 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008098
      }
      arrayAndFlag = 4295000216
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->methods()
(lldb) p $15.list.ptr
(method_list_t *const) $18 = 0x0000000100008098
(lldb) p *$18
(method_list_t) $19 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
(lldb) p $19.get(0)
(method_t) $20 = {}
(lldb) p $19.get(0)
(method_t) $21 = {}
(lldb) p $19.get(1)
(method_t) $22 = {}
Copy the code

what ?? What the hell? How can get print out? Don’t rush to explore further

method_t

Found in the source codemethod_tStructure, which has a nested structure insidebigStructure,bigThere is aSELThere areimp So you can go throughmethod_tStructural fetchbig, you can get to the inside of the method, source code also found accessbigmethodsbig()

big()

(lldb) p $19.get(0).big
(method_t::big) $23 = {
  name = "sayHello"
  types = 0x0000000100003f94 "v16@0:8"
  imp = 0x0000000100003cd0 (JPObjcBuild`-[JPPerson sayHello])
}
  Fix-it applied, fixed expression was: 
    $19.get(0).big()
(lldb) p $19.get(1).big
(method_t::big) $24 = {
  name = "hobby"
  types = 0x0000000100003f8c "@ @ 0:8 16"
  imp = 0x0000000100003d40 (JPObjcBuild`-[JPPerson hobby])
}
  Fix-it applied, fixed expression was: 
    $19.get(1).big()
(lldb) p $19.get(2).big
(method_t::big) $25 = {
  name = "setHobby:"
  types = 0x0000000100003f9c "v24@0:8@16"
  imp = 0x0000000100003d70 (JPObjcBuild`-[JPPerson setHobby:])
}
  Fix-it applied, fixed expression was: 
    $19.get(2).big()
(lldb) p $19.get(3).big
(method_t::big) $26 = {
  name = "init"
  types = 0x0000000100003f8c "@ @ 0:8 16"
  imp = 0x0000000100003c70 (JPObjcBuild`-[JPPerson init])
}
  Fix-it applied, fixed expression was: 
    $19.get(3).big()
(lldb) p $19.get(4).big
(method_t::big) $27 = {
  name = "name"
  types = 0x0000000100003f8c "@ @ 0:8 16"
  imp = 0x0000000100003ce0 (JPObjcBuild`-[JPPerson name])
}
  Fix-it applied, fixed expression was: 
    $19.get(4).big()
(lldb) p $19.get(5).big
(method_t::big) $28 = {
  name = "setName:"
  types = 0x0000000100003f9c "v24@0:8@16"
  imp = 0x0000000100003d10 (JPObjcBuild`-[JPPerson setName:])
}
  Fix-it applied, fixed expression was: 
    $19.get(5).big()
Copy the code

Well done! Ha ha 😁, method list inside count value is 6, a total of 6 are printed out, including setter method and getter method, beautiful boy will ask you to accept!

Class methods (+ methods) and member variables are printed out. What a big clove of garlic you’re packing here!

Good, refuse to accept it! So let’s explore further.

ivars

Properties and member variables are stored in different locations in Memory. WWDC2020 introduces Clean Memory and Dirty Memory

Clean Memory

Clean memory memory that doesn’t change when it’s loaded class_ro_t is clean memory because it’s read-only and it doesn’t, it doesn’t align with memory and clean memory can be removed to save more memory, Because if you need clean memory, the system can be reloaded from disk

Dirty Memory

Dirty Memory is a memory class structure that changes while the process is running and becomes dirty memory when used because new data is written to it at runtime. For example, creating a new method cache and pointing to it from a class, initializing the class-related subclasses and their parent class dirty memory is the main reason that class data is split into two parts

Dirty memory is much more expensive than clean memory, and it must exist for as long as the program runs. By separating out data that will not change, you can store most of the class data in Clean memory

Take a look at the image below (taken from the WWDC2020 video)

We know from the diagram that the member variable is inside class_ro_t, so let’s print it out

Since there is ivars member information in it, then print it out and have a look

(lldb) p $6.ivars
(const ivar_list_t *const) $7 = 0x0000000100008130
(lldb) p *$7
(const ivar_list_t) $8 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}
(lldb) p $7.get(0)
(ivar_t) $9 = {
  offset = 0x0000000100008340
  name = 0x0000000100003f2a "age"
  type = 0x0000000100003f7e "i"
  alignment_raw = 2
  size = 4
}
  Fix-it applied, fixed expression was: 
    $7->get(0)
(lldb) p $7.get(1)
(ivar_t) $10 = {
  offset = 0x0000000100008348
  name = 0x0000000100003f2e "_name"
  type = 0x0000000100003f80 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
  Fix-it applied, fixed expression was: 
    $7->get(1)
(lldb) p $7.get(2)
(ivar_t) $11 = {
  offset = 0x0000000100008350
  name = 0x0000000100003f34 "_hobby"
  type = 0x0000000100003f80 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
  Fix-it applied, fixed expression was: 
    $7->get(2)
(lldb) 
Copy the code

The data type, name, and memory size of the member information are printed out.

Class method

In the test above,JPPersonThe object method can be printed out normally and is inJPPerson.classTo get printed. In MachOView, you can also see that class methods do exist.

So is the class method, the plus method, in the metaclass? Isn’t it logical that object methods are in classes and class methods are in metaclasses? Okay, let’s explore and find out

(lldb) x/4gx JPPerson.class
0x100008358: 0x0000000100008380 0x000000010036a140
0x100008368: 0x00000001003623c0 0x0000802800000000
(lldb) p/x 0x0000000100008380 + 0x20
(long) $19 = 0x00000001000083a0
(lldb) p/x (class_data_bits_t*)0x00000001000083a0
(class_data_bits_t *) $20 = 0x00000001000083a0
(lldb) p $20->data()
(class_rw_t *) $21 = 0x0000000101337080
(lldb) p *$21
(class_rw_t) $22 = {
  flags = 2684878849
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4315117329
    }
  }
  firstSubclass = 0x00000001000083a8
  nextSiblingClass = 0x00007fff80111eb0
}
(lldb) p $22.methods()
(const method_array_t) $23 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008208
      }
      arrayAndFlag = 4295000584
    }
  }
}
(lldb) p $23.list.ptr
(method_list_t *const) $24 = 0x0000000100008208
(lldb) p *$24
(method_list_t) $25 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $25.get(0).big()
(method_t::big) $26 = {
  name = "sayNB"
  types = 0x0000000100003f94 "v16@0:8"
  imp = 0x0000000100003da0 (JPObjcBuild`+[JPPerson sayNB])
}
(lldb) 
Copy the code

Haha, who elseThis wave operation will ask you to accept!

conclusion

  • The metaclassisaTo:Metaclass ISA -> root metaclass ISA -> root metaclass (NSObject metaclass)
  • Metaclass inheritance:Class inherits ISA -> root metaclass ISA ->NSObject->nil
  • In the classIsa, Superclass, CHche, bitsMember variable,
  • bitsStores property lists, method lists, member variable lists, protocol lists, and so on
  • method_tIs the encapsulation of method functions

supplement

When we print methods, there is a types in the big structure of method_t. If you look at the underlying LLDB debugging or clang, you’ll often see something like “v16@0:8”, “v24@0:8@16”, which is Type Encodings. IOS provides a directive called @encode that can represent specific types as string encodings! For example “v16 @ 0:8”,

  • vsaidviod, no return value
  • 16Said the16bytes
  • @Said parametersid self
  • 0saidid from0On the start
  • :saidSEL, is the method number
  • 8saidSELStarting from the8No. A

Id is 8 bytes, sel is 8 bytes, that’s 16 bytes

  • Type EncodingsThe chart

  • Method_t summary

More content continues to be updated

🌹 Please move your little hands and click “like” 👍🌹

🌹 like can come a wave, collect + attention, comment + forward, so as not to find me next time, ha ha 😁🌹

🌹 welcome everyone to leave a message exchange, criticism and correction, learn from each other 😁, improve themselves 🌹