preface

The main purpose of this article is to explore how ISA relates to the current class.

1. Clang compiler

  • ClangIs an Apple-led program based onLLVMC/C++/Objective-C compiler.
  • Mainly used for low-level compilation, output some files into c++ files, such as main.m output into main.cpp, its purpose is to better observe some of the underlying structure and implementation of the logic, easy to understand the underlying principle.
  • The common methods are as follows:
CPP clang -rewrite-objc main.m -o main. CPP //2, viewController.m to viewController.cpp clang -rewrite-objc-fobjc-arc-fobjc-Runtime =ios-13.0.0 -isysroot / / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator13.7. The SDK Viewcontroller.m // The following two ways are through the command line specifying the schema, Use the xcode tool xcrun //3, emulator file compilation -xcrun -sdk iphonesimulator clang -arch arm64-rewrite-objc main.m -o main-arm64.cpp Iphoneos clang -arch arm64 - rerewrite -objc main.m -o main-arm64.cppCopy the code

2. Explore the nature of the object

  • In main, define a class named Person with a name attribute
@interface Person : NSObject

@property (nonatomic.copy) NSString *name;
@end

@implementation Person
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}
Copy the code
  • Through the terminal, go to the project root directory, use Clang to compile main.m to main.cpp
  • Open the generated main.cpp file and search for the Person we created.
struct Person_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_name;
};
Copy the code
  • From the above source we can conclude that the object is compiled at the bottomstructStructure.
  • butNSObject_IMPLWhat is it? So he continued to search,NSObject_IMPL.
struct NSObject_IMPL {
	Class isa;
};
Copy the code

conclusion

Through the above exploration, we can draw the conclusion that:

  • 1. An object is essentially a structure.
  • 2.NSObject_IVARSNature isIsa of class type

3. Explore the underlying principle of setter method

Since we have a name attribute in the Person class, we see the get and set methods for name in the underlying source code.

  • name Getter methodUnderlying source code:
static NSString * _I_Person_name(Person * self, SEL _cmd) { return(* (NSString((* *)char *)self + OBJC_IVAR_$_Person$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long.id.bool.bool);
Copy the code
  • name A setter methodUnderlying source code:
static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _name), (id)name, 0.1); }
Copy the code
  • Open the downloadable objC source code and search globallyobjc_setPropertyWe can findobjc_setPropertySource code implementation
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    bool copy= (shouldCopy && shouldCopy ! = MUTABLE_COPY);bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
Copy the code
  • Enter thereallySetPropertyMethod, view the source code implementation, the underlying implementation pairThe release of the old values.Retain for the new value.
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy.bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id((*)char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        // Retain the new value
        newValue = objc_retain(newValue);
    }

    if(! atomic) { oldValue = *slot; *slot = newValue; }else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }
    // Release of the old value
    objc_release(oldValue);
}
Copy the code

conclusion

After exploring the underlying source code of objc_setProperty, the following points emerge:

  • 1.objc_setPropertyA method is essentially an interface for associating upper-levelsetterMethods and the underlyingsetterMethods.
  • 2. If there are many upper setter methods, if you call the lower setter method directly, there will be many temporary variables, when you want to find a SEL, difficult to deal with.
  • 3, provides an interface to the upper setter method, throughSEL _cmdTo achieveThe upper and lower interfaces are isolatedThe purpose of.

4. Structure & Commonwealth

1. The structure

Structure refers to the combination of different data into a whole, its variables are coexisting, regardless of whether the variables are used, will allocate memory.

  • Advantages:Large storage capacity.receptiveAnd members do not influence each other
  • Disadvantages: struct memory space allocation is extensive, regardless of use, full allocation.

2. The commonwealth of

Union is also composed of different data types, but its variables are mutually exclusive, and all the members occupy a segment of memory. Moreover, the common body adopts the memory overwrite technology, which can only store the value of one member at a time. If a value is assigned to a new member, the value of the original member will be overwritten.

  • Advantages: More precise and flexible memory usage, but also save memory space.
  • Cons: Not “inclusive” enough.

5. Isa Exploration

In iOS basic principles 01: Alloc&init &new source code analysis article, we know the three core operations of alloc:

  • Calculate the size of memory to be opened:size = cls->instanceSize(extraBytes)
  • Applying for memory:obj = (id)calloc(1, size);
  • Bind the current class and pointer address together:obj->initInstanceIsa(cls, hasCxxDtor);

