Welcome to the iOS Exploration series.

  • IOS explores the alloc process
  • IOS explores memory alignment &malloc source code
  • IOS explores ISA initialization & pointing analysis
  • IOS exploration class structure analysis
  • IOS explores cache_T analysis
  • IOS explores the nature of methods and the method finding process
  • IOS explores dynamic method resolution and message forwarding mechanisms
  • IOS explores the dyLD loading process briefly
  • The loading process of iOS explore classes
  • IOS explores the loading process of classification and class extension
  • IOS explores isa interview question analysis
  • IOS Explore Runtime interview question analysis
  • IOS explores KVC principles and customization
  • IOS explores KVO principles and customizations
  • IOS explores the principle of multithreading
  • IOS explores multi-threaded GCD applications
  • IOS explores multithreaded GCD underlying analysis
  • IOS explores NSOperation for multithreading
  • IOS explores multi-threaded interview question analysis
  • IOS Explore the locks in iOS
  • IOS explores the full range of blocks to read

Writing in the front

Before introducing ISA, let’s introduce bitfields and structures

First, a domain

Definition 1.

Some information does not need to occupy a complete byte, but only a few or a binary bit. For example, when storing a switch quantity, there are only 0 and 1 states, and 1 bit binary can be used. To save storage space and simplify processing, C provides a data structure called a bit field or bit segment

The so-called bit-field is to divide the binary bits in a byte into several different regions and specify the number of bits in each region. Each domain has a domain name, which allows you to operate by domain name in your program — so that several different objects can be represented in a one-byte binary field

2. Comparison with structures

Bit-fields are used similar to structures and are themselves a type of structure

/ / structure
struct FXStruct {
    // (type specifier element);
    char a;
    int b;
} FXStr;

/ / a domain
struct FXBitArea {
    // (type specifier bit domain name: bit field length);
    char a: 1;
    int b: 3;
} FXBit;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Struct: % lu - BitArea: % lu".sizeof(FXStr), sizeof(FXBit));
    }
    return 0;
}
Copy the code

Output Struct: 8 — BitArea: 4

For those interested in bit fields, you can look at the definition of bit fields in structs

Ii. Consortium

Definition 1.

When multiple data needs to share memory or multiple data needs to be fetched only for a moment at a time, you can use a union.

  • A union is a structure
  • All its members have an offset of 0 relative to the base address
  • The structure should be large enough to accommodate the widest members
  • Variables are “mutually exclusive” — they share a common memory header. Joint variables can be assigned any member value, but only one value can be assigned at a time. New values flush out old values

2. Comparison with structures

Each member of the structure is stored sequentially, and the offset address of all members of the union is 0, that is, all members are stacked on top of each other, so only one member of the union is valid at any one time — the size of the structure depends on all the elements, and the union depends on the largest one

Isa structure/process analysis

1. Isa initialization

Obj ->initInstanceIsa(CLS, hasCxxDtor) — initIsa with initIsa(CLS, true, hasCxxDtor) Isa is not explained in detail

2. InitIsa analysis

