The object nature

@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation LGPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!");
    }
    return 0;
}
Copy the code

The LGPerson class in main. CPP is compiled to LGPerson_IMPL:

//main.cpp
The LGPerson class is compiled to LGPerson_IMPL
struct LGPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;// equivalent to Class isa;
    NSString *_name;
};

// The underlying NSObject is compiled to NSObject_IMPL
struct NSObject_IMPL {
    Class isa;
};

The definition of / / NSObject
@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}
Copy the code
  • LGPersonClass is compiled toLGPerson_IMPLThe first propertyNSObject_IVARSEquivalent toClass isaIs defined as the parent structure;
    • Structs can be inherited in c++, but incCan be implemented in this wayPseudo inheritance. meansLGPersonhaveNSObjectIn theAll member variables.
    • An OC object is essentially a structure.

whyisaIs of typeClass?

  • InitInstanceIsa (int, int, int, int, int, int, int, int, int, int, int, int, int);

  • In NSObject, ISA is defined as Class. The fundamental reason is that ISA sends back Class information. In order to make developers more clear, isa needs to perform a type cast when it returns, similar to the strong cast of AS in Swift. Isa’s strong transformation in the source code is as follows:

The get and set methods of name

// Get method of name
static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

// Set method of name
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) {
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0.1);
}
Copy the code

setThe implementation of a method depends on the runtimeobjc_setProperty

objc_setPropertyThe source code is as follows:Enter thereallySetPropertySource code implementation, the principle of its method isNew value retain, old value release

forobjc_setPropertyThe underlying source code exploration summary

  • The purpose of the objc_setProperty method is to associate the upper set method with the lower set method, which is essentially an interface. Interface isolation objc_setProperty ADAPTS to a unified entry, and CMD differentiates between sets.

  • The reason for this design is that there are many upper set methods. If you call the lower set method directly, there will be a lot of temporary variables. When you want to find an SEL, it will be very troublesome

  • Based on the above reasons, apple has adopted the adapter design pattern (the underlying interface adapter for the client required interfaces), provides an interface that is for the use of the upper set method, internal calls at the bottom of the set method, make its are not affected each other, so no matter how the upper and the lower is constant, change can affect the upper or lower level, It is mainly used to isolate the upper and lower interfaces.

How CLS relates to classes

The first two of the three core methods in Alloc are analyzed in the source code analysis of object principles – Alloc and Init. Today we explore how initInstanceIsa associates CLS with ISA.

Before we do that, we need to know what isa union and why is isa’s type isa_t defined as a union

Union

The union is 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. Its memory footprint is equal to that of the largest member.

The type of ISA is ISA_t

union isa_t { / / a consortium
    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

The reason for the use of union for the ISA_t type is also based on memory optimization, which is implemented in the ISA pointer through the principle of char + bitfield (that is, each bit in binary can represent different information). Typically, isa Pointers take up 8 bytes of memory, or 64 bits, which is enough to store a lot of information and can result in significant memory savings to improve performance. It can be seen from the definition of ISA_t:

  • It provides two members, CLS and bits, which are mutually exclusive, as defined by the union, meaning that there are two ways to initialize isa Pointers

    • throughclsInitialization,Bits No default value
    • throughbitsInitialization,CLS has default values
  • Also provided is a structure defined byA domain, used to store class information and other information, members of a structureISA_BITFIELDThis is aThe macroDefinition, there are two versions__arm64__(on ios mobile) and__x86_64__(for macOS), here are some of their macro definitions, as shown below:

    • nonpointerThere are two values, representing custom classes, etc., from the first0Bit start, occupy1position
      • 0:Pure isa pointer
      • 1: not onlyClass object addressIsa containsThe class informationAnd the object’sReference countingEtc.
    • has_assocsaidAssociated object flag, from the first1Bit start, occupy1position
      • 0:There is no relationshipobject
      • 1:A link betweenobject
    • has_cxx_dtorIndicates whether the object has C++/OCDestructor machine(similar to thedealloc),1position
      • ifThere areDestructor, is requiredDo the destructorlogic
      • ifThere is no, can be fasterThe release ofobject
    • shiftclssaidStore the value of a pointer to a class(The address of the class), that is, the class information
      • arm64A majority of the33Bit to turn on pointer optimization in case of arm64 architecture33Bits are used to store class Pointers
      • x86_64A majority of the44position
    • magicUsed by the debugger to determine whether the current object isThe real objectorThere is no space for initialization, accounting for6position
    • weakly_refrencedRefers to the objectWhether or not it is directedorWeak variable that used to point to an ARC
      • Objects without weak references can be released faster
    • deallocatingThe flag object isIs it being released?memory
    • has_sidetable_rcRepresents when an objectThe reference count is greater than 10, is neededBorrow the variable to store the carry
    • extra_rc(Additional reference count), indicating thatThe reference to the object counts the value, is actually the reference count value minus 1
      • If the object’sThe reference count is 10, thenextra_rc9 (this is for example only), which is actually true for the iPhoneextra_rcIt uses 19 bits to store reference counts

For two different platforms, theisaAs shown in the figure:

The source code to explore

  • throughalloc --> _objc_rootAlloc --> callAlloc --> _objc_rootAllocWithZone --> _class_createInstanceFromZoneMethod path, found toinitInstanceIsaAnd into its principle of implementation
inline void 
objc_object: :initInstanceIsa(Class cls, bool hasCxxDtor){ ASSERT(! cls->instancesRequireRawIsa()); ASSERT(hasCxxDtor == cls->hasCxxDtor());// Initialize isa
    initIsa(cls, true, hasCxxDtor); 
}
Copy the code
  • Enter the source implementation of the initIsa method, primarily to initialize the ISA pointer

