美文网首页
属性的setter和getter方法

属性的setter和getter方法

作者: forping | 来源:发表于2020-11-19 18:04 被阅读0次

    函数调用

    简单声明四个属性

    @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]; }
    

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

    相关文章

      网友评论

          本文标题:属性的setter和getter方法

          本文链接:https://www.haomeiwen.com/subject/lpbkiktx.html