inline void 
objc_object::initIsa(Class cls, bool nonpointer, boolhasCxxDtor) { assert(! isTaggedPointer());if(! nonpointer) { isa.cls = 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

Nonpointer is true

SUPPORT_INDEXED_ISA: isis_t: isis_t: isis_t: isis_t: isis_t: isis_t: isis_T: isis_T: isis_T: isis_T

③isa_t newisa(0) is equivalent to initializing isa, new. Equivalent to assigning an attribute to ISA

SUPPORT_INDEXED_ISA applies to WatchOS. Isa as a consortium is mutually exclusive. CLS and bits are elements of ISA. If nonpointer=true (CLS); if false (CLS), int (bits);

3. The structure of isa

union isa_t {
    isa_t() {}isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
Copy the code

First, isa isa union and has two initialization methods

(2) there isa Class CLS in isa, which is bound to isa

③isa uses the form of union + bitfield to optimize memory (ISA_BITFIELD isa bitfield macro definition)

The length of the union is determined by initializing bits, and then assigning the bit field ISA_BITFIELD in the union body

All attributes of the consortium share the same memory, and the memory length is equal to the length of its longest member, so that the code can store data efficiently and have strong readability. Bit-fields can hold many more types

4. ISA_BITFIELD macro definition

#if SUPPORT_PACKED_ISA

    // extra_rc must be the MSB-most field (so it matches carry/overflow flags)
    // nonpointer must be the LSB (fixme or get rid of it)
    // shiftcls must occupy the same bits that a real class pointer would
    // bits + RC_ONE is equivalent to extra_rc + 1
    // RC_HALF is the high bit of extra_rc (i.e. half of its range)

    // future expansion:
    // uintptr_t fast_rr : 1; // no r/r overrides
    // uintptr_t lock : 2; // lock for atomic property, @synch
    // uintptr_t extraBytes : 1; // allocated with extra bytes

# 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

Isa takes up 8 bytes to 64 bits in different architectures, but the internal distribution of ISA members varies. The following figure shows the internal distribution of ISA members in arm64 architecture

nonpointer

Has_assoc: flag bit of the associated object. 0 does not exist and 1 exists

Has_cxx_dtor: does the object have a destructor for C++ or Objc? If it has a destructor, the destructor logic needs to be done. If not, the object can be freed faster

Shiftcls: Store the value of the class pointer. With pointer optimization enabled, 33 bits are used to store the class pointer in the ARM64 architecture

Magic: Used by the debugger to determine whether the current object is a real object or has no space to initialize

Weakly_referenced: Weak variable of whether an object is pointed to or used to point to an ARC. Objects without weak references can be released faster

Deallocating: Indicates whether the object is freeing memory

Has_sidetable_rc: When the object reference technique is greater than 10, this variable is borrowed to store carry

Extra_rc: When representing the reference count of this object, the reference count is actually subtracted by 1. For example, if the object’s reference count is 10, the extra_rc is 9. If the reference count is greater than 10, the following has_sideTABLE_rc is used

Except for Shiftcls

5. Shiftcls association class

@interface FXPerson : NSObject
@end
@implementation FXPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FXPerson *p = [[FXPerson alloc] init];
        NSLog(@"% @",p);
    }
    return 0;
}
Copy the code

You can have an ISA property from the parent NSObject structure

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
Copy the code

However, the location of attributes in memory can be changed due to optimization, so the first place in memory must be ISA

5.1 Contradiction (not recommended)

① Print the first memory

② Binary print the memory value of the first bit memory

(3) Because the simulator is x86, we know from isa bit-field structure that shiftcls has three bits in front of it — three bits to the right — erasing the first three bits of ISA

④ There are 17 bits after shiftcls — 17 bits to the left — and 17 bits after ISA erased

⑤ Since we added all the trailing zeros — shift 17 bits to the right — we get Shiftcls

⑥ Newisa.shiftcls = (uintptr_t) CLS >> 3; — Shiftcls equals class address shifted 3 bits to the right

Compare the two sets of ShiftCLs binaries and find that they are the same binaries.

If you’re confused, look at the second method

5.2 Using object_getClass (Recommended)

① Use the objc/ Runtime. h> object_getClass method

#import <objc/runtime.h>

@interface FXPerson : NSObject
@end
@implementation FXPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FXPerson *p = [[FXPerson alloc] init];
        object_getClass(p);
    }
    return 0;
}
Copy the code

② Follow up the object_getClass method

/***********************************************************************
* object_getClass.
* Locking: None. If you add locking, tell gdb (rdar://7516456).
**********************************************************************/
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}
Copy the code

(3) follow up getIsa ()

#if SUPPORT_TAGGED_POINTERS