Now go to obj->initInstanceIsa(CLS, hasCxxDtor) source code.

inline void 
objc_object::initInstanceIsa(Class cls, boolhasCxxDtor) { ASSERT(! cls->instancesRequireRawIsa()); ASSERT(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls,true, hasCxxDtor);
}
Copy the code

1, enter the source code of initIsa

inline void 
objc_object::initIsa(Class cls, bool nonpointer, boolhasCxxDtor) { ASSERT(! isTaggedPointer());if(! nonpointer) { isa = isa_t((uintptr_t)cls); }else{ ASSERT(! DisableNonpointerIsa); ASSERT(! cls->instancesRequireRawIsa()); isa_t newisa(0);
#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
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        // 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 instantiationisa = newisa; }}Copy the code

2. What is ISA

Enter the definition of ISA, ISA = ISA_t ((Uintptr_t) CLS), it can be seen from the source code that ISA is defined by the union.

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    // CLS and bits are mutually exclusive
    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
Copy the code
  • As you can see from the source, the union defines two CLS and bits members and an ISA_BITFIELD (which holds class information and other information).

    There are two ways to initialize an ISA pointer:

    • 1. ByclsInitialization: that isisa = isa_t((uintptr_t)cls).
    • 2. BybitsInitialization.

3. ISA_BITFIELD bit field

Here ISA_BITFIELD is a bit field, which has two versions, corresponding to __arm64__ and __x86_64__ respectively, i.e. iOS and macOS:

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   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 deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   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 deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

# else
#   error unknown architecture for packed isa
# endif

// SUPPORT_PACKED_ISA
#endif
Copy the code

(Take ARM64 architecture as an example)

  • nonpointer(stored in byte 0) : indicates whether it is correctIsa pointer Pointer optimization is enabled0: pure ISA pointer, 1: not only the address of the class object, ISA contains the class information, reference count of the object, and so on.
  • has_assoc(stored in the first byte) :Associated object flag bit0 does not exist, 1 exists.
  • has_cxx_dtor(stored in byte 2) : Whether the object hasC++ or Objc destructorIf there is a destructor, the destructor logic needs to be done. If there is no destructor, the object can be freed faster.
  • shiftcls(stored in bytes 3-35) :

Store the value of a class pointer. With pointer optimization turned on, 33 bits are used to store class Pointers in the ARM64 architecture.

  • magic(stored in bytes 36-41) : Used by the debugger to determine whether the current object is a real object or notInitialized space.
  • weakly_referenced(stored in byte 42) : The object is pointed to or used to point to an ARC weak variable, and objects without weak references can be released faster.
  • deallocating(stored in byte 43) : indicates whether the object is onFree memory.
  • has_sidetable_rc(stored in byte 44) : When the object reference technique is greater than 10, this variable is borrowed to store the carry.
  • extra_rc(stored in bytes 45-63) : When representing the reference count value of this object, the reference count value is actually subtracted by 1. For example, if the object’s reference count is 10, extra_rc is 9. If the reference count is greater than 10, the following has_sideTABLE_rc is used.

Isa storage distribution is as follows:

4. Enter initIsa source code analysis

  • First through the Person breakpoint in main –>initInstanceIsa –> initIsa–> Go to elseisa_t newisa(0);
  • Run the LLDB command:p newisaTo print the information for newisa

  • Click on thestep overWe go down to newisa.bits = ISA_MAGIC_VALUE; Here areisathebitsThe member is assigned and the LLDB command is executed againp newisa, the print result is as follows:

  • A comparison of the printed results shows that nonpointer is assigned 1 and magic is assigned 59. So why is magic assigned 59?

  • Open the calculator and type 0x001D800000000001, read 6 bits from 47 bits, convert 59 bits to binary and find 111011, as shown below:

5, isa pointer and class association

  • The procedure for initInstanceIsa is to associate a pointer to calloc with the current CLS class.

  • The class information is stored in the Shiftcls bit field in the ISA pointer.

  • Associated process

  • Shiftcls = (uintptr_t) CLS >> 3

  • CLS is the Person class

  • 3,shiftclsThe assignment logic is to encode Person and move it three bits to the right
  • 4. Run the LLDB commandp (uintptr_t)cls, the results for(uintptr_t) $0 = 4294975864
  • 5,(uintptr_t)clsRun the LLDB commandp (uintptr_t)cls >> 3, get the result uintptr_t) $1 = 536871983
  • 6, continue to execute the code toisa = newisa; Part, run the LLDB commandp newisa

contrastbitsThe result of the assignment:

  • At this point, CLS is changed from the default to Person, perfectly associating ISA with CLS.

  • Shiftcls changed from 0 to 5536871983.

  • Why do I need to move 3 to the right?

This is mainly because Shiftcls is in the middle of the ISA pointer address, and there are three bit fields in front of it. In order not to affect the data in the first three bit fields, it needs to be zeroed by right shift.

  • Validation of isa pointer to class association

  • Method 1: Use isa & ISA_MSAK

  • Continue with the above steps and return to the _class_createInstanceFromZone method. At this point, the CLS and ISA have been associated. Execute the LLDB command Po objc

  • To print four segments of memory in hexadecimal format, run x/4gx obj to obtain the ISA address 0x001D8001000021C9

  • Add isa pointer address and ISA_MASK (in macOS, using macros defined in X86_64), Po 0x001D8001000021c9&0x00007ffffFFFF8, to get Person

    • arm64, the value defined by the ISA_MASK macro is0x0000000ffffffff8ULL
    • x86_64, the value defined by the ISA_MASK macro is0x00007ffffffffff8ULL

  • Why do you want and?

  • Because isa isa consortium in macOS x86_64, bits 3-35 are used to store the class information. Isa needs to perform a bit operation to calculate the real address of the stored class, which is equivalent to reading only bits 3-46.

  • Method 2: View the object_getClass source code

  • #import <objc/runtime.h>

  • Get the class information from the Runtime API, the object_getClass function

  • To viewobject_getClassThe source code
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}
Copy the code
  • To viewgetIsaThe source code
