How does the compiler handle the two different cases when we use atomic and nonatomic to modify properties, respectively? We all know that using atomic modifier properties is not thread-safe, so how is it different from nonatomic? Where do atomic performance losses come from? How is the copy property implemented? And so on. We’ll look at all the questions about attribute modifiers in this article. ⛽ ️ ⛽ ️

@ property modifier

Start by defining a LGPerson class and adding a set of properties with the following different modifiers, the essence of which the compiler automatically generates for us: _Ivar + setter + getter.

// lgPerson. h: lgPerson. m does not write anything
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject

@property (nonatomic, strong) NSObject *objc_nonatomic_strong;
@property (nonatomic, retain) NSObject *objc_nonatomic_retain;
@property (nonatomic, copy) NSObject *objc_nonatomic_copy;
@property (nonatomic, weak) NSObject *objc_nonatomic_weak;
@property (nonatomic, unsafe_unretained) NSObject *objc_nonatomic_unsafe_unretained;
@property (nonatomic, assign) NSObject *objc_nonatomic_assign;

// Readonly modifies the property. The compiler automatically generates only getters
@property (nonatomic, strong, readonly) NSObject *objc_nonatomic_strong_readonly;

@property (atomic, strong) NSObject *objc_atomic_strong;
@property (atomic, retain) NSObject *objc_atomic_retain;
@property (atomic, copy) NSObject *objc_atomic_copy;
@property (atomic, weak) NSObject *objc_atomic_weak;
@property (atomic, unsafe_unretained) NSObject *objc_atomic_unsafe_unretained;
@property (atomic, assign) NSObject *objc_atomic_assign;

@end
NS_ASSUME_NONNULL_END
Copy the code

Select the real mode, make sure to compile the ARM assembly instructions, and then select lgPerson.m on the left side of Xcode. Generate assembly instructions from the Xcode menu bar Product -> Perform Action -> Assemble “lgPerson.m” to see the assembly implementation of the setter getter methods for all of our properties.

[LGPerson objc_nonatomic_strong]

    .p2align    2               ; -- Begin function -[LGPerson objc_nonatomic_strong]
"-[LGPerson objc_nonatomic_strong]":; @"\01-[LGPerson objc_nonatomic_strong]"
Lfunc_begin0:
    .loc    1 27 0                  ; Simple_iOS/LGPerson.h:27:0
    .cfi_startproc
