How to explore the nature of the object?
Because oc’s underlying implementation is c and C ++, Clang can restore OC to C or C ++ code, so you can see the C or C ++ underlying implementation of an object through Clang.
An extension of Clang. What is Clang?
Clang: A C language family frontend for LLVM. The Clang project provides a language front end and tool infrastructure for languages in the LLVM project’s C family of languages (C, C++, Objective C/C++, OpenCL, CUDA, and RenderScript). Gcc-compliant and MSVC-compliant compiler drivers are provided.
Clang compiles oc files
Simulator CPP file generation command:
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
Copy the code
The main-arm64.cpp file is generated:
I declared and implemented the NNPerson class in main.m, and the corresponding main. CPP file code is as follows:
You can see that NNPerson generates a structure:
struct NNPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _myAge;
};
Copy the code
There is an NSObject_IMPL and a myAge that I defined in oc. To take a closer look at what NSObject_IMPL is, a global search shows the following code block:
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
typedef struct objc_object *id;
typedef struct classref *classref_t;
Copy the code
NSObject_IMPL (ISA) is an objC_class pointer. Here we also see the definition of id, which is an objc_object pointer, so when we declare an object with id, we just declare the id instead of the asterisk.
If you look further at objc_class, you can see that objc_class is defined 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; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
Copy the code
Objc_class inherits from objc_Object. If we click on objc_Object, we can see the structure of objc_class:
#if ! OBJC_TYPES_DEFINED /// An opaque type that represents a method in a class definition. typedef struct objc_method *Method; /// An opaque type that represents an instance variable. typedef struct objc_ivar *Ivar; /// An opaque type that represents a category. typedef struct objc_category *Category; /// An opaque type that represents an Objective-C declared property. typedef struct objc_property *objc_property_t; struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY; #if ! __OBJC2__ Class _Nullable super_class OBJC2_UNAVAILABLE; const char * _Nonnull name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE; /* Use `Class` instead of `struct objc_class *` */ #endifCopy the code
See this, but this code is deprecated in the current OC version, with the macro definition #if! OBJC_TYPES_DEFINED and then look at the OBJC_TYPES_DEFINED and find the following definitions:
#ifdef _OBJC_OBJC_H_
#error include objc-private.h before other headers
#endif
#define OBJC_TYPES_DEFINED 1
#undef OBJC_OLD_DISPATCH_PROTOTYPES
#define OBJC_OLD_DISPATCH_PROTOTYPES 0
Copy the code
It’s always going to be 1, and if you take non it’s going to be 0 you’re not going to go there.
Objc_class (objc_object); objc_class (objc_object);
struct objc_object { private: isa_t isa; // the following definitions are omitted here.Copy the code
Isa is of the isa_T type, and ISA_t is defined as follows:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
Copy the code
Isa_t isa union, so here’s the difference between a structure and a Commons.
Structure and Commons
Structure:
A Struct is a collection of variables or arrays of the same or different types. Each such variable or array is called a Member of the structure. Define the format as follows:
Struct struct name {struct containing variables or arrays};Copy the code
The appropriate:
A Union, “Union,” is sometimes called a Union or Union. Define the format as follows:
Union Common body name {member list};Copy the code
Difference between structure and Commons:
The memory used by the structure is greater than or equal to the sum of the memory used by all the members (there may be gaps between the members), and the memory used by the common is equal to the memory used by the longest member. The pool uses a memory override technique, in which only one member’s value can be held at a time. If a new member is assigned a value, the value of the original member will be overwritten.
An example of a common body 🌰:
union NNTestUnion{ int a; char b; double c; }testUnion; NSLog(@" occupy size is :%lu",sizeof(testUnion)); testUnion.a = 5; NSLog(@"a=%d,b=%c,c=%f",testUnion.a,testUnion.b,testUnion.c); testUnion.b = 'a'; NSLog(@"a=%d,b=%c,c=%f",testUnion.a,testUnion.b,testUnion.c); TestUnion. C = 8.0; NSLog(@"a=%d,b=%c,c=%f",testUnion.a,testUnion.b,testUnion.c);Copy the code
Print the following:
As you can see from the above results, only one member can be saved at a time. Members are mutually exclusive
If the union membership definition is changed to the following:
union NNTestUnion{
int a;
char b;
float c;
}testUnion;
Copy the code
2021-06-18 17:23:57.350435+0800 KCObjcBuild[48834:10108220] is the same as the memory used by the longest member.
A domain
Bit-fields: Some data does not need to occupy a complete byte, but only one or more binary bits. For example, the switch has only two states: power and power. It is sufficient to use 0 and 1, that is, a binary digit. It is with this in mind that C provides another data structure called bitfields.
When defining a structure, we can specify the number of bits (bits) that a member variable occupies, which is called the bitfield.
To take an example of a bit field 🌰:
struct direction{ bool up:1; bool down:1; bool left:1; bool right:1; }direction1; struct Direction{ bool up; bool down; bool left; bool right; }Direction1; NSLog(@"direction1 size is :%lu",sizeof(direction1)); NSLog(@"Direction1 size is :%lu",sizeof(Direction1));Copy the code
The print result is as follows:
You can see the result very clearly, and you save a lot of memory after you specify it
####ISA_BITFIELD there is an ISA_BITFIELD in union ISA_t. Click here to see the definition of this:
typedef unsigned long uintptr_t;
# if __arm64__
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# elif __x86_64__
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
Copy the code
You can see that ISA_BITFIELD isa macro definition, so in that ISA_t:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
Copy the code
Can be replaced with:
typedef unsigned long uintptr_t;
#if defined(ISA_BITFIELD)
struct {
// defined in isa.h
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t unused : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
};
Copy the code
The definition of ISA, as you can see, has a bit field defined in it. 1+1+1+33+6+1+1+1+1 +19 = 64 (x86_64)
- Nonpointer: indicates whether pointer optimization is enabled for ISA. 0 indicates a pure ISA pointer, and 1 indicates that in addition to the address, it contains some information about the class, the reference count of the object, and so on.
- Has_assoc: associated object flag bit.
- Has_cxx_dtor: does this object have a destructor for C++ or Objc? If it does, it needs to do some logical processing for the destructor. If it does not, it can release the object faster.
- Shiftcls: the value of the class pointer exists. With pointer optimization enabled, 33 bits of the ARM64 bit are used to store the class pointer.
- Magic: Determines whether the current object is a real object or an uninitialized space.
- Weakly_referenced: Weak variable that is pointed to or has been pointed to an ARC, objects without weak references are released faster.
- Unused: Indicates whether the flag has not been used.
- Has_sidetable_rc: Carry is required when the object reference count is greater than 10.
- Extra_rc: Represents the reference count value of this object, which is actually the reference count minus one. For example: if the reference count is 10, extra_rc is 9. If the reference count is greater than 10, has_sideTABLE_rc is used.
ε=(´ο ‘))))*
How are classes and ISA bound?
In the _class_createInstanceFromZone method, three things are done: one is to calculate the required space, one is to create memory space, and the other is to initialize isa and bind related classes. The method is initIsa, so let’s see what’s going on in this method.
inline void objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor) { ASSERT(! isTaggedPointer()); isa_t newisa(0); if (! nonpointer) { newisa.setClass(cls, this); } else { ASSERT(! DisableNonpointerIsa); ASSERT(! cls->instancesRequireRawIsa()); #if SUPPORT_INDEXED_ISA ASSERT(cls->classArrayIndex() > 0); newisa.bits = ISA_INDEX_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.indexcls = (uintptr_t)cls->classArrayIndex(); #else newisa.bits = ISA_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE # if ISA_HAS_CXX_DTOR_BIT newisa.has_cxx_dtor = hasCxxDtor; # endif newisa.setClass(cls, this); #endif newisa.extra_rc = 1; } // This write must be performed in a single store in some cases // (for example when realizing a class because other threads // may simultaneously try to use the class). // fixme use atomics here to guarantee single-store and to // guarantee memory order w.r.t. the class index table // ... but not too atomic because we don't want to hurt instantiation isa = newisa; }Copy the code
Shiftcls is used to store the value of the class pointer, and magic is used to determine whether the class has been initialized. As you can see from the above code, it initializes an ISA_t and performs subsequent initialization operations on different branches based on nonpointer, i.e. whether or not the ISA pointer is pure. This is a pure pointer flow:
Then we come to the setClass method:
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
// Match the conditional in isa.h.
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE
// No signing, just use the raw pointer.
uintptr_t signedCls = (uintptr_t)newCls;
# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT
// We're only signing Swift classes. Non-Swift classes just use
// the raw pointer
uintptr_t signedCls = (uintptr_t)newCls;
if (newCls->isSwiftStable())
signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));
# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
// We're signing everything
uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));
# else
# error Unknown isa signing mode.
# endif
shiftcls_and_sig = signedCls >> 3;
#elif SUPPORT_INDEXED_ISA
// Indexed isa only uses this method to set a raw pointer class.
// Setting an indexed class is handled separately.
cls = newCls;
#else // Nonpointer isa, no ptrauth
shiftcls = (uintptr_t)newCls >> 3;
#endif
}
Copy the code
I’m going to go straight into the setClass method
shiftcls = (uintptr_t)newCls >> 3;
Copy the code
Shiftcls is the value of newCls shifted three places to the right. I looked at the data and said this is for memory alignment, Pointers are 8 bytes so it’s 8 bytes aligned. At this point, I print the memory address before and after the right shift as follows:
Go over the break point and get to this place:Print newisa as:
According to the displayed address, CLS in ISA has been associated with the class, and Shiftcls has saved the relevant class information.
(although I am still a little confused, there is a new understanding will update, too difficult ðŸ˜)