inline Class 
objc_object::getIsa() 
{
    if(fastpath(! isTaggedPointer()))return ISA();

    extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
    uintptr_t slot, ptr = (uintptr_t)this;
    Class cls;

    slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
    cls = objc_tag_classes[slot];
    if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
        slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        cls = objc_tag_ext_classes[slot];
    }
    return cls;
}
Copy the code
  • To viewISA()The source code

SUPPORT_INDEXED_ISAisindexedType,SUPPORT_INDEXED_ISA= 1, store the class in ISA with the fields as indexes to the class table.

#if__ARM_ARCH_7K__ >= 2 || (__arm64__ && ! __LP64__)
#   define SUPPORT_INDEXED_ISA 1
#else
#   define SUPPORT_INDEXED_ISA 0
#endif
Copy the code

The else process returns isa.bits & ISA_MASK, consistent with the validation above, to get information about the current class.

  • Method 3: By bit operation
  • Go back toclass_createInstanceFromZoneMethods. throughx/4gx objThe current class information is stored in the ISA pointer, and the shiftcls in ISA is now 44 bits (inmacOSEnvironment)
(lldb) x/4gx obj
0x102933830: 0x001d8001000021c9 0x0000000000000000
0x102933840: 0x67616d49534e5b2d 0x7765695674694b65
Copy the code
  • If you want to read the 44-bit information in the middle, you need to go through the bit operation, move 3 bits to the right, and erase the left part except the 44-bit, its relative position is unchanged.
  • Move ISA three places to the right, that is, zero the lower three places
(lldb) p/x 0x001d8001000021c9 >> 3
(long) $1 = 0x0003b00020000439
Copy the code
  • What I’m going to get0x0003b00020000439We moved 20 places to the left becauseshiftclsIn places 3 to 46, we need to erase the 17 higher digits. We have moved three to the right in the previous step, so we need to move 20 to the left to erase the 17 higher digits.
(lldb) p/x 0x0003b00020000439 << 20
(long) $2 = 0x0002000043900000
Copy the code
  • Shift the result 17 places to the right, back to the original position.
(lldb) p/x 0x0002000043900000 >> 17
(long) $3 = 0x00000001000021c8
Copy the code
  • Print the CLS information to verify that the CLS and ISA are perfectly related.
(lldb) p/x cls
(Class) $4 = 0x00000001000021c8 Person
Copy the code

By exploring the underlying principles of ISA, we have gained a new understanding of ISA and how isa Pointers relate to classes. The next article will continue to explore isa bitmaps.