; %bb. 0:
    sub    sp, sp, #16             ; =16
    .cfi_def_cfa_offset 16
    str    x0, [sp, #8]
    str    x1, [sp]
Ltmp1:
    .loc    1 27 41 prologue_end    ; Simple_iOS/LGPerson.h:27:41
    ldr    x0, [sp, #8]
    ldr    x0, [x0, #8]
    add    sp, sp, #16             ; =16
    ret
Ltmp2:
Lfunc_end0:
    .cfi_endproc
                                        ; -- End function
Copy the code

The getter of the objc_nonatomic_STRONG property calls no function internally, just the address offset value.

[LGPerson setObjc_nonatomic_strong:]

    .p2align    2               ; -- Begin function -[LGPerson setObjc_nonatomic_strong:]
"-[LGPerson setObjc_nonatomic_strong:]":; @"\01-[LGPerson setObjc_nonatomic_strong:]"
Lfunc_begin1:
    .loc    1 27 0                  ; Simple_iOS/LGPerson.h:27:0
    .cfi_startproc
; %bb. 0:
    sub    sp, sp, #48             ; =48
    stp    x29, x30, [sp, #32];16-byte Folded Spill
    add    x29, sp, #32            ; =32
    .cfi_def_cfa w29, 16
    .cfi_offset w30, - 8 -
    .cfi_offset w29, - 16
    stur    x0, [x29, #- 8 -]
    str    x1, [sp, #16]
    str    x2, [sp, #8]
Ltmp3:
    .loc    1 0 0 prologue_end      ; Simple_iOS/LGPerson.h:0:0
    ldr    x0, [sp, #8]
    ldur    x1, [x29, #- 8 -]
    add    x1, x1, #8              ; =8
    str    x0, [sp]                ; 8-byte Folded Spill
    mov    x0, x1
    ldr    x1, [sp]                ; 8-byte Folded Reload
    bl    _objc_storeStrong // ⬅️ See the bl directive jump to the objc_storeStrong function
    .loc    1 27 41                 ; Simple_iOS/LGPerson.h:27:41
    ldp    x29, x30, [sp, #32];16-byte Folded Reload
    add    sp, sp, #48             ; =48
    ret
Ltmp4:
Lfunc_end1:
    .cfi_endproc
                                        ; -- End function
Copy the code

In the setter function of the objc_nonatomic_STRONG property, we see that the BL directive jumps to the objc_storeStrong function. The objc_storeStrong function, which we parsed earlier when we read retain and release, will do just that: retain new values and release old values.

The objc_storeStrong function implements:

void
objc_storeStrong(id *location, id obj)
{
    // 1. Fetch the old value that the attribute pointed to
    id prev = *location;
    
    // 2. If the old value is the same as the new value passed by the input parameter, there is no need to assign the value
    if (obj == prev) {
        return;
    }
    
    Retain new value obj, obj reference count +1
    objc_retain(obj);
    
    // 4. Point my property to the new value
    *location = obj;
    
    // 5. Release the old value
    objc_release(prev);
}
Copy the code

[LGPerson objc_nonatomic_retain]/[LGPerson setObjc_nonatomic_retain:]

The setter and getter functions for the objC_nonatomic_retain property are the same as those for the objC_nonatomic_strong property and will not be analyzed here.

[LGPerson objc_nonatomic_copy]

    .p2align    2               ; -- Begin function -[LGPerson objc_nonatomic_copy]
"-[LGPerson objc_nonatomic_copy]":; @"\01-[LGPerson objc_nonatomic_copy]"
Lfunc_begin4:
    .loc    1 29 0                  ; Simple_iOS/LGPerson.h:29:0
    .cfi_startproc
; %bb. 0:
    sub    sp, sp, #32             ; =32
    .cfi_def_cfa_offset 32
    str    x0, [sp, #24]
    str    x1, [sp, #16]
Ltmp11:
    .loc    1 29 39 prologue_end    ; Simple_iOS/LGPerson.h:29:39
    ldr    x0, [sp, #16]
    ldr    x1, [sp, #24]
    orr    w8, wzr, #0x18
    mov    x2, x8
    
    mov    w8, #0 // Low word save 0
    mov    x3, x8 // x3 stores 0, indicating that the 4th parameter BOOL atomic is 0 (false) when jumping to objc_getProperty
                  // x0-x7 registers hold function parameters
                  
    str    x0, [sp, #8];8-byte Folded Spill
    mov    x0, x1
    ldr    x1, [sp, #8];8-byte Folded Reload
    add    sp, sp, #32             ; =32
    b    _objc_getProperty // ⬅️ see b directive jump to objc_getProperty function
Ltmp12:
Lfunc_end4:
    .cfi_endproc
                                        ; -- End function
Copy the code

In the getter function of the objc_nonatomic_copy property, you can see that the b instruction finally jumps to the objc_getProperty function. Let’s take a look at the objc_getProperty function implementation. If the property is not atomic, there is no need to lock the read process. The first half of the objc_getProperty function already returns the member variable, which is still found by the self pointer offset and returned. If the property is atomic, a lock is acquired via PropertyLocks[slot] with id value = objc_retain(*slot) a retain operation is performed on the member variable, reference count +1, Then, for performance purposes, after unlocking, call objc_autoreleaseReturnValue(value) to place the member variable into the automatic release pool, which is guaranteed to cancel the retain operation.

// ptrdiff_t offset
// Ptrdiff_t is a machine-specific data type defined in the C/C++ standard library.
The ptrdiff_t type variable is usually used to hold the results of two pointer subtraction operations.
The ptrdiff_t type should be sufficient to hold the difference between two Pointers in the same array, which may be negative.

// offset is the offset of a member variable from the start address of the object.

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    // If offset is 0, return the address of the class object to which the object belongs
    if (offset == 0) {
        return object_getClass(self);
    }

    // Retain release world
    // self pointer offset finds member variables
    id *slot = (id*) ((char*)self + offset);
    // If atomic is false, return the member variable directly
    if(! atomic)return *slot;
        
    // Atomic retain release world
    
    // Get the lock from the global attribute lock list
    spinlock_t& slotlock = PropertyLocks[slot];
    / / lock
    slotlock.lock(a);// retain
    id value = objc_retain(*slot);
    / / unlock
    slotlock.unlock(a);// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    // Putting value into the auto-release pool balances the reference count with the retain operation above
    return objc_autoreleaseReturnValue(value);
}
Copy the code

[LGPerson setObjc_nonatomic_copy:]

    .p2align    2               ; -- Begin function -[LGPerson setObjc_nonatomic_copy:]
"-[LGPerson setObjc_nonatomic_copy:]":; @"\01-[LGPerson setObjc_nonatomic_copy:]"
Lfunc_begin5:
    .loc    1 29 0                  ; Simple_iOS/LGPerson.h:29:0
    .cfi_startproc
; %bb. 0:
    sub    sp, sp, #48             ; =48
    stp    x29, x30, [sp, #32];16-byte Folded Spill
    add    x29, sp, #32            ; =32
    .cfi_def_cfa w29, 16
    .cfi_offset w30, - 8 -
    .cfi_offset w29, - 16
    stur    x0, [x29, #- 8 -]
    str    x1, [sp, #16]
    str    x2, [sp, #8]
Ltmp13:
    .loc    1 29 39 prologue_end    ; Simple_iOS/LGPerson.h:29:39
    ldr    x1, [sp, #16]
    ldur    x0, [x29, #- 8 -]
    ldr    x2, [sp, #8]
    orr    x3, xzr, #0x18
    bl    _objc_setProperty_nonatomic_copy // ⬅️ see the bl directive jump to the objc_setProperty_nonatomic_copy function
    ldp    x29, x30, [sp, #32];16-byte Folded Reload
    add    sp, sp, #48             ; =48
    ret
Ltmp14:
Lfunc_end5:
    .cfi_endproc
                                        ; -- End function
Copy the code

Inside the setter function for the objc_nonatomic_copy property we see that the BL directive jumps to the objc_setProperty_nonatomic_copy function. Let’s look at the objc_setProperty_nonatomic_copy function implementation.

void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    // Call reallySetProperty directly
    // Take the following three arguments
    
    // atomic: false
    // copy: true
    // mutableCopy: false
    
    reallySetProperty(self, _cmd, newValue, offset, false.true.false);
}
Copy the code
// atomic: false
// copy: true
// mutableCopy: false

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    // If offset is 0, changeIsa is called to modify the object's ISA
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    // A temporary variable used to record the old value, mainly at the end of the old value release,
    // Release the old value
    id oldValue;
    
    / / based on the current offset obtains the object to be a setter of member variables (old)
    id *slot = (id*) ((char*)self + offset);
    
    if (copy) {
        // Copy newValue once.
        // Assign the copy result directly to newValue
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        // If it's mutableCopy, perform mutableCopy on newValue,
        // Assign the result of mutableCopy directly to newValue
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        // If the old value is the same as the new value, return
        if (*slot == newValue) return;
        
        / / retain new values
        newValue = objc_retain(newValue);
    }

    if(! atomic) {// If it is not atomic, it does not need to be locked.
        // Assigning *slot to oldValue releases oldValue at the end of the function
        oldValue = *slot;
        
        // Assign the new value to the member variable of the object
        *slot = newValue;
    } else {
        // If it is atomic, the assignment process will be locked,
        // See that the attribute modified by atomic only locks the assignment of new and old values.
        // This locking operation is a source of performance loss compared to nonatomic not locking.
        Atomic only locks the setter and getter,
        // Only setters and getters are thread-safe, but most of our daily development is composite operations,
        // if self.a = self.a + 1;
        // This operation contains getters, setters, and add operations.
        // Atomic only locks individual getter and setter operations,
        // There is no guarantee that this compound operation is thread-safe. Additional locks are required to achieve thread-safe operation.
        
        // The following pseudocode:
        // lock.lock();
        // self.a = self.a + 1;
        // lock.unlock();
        
        // Get the lock from the global attribute lock list
        spinlock_t& slotlock = PropertyLocks[slot];
        / / lock
        slotlock.lock(a);// Assigning *slot to oldValue releases oldValue at the end of the function
        oldValue = *slot;
        // Assign the new value to the member variable of the object
        *slot = newValue;
        
        / / unlock
        slotlock.unlock(a); }// Release the old value
    objc_release(oldValue);
}
Copy the code

[LGPerson objc_nonatomic_weak]

    .p2align    2               ; -- Begin function -[LGPerson objc_nonatomic_weak]
"-[LGPerson objc_nonatomic_weak]":; @"\01-[LGPerson objc_nonatomic_weak]"
Lfunc_begin6:
    .loc    1 30 0                  ; Simple_iOS/LGPerson.h:30:0
    .cfi_startproc
; %bb. 0:
    sub    sp, sp, #32             ; =32
    stp    x29, x30, [sp, #16];16-byte Folded Spill
    add    x29, sp, #16            ; =16
    .cfi_def_cfa w29, 16
    .cfi_offset w30, - 8 -
    .cfi_offset w29, - 16
    str    x0, [sp, #8]
    str    x1, [sp]
Ltmp15:
    .loc    1 30 39 prologue_end    ; Simple_iOS/LGPerson.h:30:39
    ldr    x0, [sp, #8]
    add    x0, x0, #32             ; =32
    bl    _objc_loadWeakRetained // ⬅️ See bl command jump to objc_loadWeakRetained function
    ldp    x29, x30, [sp, #16];16-byte Folded Reload
    add    sp, sp, #32             ; =32
    b    _objc_autoreleaseReturnValue // ⬅️ see bl directive jump to objc_autoreleaseReturnValue function
Ltmp16:
Lfunc_end6:
    .cfi_endproc
                                        ; -- End function
Copy the code

Across the getter of the objc_nonatomic_weak property, bl jumps to objc_loadWeakRetained function. At the end, B jumps to objc_autoreleaseReturnValue. I don’t think of the objc_loadWeak function here.

id
objc_loadWeak(id *location)
{
    if(! *location)return nil;
    return objc_autorelease(objc_loadWeakRetained(location));
}
Copy the code

This function was examined in detail in the Weak section, which will not be repeated here. (Retain is paired with autorelease to prevent object release during read, and delayed release in the automatic release pool ensures object destruction.)

[LGPerson setObjc_nonatomic_weak:]

. bl _objc_storeWeak ...Copy the code

In the setter function of the objc_nonatomic_weak property, the bl directive jumps to the objc_storeWeak function, which is a very long and important function and is analyzed in detail in the weak section. (Weak will not retain the new value)

[LGPerson objc_nonatomic_unsafe_unretained]

The getter of the objC_nonatomic_unsafe_unretained property is the same as the getter of the objC_nonatomic_strong property. No function is called internally but the address offset value is retained.

[LGPerson setObjc_nonatomic_unsafe_unretained:]

    .p2align    2               ; -- Begin function -[LGPerson objc_nonatomic_unsafe_unretained]
"-[LGPerson objc_nonatomic_unsafe_unretained]":; @"\01-[LGPerson objc_nonatomic_unsafe_unretained]"
Lfunc_begin8:
    .loc    1 31 0                  ; Simple_iOS/LGPerson.h:31:0
    .cfi_startproc
; %bb. 0:
    sub    sp, sp, #16             ; =16
    .cfi_def_cfa_offset 16
    str    x0, [sp, #8]
    str    x1, [sp]
Ltmp20:
    .loc    1 31 52 prologue_end    ; Simple_iOS/LGPerson.h:31:52
    ldr    x0, [sp, #8]
    ldr    x0, [x0, #40]
    add    sp, sp, #16             ; =16
    ret
Ltmp21:
Lfunc_end8:
    .cfi_endproc
                                        ; -- End function
Copy the code

The setter function of the objc_nonatomic_unsafe_unretained property sees that no other function is called internally, except pure input, address offset, and stored in the location of the input parameter to the member variable. This also validates the nature of the unsafe_unretained setter, which retains no new value and releases no old value. Both setters and getters simply store and read values by address. Retained property of unsafe_unretained is not retained by unsafe_unretained property. Therefore, the retained property of unsafe_unretained is not nil. In this case, if we use the unsafe_unretained property to read the object based on the address, the access of wild Pointers will be directly triggered, leading to crash.

[LGPerson objc_nonatomic_assign]/[LGPerson setObjc_nonatomic_assign:]

The setter and getter of the OBJC_nonatomic_assign attribute is exactly the same as the objC_Nonatomic_unsafe_unretained attribute, so we won’t expand it here.

[LGPerson objc_nonatomic_strong_readonly]

The objc_nonatomic_STRONG_readonly property generates only getters, as we would expect.

[LGPerson objc_atomic_strong]/[LGPerson setObjc_atomic_strong:]

// getter. .W3 = 1; BOOL atomic is true; w3 = 1
// x0-x7 registers hold function parameters
orr    w3, wzr, #0x1. b _objc_getProperty ...// setter. bl _objc_setProperty_atomic ...Copy the code
void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    // The atomic value uses true
    reallySetProperty(self, _cmd, newValue, offset, true.false.false);
}
Copy the code

The objc_atomic_STRONG property locks both setter and getter functions.

[LGPerson objc_atomic_retain]/[LGPerson setObjc_atomic_retain:]

The objC_atomic_retain property is the same as the setter and getter for the objC_atomic_strong property and is not expanded.

[LGPerson objc_atomic_copy]/[LGPerson setObjc_atomic_copy:]

// getter. orr w3, wzr, #0x1 // The fourth parameter BOOL atomic is true. b _objc_getProperty ...// setter
bl    _objc_setProperty_atomic_copy
Copy the code
void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    // The atomic value uses true
    reallySetProperty(self, _cmd, newValue, offset, true.true.false);
}
Copy the code

Objc_atomic_weak, objc_atomic_unsafe_unretained, and objc_atomic_assign are the same as the setter and getter of the corresponding nonatomic modified property, so they are not expanded. __strong, __weak, __unsafe_unretained member variables, and so on. Let’s find out.

Member variable modifier

__strong, __weak, and __unsafe_unretained (if not specified, the default is __strong). This allows member variables to be managed by ARC as correctly as attributes modified by the strong, weak, and unsafe_unretained modifiers. Define the following test classes:

// lgPerson.h. m implements nothing
@interface LGPerson : NSObject {
    NSObject *ivar_none; // Objects without modifiers add __strong by default
    __strong NSObject *ivar_strong;
    __weak NSObject *ivar_weak;
    __unsafe_unretained NSObject *ivar_unsafe_unretained;
}
@end

// Write the following code to test separately:
    LGPerson *person = [[LGPerson alloc] init];
    NSObject *temp = [[NSObject alloc] init];
    
    NSLog(@"START");
    person->ivar_none = temp;
    
// person->ivar_strong = temp;
// NSLog(@"TTT %@", person->ivar_strong);

// person->ivar_weak = temp;
// NSLog(@"read weak: %@", person->ivar_weak);

// person->ivar_unsafe_unretained = temp;
    NSLog(@"END"); // ⬅️ interrupts here
Copy the code

Debug -> Debug Workflow -> Always Show Disassembly check Always Show Disassembly to run the program when the breakpoint is executed. Our code is compiled into assembly code and we see the instruction jump when temp assigns a value to a property:

  • ivar_noneassignmentbl 0x10092e470; symbol stub for: objc_storeStrong
  • ivar_strongassignmentbl 0x1009be470; symbol stub for: objc_storeStrong
  • ivar_weakassignmentbl 0x1009be47c; symbol stub for: objc_storeWeakIs called when readingobjc_loadWeakRetainedobjc_release
  • ivar_unsafe_unretainedAssignment does not involve any instruction jump, but simply stores values by address.

The result is exactly the same as when we tested the properties with the different modifiers above. When analyzing the assembly code for the above properties, we know that the compiler does different things for different property modifiers to properly manage the object reference count when generating getter setter functions for properties. So where do we store the modifiers we specify for different member variables? How does it work?

ivarLayout/weakIvarLayout

Struct class_ro_t const Uint8_t * ivarLayout and const Uint8_t * weakIvarLayout record which member variables are strong or weak respectively. What’s missing is the base type and the __unsafe_unretained object type. These two values can be accessed and modified through several apis provided by the Runtime:

OBJC_EXPORT const uint8_t * _Nullable class_getIvarLayout(Class _Nullable cls) OBJC_AVAILABLE(10.5.2.0.9.0.1.0.2.0);
OBJC_EXPORT const uint8_t * _Nullable class_getWeakIvarLayout(Class _Nullable cls) OBJC_AVAILABLE(10.5.2.0.9.0.1.0.2.0);
OBJC_EXPORT void class_setIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout) OBJC_AVAILABLE(10.5.2.0.9.0.1.0.2.0);
OBJC_EXPORT void class_setWeakIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout) OBJC_AVAILABLE(10.5.2.0.9.0.1.0.2.0);
Copy the code

The ivarLayout and weakIvarLayout types are uint8_t *, where a Uint8_t is two bits in hexadecimal.

IvarLayout is a sequence of two characters, such as \ XMN. In each Ivar Layout, the first character indicates m non-strong attributes, and the second character indicates n strong attributes.

🌰 1:

// LGPerson.h @interface LGPerson : NSObject { __strong NSObject *ivar_strong; __strong __strong NSObject *ivar_strong2; __weak NSObject *ivar_weak; __weak NSObject *ivar_weak2; __unsafe_unretained NSObject *ivar_unsafe_unretained; }Copy the code

The console executes the following commands:

Class_getIvarLayout Gets ivarLayout
(lldb) p class_getIvarLayout([LGPerson class])
(const uint8_t *) $0 = 0x0000000100000f89 "\x02"
(lldb) x/2xb $0
0x100000f89: 0x02 0x00 
// 0x02 is preceded by 0 and followed by 2, which indicates two consecutive strong IVars (ivar_strong and ivar_STRONG2).

// class_getWeakIvarLayout Gets weakIvarLayout
(lldb) p class_getWeakIvarLayout([LGPerson class])
(const uint8_t *)The $1= 0x0000000100000f8b """ (lldb) x/2xb $1 0x100000f8b: 0x22 0x00 // 0x22 The first 2 indicates two consecutive non-weak IVArs (iVAR_strong and iVAR_Strong2), and the second 2 indicates two consecutive weak IVars (iVAR_weak and iVAR_weak2).Copy the code

🌰 2:

@interface LGPerson : NSObject { __strong NSObject *ivar_strong; // Objects without modifiers add __strong int a by default; __strong NSObject *ivar_strong2; __weak NSObject *ivar_weak; __weak NSObject *ivar_weak2; __unsafe_unretained NSObject *ivar_unsafe_unretained; }Copy the code

The console executes the following commands:

(lldb) p class_getIvarLayout([LGPerson class])
(const uint8_t *) $0 = 0x0000000100000f8b "\x01\x11"
(lldb) x/3xb $0
0x100000f8b: 0x01 0x11 0x00
// 0 before 0x01 indicates a strong Ivar (ivar_strong),
// 0x11 1 before is a non-strong Ivar (a) 1 after is a strong Ivar (ivar_STRONG2).

(lldb) p class_getWeakIvarLayout([LGPerson class])
(const uint8_t *)The $1= 0x0000000100000f8e "2"
(lldb) x/2xb $1
0x100000f8e: 0x32 0x00 
// 0x32 indicates three consecutive non-weak ivArs (ivar_strong, a, and ivar_STRONG2).
// The last 2 indicates two consecutive weak Ivar (iVAR_weak and iVAR_weak2).
Copy the code

🌰 3:

@interface LGPerson : NSObject { int a; __strong NSObject *ivar_strong; // Objects without modifiers add __strong int b by default; __strong NSObject *ivar_strong2; __strong NSObject *ivar_strong3; int c; __weak NSObject *ivar_weak; int d; __weak NSObject *ivar_weak2; __weak NSObject *ivar_weak3; __weak NSObject *ivar_weak4; __strong NSObject *ivar_strong4; __unsafe_unretained NSObject *ivar_unsafe_unretained; }Copy the code

The console executes the following commands:

(lldb) p class_getIvarLayout([LGPerson class])
(const uint8_t *) $0 = 0x0000000100000f85 "\x11\x12a"
(lldb) x/4xb $0
0x100000f85: 0x11 0x12 0x61 0x00
// 1 before 0x11 indicates a non-strong Ivar (a),
// 1 indicates strong Ivar (ivar_strong).
// 1 before 0x12 indicates non-strong Ivar (b),
// 2 indicates two consecutive strong IVars (iVAR_STRONG2 and iVAR_STRONG3).
// 0x61 6 indicates six consecutive non-strong Ivar (c, iVAR_weak, D, iVAR_weak2, iVAR_Weak3, iVAR_Strong4),
// 1 indicates a strong Ivar (ivar_strong4).

(lldb) p class_getWeakIvarLayout([LGPerson class])
(const uint8_t *)The $1= 0x0000000100000f89 "a\x13"
(lldb) x/3xb $1
0x100000f89: 0x61 0x13 0x00
// 0x61 indicates six consecutive non-weak ivArs (a, iVAR_strong, B, iVAR_STRONG2, iVAR_STRONG3, and C).
// The value 1 indicates a weak Ivar (ivar_weak).
// 1 before 0x13 indicates a non-weak Ivar (d),
Weak2, weak3, weak4) iVAR_weak2, iVAR_weak3, iVAR_weak4
Copy the code

🌰 4: what if the number of consecutive strong ivArs exceeds 0xF? Will restart a uint8_t to record)

@interface LGPerson : NSObject { __weak NSObject *ivar_weak1; . __weak NSObject *ivar_weak18; // Define 18 weak Ivar __strong NSObject *ivar_strong4; __unsafe_unretained NSObject *ivar_unsafe_unretained; } @endCopy the code

The console executes the following commands:

(lldb) p class_getIvarLayout([LGPerson class])
(const uint8_t * _Nullable) $0 = 0x0000000100003e98 "\xfffffff01"
(lldb) x/3xb $0
0x100003e98: 0xf0 0x31 0x00
// 0xf0 f indicates fifteen consecutive non-strong IVars,
// 0 indicates zero strong Ivar.
// 0x31 indicates that there are 18 weak ivArs in the sequence of three non-strong ivars and 15 in the sequence.
// A strong Ivar (ivar_strong4)

(lldb) p class_getWeakIvarLayout([LGPerson class])
(const uint8_t * _Nullable)The $1= 0x0000000100003e9b "\x0f\x03"
(lldb) x/3xb $1
0x100003e9b: 0x0f 0x03 0x00
// 0x0f and 0x03, f + 3 indicates 18 weak ivars in a row
Copy the code

For ivarLayout, the high 4 bits of each Uint8_t represent the continuous number of non-Storng type Ivar (m), m ∈ [0x0, 0xF], and the low 4 bits represent the continuous number of strong type Ivar (n). N ∈ [0x0, 0xF]. For weakIvarLayout, the high 4 bits of each Uint8_t represent the continuous number of non-weak Ivar (m), m ∈ [0x0, 0xF], and the low 4 bits represent the continuous number of weakIvar (n). N ∈ [0x0, 0xF]. For both ivarLayout and weakIvarLayout, the end needs to be filled with the \x00 end.

As for ivarLayout, it only cares about the number of strong member variables, and the number of non-strong variables in front of it is simply a matter of moving the index values correctly. Any non-strong variables after the last strong variable are automatically ignored. Similarly, the original intention of Apple is to describe the memory modifiers of each member variable of the class with as little memory as possible. WeakIvarLayout uses 2 + 1 = 3 bytes. WeakIvarLayout uses 2 + 1 = 3 bytes. WeakIvarLayout describes the memory modifier of 20 variables.

Refer to the link

Reference link :🔗

  • How does ObjC use runtime to change Ivar memory management mode
  • Objective-c Class Ivar Layout exploration
  • Objective-c class member variables in depth
  • IOS Base series — Atomic, nonatomic
  • How is the extreme Crash rate below 0.01% achieved?
  • [iOS] In-depth understanding of IVAR and property
  • Declared Properties
  • Summary of the iOS @property attribute
  • Some understanding of the atomic keyword