属性的setter和getter方法

2020-11-19  本文已影响0人  forping

函数调用

简单声明四个属性

@property (nonatomic, strong) NSMutableArray *arr;
@property (strong) NSMutableArray *arr1;
@property (nonatomic, copy) NSMutableArray *arr2;
@property (nonatomic, retain) NSMutableArray *arr3;

arr 转换成arm64汇编的方法,去除汇编指令,并加上简单的注释

"-[A arr]":                             ; @"\01-[A arr]"
sub    sp, sp, #16             ; =16  栈寄存器存储的值 -= 16
.cfi_def_cfa_offset 16 ; .cfi_def_cfa_offset 16 和 .cfi_offset %rbp, -16 会输出一些堆栈和调试信息,确保调试器要使用这些信息时能够找到
str    x0, [sp, #8]  ; 第一个参数self的地址 存放到 sp + 8 的地址值
str    x1, [sp] ; 第二个参数 SELF 存放到sp
.loc    1 15 47 prologue_end    ; 测试/A.h:15:47
ldr    x8, [sp, #8]  ; sp+8 也就是 self的地址 存放到x8
ldr    x0, [x8, #8] ; self的地址+8; 也就是 arr的地址 存放到x0 也就是返回值
add    sp, sp, #16             ; =16 恢复sp
ret ;返回arr的地址值

"-[A setArr:]":                         ; @"\01-[A setArr:]"
sub    sp, sp, #48             ; =48 ; 栈寄存器存储的值 - 48
stp    x29, x30, [sp, #32]     ; 16-byte Folded Spill ; x29 X30(函数的返回值)的值放到 sp + 32的 内存中, 两个64位寄存器 需要16个字节,刚好 48 -32 = 16
add    x29, sp, #32            ; =32 sp+32的地址值存储到x29
stur    x0, [x29, #-8] ; 第一个参数self的地址 存放到 X29 - 8(sp+24) 的地址值,
str    x1, [sp, #16] ; 第二个参数的地址SEL存放到sp + 16 的地址值
str    x2, [sp, #8] ; ; 第二个参数的地址newValue存放到sp + 8 的地址值
ldr    x1, [sp, #16] ; SEL 存储到x1
ldur    x0, [x29, #-8] ; self的地址 存放到X0
ldr    x2, [sp, #8] ;newValue存放到X2
mov    x3, #8 ; 8 赋值给X3
bl    _objc_setProperty_nonatomic ; 调用 _objc_setProperty_nonatomic 函数 (bl:将下一条指令(ldp    x29, x30, [sp, #32]     ; 16-byte Folded Reload)的地址存储到lr(x30)寄存器里)
ldp    x29, x30, [sp, #32]     ; 16-byte Folded Reload  将sp+32地址存储的值 赋给 x29 和 X30寄存器
add    sp, sp, #48             ; =48  还原栈
ret ; 返回

arr1

"-[A arr1]":                            ; @"\01-[A arr1]"
sub    sp, sp, #16             ; =16
.cfi_def_cfa_offset 16
str    x0, [sp, #8]
str    x1, [sp]
ldr    x1, [sp]
ldr    x0, [sp, #8]
mov    x2, #16
mov    w8, #1
and    w3, w8, ;0x1 //  w8 和 1 进行逻辑与运算,然后运算结果存储到w3,第三个参数
add    sp, sp, #16             ; =16
b    _objc_getProperty ; 用的b指令,证明不会返回到当前函数了. 执行完_objc_getProperty就跳转到当前函数的调用地址的下一条指令了


"-[A setArr1:]":                        ; @"\01-[A setArr1:]"
sub    sp, sp, #48             ; =48
stp    x29, x30, [sp, #32]     ; 16-byte Folded Spill
add    x29, sp, #32            ; =32
stur    x0, [x29, #-8]
str    x1, [sp, #16]
str    x2, [sp, #8]
ldr    x1, [sp, #16]
ldur    x0, [x29, #-8]
ldr    x2, [sp, #8]
mov    x3, #16
bl    _objc_setProperty_atomic
ldp    x29, x30, [sp, #32]     ; 16-byte Folded Reload
add    sp, sp, #48             ; =48
ret

arr2

"-[A arr2]":                            ; @"\01-[A arr2]"
sub    sp, sp, #16             ; =16
str    x0, [sp, #8]
str    x1, [sp]
ldr    x1, [sp]
ldr    x0, [sp, #8]
mov    x2, #24
mov    w8, #0
and    w3, w8, #0x1 ; 0和1 进行逻辑于运算,结果存储到 第三个参数
add    sp, sp, #16             ; =16
b    _objc_getProperty

"-[A setArr2:]":                        ; @"\01-[A setArr2:]"
sub    sp, sp, #48             ; =48
stp    x29, x30, [sp, #32]     ; 16-byte Folded Spill
add    x29, sp, #32            ; =32
stur    x0, [x29, #-8]
str    x1, [sp, #16]
str    x2, [sp, #8]
ldr    x1, [sp, #16]
ldur    x0, [x29, #-8]
ldr    x2, [sp, #8]
mov    x3, #24
bl    _objc_setProperty_nonatomic_copy
ldp    x29, x30, [sp, #32]     ; 16-byte Folded Reload
add    sp, sp, #48             ; =48
ret

arr3

"-[A arr3]":                            ; @"\01-[A arr3]"
sub    sp, sp, #16             ; =16
str    x0, [sp, #8]
str    x1, [sp]
ldr    x8, [sp, #8]
ldr    x0, [x8, #32]
add    sp, sp, #16             ; =16
ret

"-[A setArr3:]":                        ; @"\01-[A setArr3:]"
sub    sp, sp, #48             ; =48
stp    x29, x30, [sp, #32]     ; 16-byte Folded Spill
add    x29, sp, #32            ; =32
stur    x0, [x29, #-8]
str    x1, [sp, #16]
str    x2, [sp, #8]
ldr    x1, [sp, #16]
ldur    x0, [x29, #-8]
ldr    x2, [sp, #8]
mov    x3, #32
bl    _objc_setProperty_nonatomic
ldp    x29, x30, [sp, #32]     ; 16-byte Folded Reload
add    sp, sp, #48             ; =48
ret

getter 函数里第一个和第四个属性的汇编码基本一样,根据地址得到属性的值,第二个和第三个最后跳转到了_objc_getProperty然后获得值:

setter函数跳转 _objc_setProperty_nonatomic, _objc_setProperty_atomic, _objc_setProperty_nonatomic_copy三个函数,我们从runtime源码中来找到这些函数

setter

//底层都是调用了 reallySetProperty函数
void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}
void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}
void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, false, true, false);
}

reallySetProperty

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) { // offset 表示赋值的是isa
        object_setClass(self, newValue);
        return;
    }

    id oldValue; 
    id *slot = (id*) ((char*)self + offset); // self 保存的地址值加上偏移量得到旧值的地址值, slot 是一个指针,存储的是 旧值的地址值 ,旧值如果是对象,那么旧值也会是个指针,因此用id * 来表示

// 针对策略处理新的value值
    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {// 如果不进行copy操作的话
        if (*slot == newValue) return; // 如果两个值的地址是一样的,直接返回
        newValue = objc_retain(newValue); retain新值
    }

    if (!atomic) { // 如果不需要加锁
        oldValue = *slot; // 旧值 赋给oldValue
        *slot = newValue; //  self + offset 的这个地址保存newValue的地址值
    } else {
// extern StripedMap<spinlock_t> PropertyLocks;
// using spinlock_t = mutex_tt<LOCKDEBUG>;
// typedef mutex_t spinlock_t;
// using mutex_t = mutex_tt<LOCKDEBUG>;
// class mutex_tt : nocopy_t {
//os_unfair_lock mLock;
//...
//}
// & 是引用符号,引用是C++对C的一个重要补充,在声明一个引用时,必须同时使之初始化,即声明它代表哪一个变量。请注意:由于引用不是独立的变量,编译系统不给它单独分配存储单元,因此在建立引用时只有声明没有定义,只是声明它与原有的某一变量的关系。在声明一个变量的引用后,在本函数执行期间,该引用一直与其代表的变量相联系,不能再作为其他变量的别名。
// 怎样区分&是引用还是取地址符呢?方法是:判断&a这样的形式前是否有类型符即int &a=b;如果有类型符(int)则是引用,否则是取地址运算符。
// 所以  spinlock_t&  就是 spinlock_t类型,只不过引用了 PropertyLocks[slot];  的返回值,不占用存储单元
        spinlock_t& slotlock = PropertyLocks[slot]; // spinlock_t 是os_unfair_lock互斥锁 ,因为OSSpinLock自旋锁已经不安全了,会有优先级反转的问题
        slotlock.lock(); // 加锁
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock(); // 解锁
    }

    objc_release(oldValue); // 旧值的引用计数-1
}

看代码的时候对id *slot = (id*) ((char*)self + offset);有所疑惑,画了个图就明白了.

image.png

getter方法

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) { // 判断偏移量 如果是0,根据isa指针获得类对象
        return object_getClass(self);
    }

    // Retain release world
    id *slot = (id*) ((char*)self + offset); // 根据偏移量获得 获得成员变量的地址值. 而成员变量是一个id类型的 因此用 id *引用
    if (!atomic) return *slot; // 如果不用加锁,就直接返回
        
    // Atomic retain release world
    spinlock_t& slotlock = PropertyLocks[slot]; // 获得锁
    slotlock.lock(); // 加锁
    id value = objc_retain(*slot); // 获得值
    slotlock.unlock(); // 解锁
    
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value); // 返回value
}

id 
objc_autoreleaseReturnValue(id obj)
{
    if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;

    return objc_autorelease(obj);
}
id objc_autorelease(id obj) { return [obj autorelease]; }

如果不加锁,获得值直接返回,如果加锁,直接返回或加入自动释放池返回

上一篇下一篇

猜你喜欢

热点阅读