inline Class 
objc_object::getIsa() 
{
    if(! isTaggedPointer())return ISA();

    uintptr_t ptr = (uintptr_t)this;
    if (isExtTaggedPointer()) {
        uintptr_t slot = 
            (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        return objc_tag_ext_classes[slot];
    } else {
        uintptr_t slot = 
            (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
        returnobjc_tag_classes[slot]; }}Copy the code

ISA() {isTaggedPointer = false;

#if SUPPORT_NONPOINTER_ISA

inlineClass objc_object::ISA() { assert(! isTaggedPointer());#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}
Copy the code

⑤ If SUPPORT_INDEXED_ISA applies to WatchOS, return (Class)(isa.bits & ISA_MASK);

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif
Copy the code

6. Inspection

Print out the value of ISA & Mask and compare it to class (mask for x86 architecture)

The first address of the instance object must be ISA

6. Isa initialization flowchart

Four, ISA position

1. Only one class exists in memory

@interface FXPerson : NSObject
@end
@implementation FXPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Class class1 = [FXPerson class];
        Class class2 = [FXPerson alloc].class;
        Class class3 = object_getClass([FXPerson alloc]);
        Class class4 = [FXPerson alloc].class;
        NSLog(@"\n%p\n%p\n%p\n%p",class1,class2,class3,class4);
    }
    return 0;
}
Copy the code
0x1000024a0
0x1000024a0
0x1000024a0
0x1000024a0
Copy the code

There will only be one output proof class in memory, and there can be multiple instance objects (self-proof)

2.1 Viewing THE ISA Direction by Object or Class

A class, like an instance object, is instantiated by a parent called a metaclass

We first print the memory address of the class with p/x, then print the memory structure with x/4gx to get the corresponding ISA, and then offset it with mask to get the upper level that ISA points to (equivalent to object_getClass)

FXPerson class

② Offset by the FXPerson class to get the FXPerson metaclass pointer, and print the FXPerson metaclass to get isa

③ Offset the FXPerson metaclass to get the NSObject root metaclass pointer. Print the NSObject root metaclass to get ISA

④ The NSObject root metaclass is offset to get the NSObject root metaclass itself pointer

⑤ Print the NSObject root class to get ISA

⑥ The NSObject root metaclass pointer is offset by the NSObject root class

Conclusion:

Instance object -> Class Object -> metaclass -> root metaclass -> root metaclass (itself)

②NSObject (root) -> root metaclass -> root metaclass

Isas that point to the root metaclass are the same

2.2. View isa direction using NSObject

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // NSObject instance object
        NSObject *object1 = [NSObject alloc];
        / / NSObject class
        Class class = object_getClass(object1);
        / / NSObject metaclass
        Class metaClass = object_getClass(class);
        // NSObject root metaclass
        Class rootMetaClass = object_getClass(metaClass);
        // NSObject Root metaclass
        Class rootRootMetaClass = object_getClass(rootMetaClass);
        NSLog(@"\n%p instance object \n%p class \n%p metaclass \n%p root metaclass \n%p root metaclass",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
    }
    return 0;
}
Copy the code
0x100660Ba0 Instance object 0x7FfFACD3D140 Class 0x7FfFacd3D0F0 Metaclass 0x7FfFACd3D0F0 Root metaclass 0x7FFFACd3D0F0 Root metaclass 0x7FFFACd3D0F0Copy the code

Because it’s NSObject, its metaclass is the root metaclass — the output gets the root metaclass pointing to itself

2.3 Prove that classes and metaclasses are created by the system

(1) Perjury during operation

main
FXPerson class
FXPerson metaclass

② Check MachO file method

FXPerson

Conclusion: the object is the program according to the class instantiation of the class is written in code, only one copy in memory, is the system created metaclass is the system compiled, the system compiler created, convenient method compilationCopy the code

3. Isa bitmap

Isa route (dotted line) :
Instance object -> Class object -> metaclass -> root metaclass -> root metaclass (itself)

Inheritance relationship (solid line) : NSObject has a nil parent, and the parent of the root metaclass is NSObject

Easter egg – Compiler optimization

  • None[-O0]: Not optimized. In this setting, the compiler’s goal is to reduce compilation costs and ensure that the desired results are produced during debugging. The program statements are independent: if the program stops at a breakpoint on a line, we can assign a new value to any variable or point the program counter to any statement in the method, and get a run result that is exactly the same as the source code.
  • Fast[-O1]: Large functions require slightly more compile time and memory consumption. In this setting, the compiler tries to reduce the size of the code file and the execution time, but does not perform optimizations that require a lot of compile time. In Apple’s compiler, strict aliases, block rearrangements, and scheduling between blocks are disabled by default during optimization.
  • Faster[-O2]The compiler performs all supported optimization options that do not involve time-space swapping. In this setting, the compiler does not loop unwrap, function inlining, or register renaming. This setting increases compilation time and generated code performance compared to the ‘Fast[-o1]’ option.
  • Fastest[-O3]: Enables function inlining and register renaming options while enabling all optimizations supported by the ‘Fast[-o1]’ option. This setting may cause the binaries to become larger.
  • Fastest, Smallest[-Os]: Optimize the size. This setting enables all optimizations in the ‘Fast[-o1]’ option that do not increase the code size, and further optimizations that reduce the code size.
  • Fastest, Aggressive Optimizations[-Ofast]This setting enabled all of the optimizations in ‘Fastest[-O3]’, as well as active optimizations that might break the strict compilation standards but did not affect the code that worked well.

Debug default does not optimize, Relese default Fastest, Smallest[-OS]