preface

In the process of creating the instance object of the class, through alloc analysis, we know that the instance object stores the address of the corresponding class through ISA (UNOin), so as to associate the instance object with the class. So, what exactly is class? So let’s explore.

Preliminary knowledge

Oc language is a superset of C/C ++, in order to facilitate the study of the underlying structure, we convert OC source code into C ++ code, which requires the use of LLVM oc language front-end compiler Clang.

1. Clang

clang -rewrite-objc -main.m -o main.cpp
Copy the code

This command compiles the object file into a c++ file for us to explore the underlying code.

2. If the target file references the FRAMEWORK such as UIKit, you need to specify the platform, system version, and SDK

Clang-rewrite-objc-fobjc-arc-fobjc-runtinme = ios-13.2.0-isysroot / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator13.2. The SDK main.m -o main.cppCopy the code

The above instructions specify arc, system version, simulator SDK, and so on

3.xcrun clang

Xcode installs the xcrun command, which wraps clang a bit and makes it easier to write. Recommended. True machine:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_arm_64.cpp
Copy the code

The simulator:

xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m -o main_x86_64.cpp
Copy the code

I. The existence of ISA

1. Explore source code

@interface LGPerson : NSObject

@property(nonatomic, strong) NSString *name;

@end

@implementation LGPerson

@end
Copy the code

Define a LGPerson class that inherits from NSObject, converted to c++ code using clang for the sake of exploring the underlying structure

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc LGPerson.m -o LGPerson_arm64.cpp
Copy the code

Analyze lgPerson_arm64.cpp and search LGPerson to find the following code block:

#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif

extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString * _Nonnull _name;
};

Copy the code

It mainly consists of two parts:

  1. defineLGPersonclass
typedef struct objc_object LGPerson;
Copy the code
  1. Defining structureLGPerson_IMPL:
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString * _Nonnull _name;
};
Copy the code

1. Underlying instance object: ISA points to objc_class

The second part code:

extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString * _Nonnull _name;
};
Copy the code

Is the underlying structure of the LGPerson instance object: a structure containing the data member NSObject_IVARS, _name.

  • _nameIs the instance variable corresponding to the property and is used to store the property data.
  • NSObject_IVARS is a struct, find its definition:
struct NSObject_IMPL {
	Class isa;
};
Copy the code

So this is the structure of an instance object of NSObject.

typedef struct objc_class *Class;
Copy the code
  • Isa isa structure pointer to objc_class
  • All instance objects have an ISA that points to objc_class.
  • According to alloc source code analysis, under 64-bit architecture, ISA usually corresponds to an optimized nonpointer instead of a pure pointer.

2. Objc_class: inherits objc_Object

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

3. Objc_object: only ISA

This brings us back to the familiar objc_object:

struct objc_object { private: isa_t isa; . }Copy the code

4. To summarize

  1. Objc_object, the cornerstone of all, isa structure with isa of type isa_t
  2. At the bottom of the class is the objC_class structure, which inherits from ObjC_Object and also has ISA
  3. The underlying instance object is a structure, and according to inheritance rules, the final source comes from the corresponding structure of NSObject: there is only one member:Class isa, pointing to objc_class, so all instance objects still have an ISA
  4. Using objc_Object as the template, everything in oc is an object

The directivity of ISA

Source:

LGPerson *person = [[LGPerson alloc] init];
person.name = @"zxdix";
Copy the code

1. Instance object ISA points to Class

The LLDB directive is used to debug the isa pointer to the instance object person:

2. Class ISA points to metaClass

Continue to look at LGPerson’s memory:The isa pointer is still a LGPerson with a different memory address, called metaClass

3. Isa points to metaclass: the rootMetaClass rootMetaClass, whose isa points to itself

Continue exploring ISA:It turns out that the metaclass’S ISA points to an NSObject, and that NSObject’s ISA points to itself. Then opportunity goes into an infinite loop and nothing new happens.

So what is this NSObject? Is it the root class we used to call NSObject(Nsobject.class)?NSObject is not the root class (nsobject. class), but the ISA of the root class (nsobject. class), called the root metaclass

4. Isa goes to summary: Instance object -> Class object -> metaclass object -> root metaclass object