    The logic of this method is divided into two parts:

    • throughclsInitialize theisa
    • throughbitsInitialize theisa
Verifying isa pointer bit fields (0-64)

From the 0-64 bit fields mentioned above, you can prove here that isa pointer has these bit fields in the initIsa method (currently on macOS, so x86_64 is used).

  • Init with the LGPerson breakpoint in main –> initInstanceIsa –> initIsa –> go to ISA in else

  • Run the LLDB command p newisa to obtain details about newisa

  • To continue, go to newisa.bits = ISA_MAGIC_VALUE; On the next line, assign a value to the bits member of ISA and re-execute the LLDB command p newisa. The result is as follows

  • By comparing the information with the previous newISA, there are some changes in the ISA pointer, as shown in the figure below

  • Magic is 59 because the ISA pointer address is converted into binary, 6 bits are read from 47 (because there are 4 bit fields in front, 47 bits are occupied, and the address starts from 0), and then converted into decimal, as shown in the figure below

Isa associates Pointers and classes

The principle of CLS association with ISA is that the shiftCls bit field in ISA pointer stores the class information, where initInstanceIsa process is to associate the Calloc pointer with the current CLS class. There are the following verification methods:

  • Shiftcls = (uintptr_t) CLS >> 3; validation

  • [Method 2] Verify the isa pointer address and the value of ISA_MSAK &

  • [Method 3] Verify this by using the object_getClass runtime method

  • [Mode 4] Verify by bit operation

Method 1: Use the initIsa method

Uintptr_t CLS >> 3; Previous step, where Shiftcls stores the value information for the current class

  • When you look at CLS, it’s the LGPerson class

  • The logic of shiftcls assignment is to shift LGPerson three bits to the right after encoding it

  • Execute the LLDB command p (uintptr_t) CLS, the result is (uintptr_t) $2 = 4294975720, move it three places right in either of the following ways to get 536871965 and store it in shiftcls of Newisa

    • p (uintptr_t)cls >> 3
    • Through the results of the previous step$2Run the LLDB commandp $2 >> 3

  • Continue the program to isa = newisa; P newisa is executed

There are two changes in the bit field of bits compared to the result of the bits assignment

  • cls 由The default valueAnd turned intoLGPersonThat will beIsa and CLS are perfectly correlated
  • shiftclsby0Turned out to be536871965

soisaThrough theInitialize theAfter theMembers of thetheProcess of value change, as shown in the figure below: Why do I need a strong type when SHIFtcls is assigned?

Since the memory store cannot store strings, machine code can only recognize 0 and 1, so it needs to convert them to uintptr_t data type so that the class information stored in ShiftCLs can be understood by machine code, where Uintptr_t is unsigned long

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.

Method 2: Use ISA & ISA_MSAK

  • After method 1, go back to _class_createInstanceFromZone. At this point, CLS is associated with ISA. Execute Po objc

  • Execute x/4gx obj to obtain the address of isa pointer 0x001D8001000020e9

  • Isa pointer address & ISA_MASK (in macOS, defined using macros in X86_64), Po 0x001D8001000020e9&0x00007ffffffFF8ull, resulting in LGPerson

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

Method 3: Use object_getClass

You can also verify isa’s association with a class by viewing object_getClass’s source code implementation. There are several steps:

  • #import
  • throughruntimeAPI, namelyobject_getClassFunction to get class information.
object_getClass(<#id  _Nullable obj#>)
Copy the code
  • View the object_getClass function source code implementation

  • Click on the underlying implementation of object_getClass

  • Enter getIsa source code implementation

  • Click ISA() to access the source code, and you can see that if it’s indexed, the if flow is executed, and else flow is executed instead

    • inelseIn the process, getisathebitsThis place, and then& ISA_MASK, which is consistent with the principle in mode two,Get the current class information
    • You can do it from hereCLS and ISA have been perfectly correlated

Mode 4: Through bit operation

  • Back to the _class_createInstanceFromZone method. X /4gx obj is used to get the obj storage information. The current class information is stored in isa pointer, and shiftcls in ISA is 44 bits (because of macOS environment).

  • If you want to read the 44-bit information in the middle, you need to go through the bit operation, erase the three bits on the right and the part on the left except the 44-bit, and its relative position is unchanged. Its bit operation process is shown in the figure, where Shiftcls is the class information to be read

    • Move the ISA address right by 3 bits: P /x 0x001D8001000020E9 >> 3 to get 0x0003B0002000041D

    • Shift the resulting 0x0003B0002000041D 20 bits to the left: p/x 0x0003B0002000041D << 20, resulting in 0x0002000041D00000

      • Why is itThe left 20Who? Because the firstrightMove the3Bit is the same thing as going to the rightIt's offset by 3And theOn the leftNeed to beMaLingThe figures are17Bit, so we have to move it20position
    • Move the resulting 0x0002000041D00000 17 bits right: p/x 0x0002000041D00000 >> 17 to get the new 0x00000001000020E8

  • P /x CLS also produces 0x00000001000020E8, so it can be proved that CLS is associated with ISA

The article lists

  • IOS Articles List

Thanks for the article reference

  • IOS – Underlying Principles 07: How ISA is associated with classes