The root metaclass is also a metaclass, so it points to itself

With a classic illustration: (PS: too lazy, stole a hey hey)

3, Superclass direction

In the objc_class definition, there is a data member superclass:

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

As the name indicates, the object should be referred to as the parent class. We verify this by viewing the memory (or studying the code used in isa) :Superclass does point to a superclass. Verify the superclass of the metaClass and the superclass of the metaClass. The following inheritance diagram can be obtained:There are two points to note:

  1. Superclass of NSObject(class) is nil
  2. The superclass of NSObject is NSObject, which means that both the class and the metaClass trace down the inheritance tree and end up nil.


4. Preliminary study on the structure of class

Objc_class has the following source code:

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

Analyze the memory structure of the class object (arm64 architecture) : at first glance, there are four members

  1. Inherits from objc_Object, so the first variable is ISA, which is 8 bytes
  2. Superclass, pointer, 8 bytes
  3. Cache, unfamiliar cache_t structure, unknown
  4. Bits, unknown

1. Cache_t analysis

Source code is very complex at first glance, there is a very certain unknown definition, but look at the structure, divided into three parts:

  1. Static variables do not exist in the structure, ignore
  2. Part 1, #if… #elif… #elif.. #else… The #endif structure is essentially two variables:_buckets,mask.explicit_atomicIt’s a generic definition that essentially storesstruct bucket_t *Pointer and MASK_T (uint32). The other if branches are the same, of the same length.

3. Part 2, 2 bytes:typedef unsigned short uint16_t(LP64 holds: long, pointer are both 64-bit); 4. Part 3, also 2 bytes; So cache_t size is: 8 + 4 + 2 + 2 = 16 bytes

2. The memory structure of the class

At this point, we know that objc_class’s memory distribution looks like this:If the address offset of the BITS member is 32, the address of the bits member is offset by 32 bytes by the first address of the class.

3. Obtain class_rw_t

Using address offset, we can obtain the address of bits to read class_rw_t:

4. The methods and properties

There are several important methods in class_rw_t :methods, properties. We can print out the content.

  1. Methods: Contains information about instance methods.
(lldb) p/x LGPerson.class (Class) $1 = 0x0000000100002188 LGPerson (lldb) p/x (class_data_bits_t*)(0x00000001000021a8) Offset 32 bytes (class_datA_bits_t *) $2 = 0x00000001000021A8 (LLDB) p *$2 (class_data_bits_t) $3 = (bits = 4320411204) (LLDB)  p $3.data() (class_rw_t *) $4 = 0x0000000101843e40 (lldb) p *$4 (class_rw_t) $5 = { flags = 2148007936 witness = 1 ro_or_rw_ext = { std::__1::atomic<unsigned long> = 4294975592 } firstSubclass = nil nextSiblingClass = NSUUID } (lldb) p  $5.methods() (const method_array_t) $6 = { list_array_tt<method_t, method_list_t> = { = { list = 0x00000001000020b0 arrayAndFlag = 4294975664 } } } (lldb) p $6.list (method_list_t *const)  $7 = 0x00000001000020b0 (lldb) p *$7 (method_list_t) $8 = { entsize_list_tt<method_t, method_list_t, 3> = { entsizeAndFlags = 26 count = 3 first = { name = ".cxx_destruct" types = 0x0000000100000f8d "v16@0:8" imp = 0x0000000100000e60 (KCObjc`-[LGPerson .cxx_destruct]) } } } (lldb) p $8.get(1) (method_t) $10 = { name = "name" types = 0x0000000100000f95 "@16@0:8" imp = 0x0000000100000e90 (KCObjc`-[LGPerson name]) } (lldb) p $8.get(2) (method_t) $11 = { name = "setName:" types = 0x0000000100000f9d "v24@0:8@16" imp = 0x0000000100000eb0 (KCObjc`-[LGPerson setName:]) }Copy the code
  1. Properties: Stores property-related information
(lldb) p $5.properties()
(const property_array_t) $12 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x0000000100002128
      arrayAndFlag = 4294975784
    }
  }
}
(lldb) p $12.list
(property_list_t *const) $14 = 0x0000000100002128
(lldb) p *($14)
(property_list_t) $15 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
  }
}
Copy the code