【iOS】——属性关键字的底层原理

strong,retain,copy,atomic,nonatomic c++源码

@interface propertyTest : NSObject
@property (nonatomic, strong) NSString *nsstring___StrongTest;
@property (nonatomic, retain) NSString *nsstring___RetainTest;
@property (nonatomic, copy) NSString *nsstring___CopyTest;
@property (atomic, copy) NSString *nsstring___AtomicCopyTest;
@end

用clang -rewrite-objc propertyTest.m生成c++源码看如下:

// 实现propertyTest类的定义
@implementation propertyTest

// 以下是对不同属性类型的实现细节

// 对于强引用属性(__strong)的get方法
static NSString * _I_propertyTest_nsstring___StrongTest(propertyTest * self, SEL _cmd) {
    // 强引用的get方法直接返回实例变量的值。
    // 使用*(NSString **)来解引用指针,((char *)self + ...)计算出实例变量在内存中的位置。
    return (*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___StrongTest));
}

// 对于强引用属性(__strong)的set方法
static void _I_propertyTest_setNsstring___StrongTest_(propertyTest * self, SEL _cmd, NSString *nsstring___StrongTest) {
    // 强引用的set方法直接赋值给实例变量。
    (*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___StrongTest)) = nsstring___StrongTest;
}

// 对于retain属性的get方法
static NSString * _I_propertyTest_nsstring___RetainTest(propertyTest * self, SEL _cmd) {
    // retain属性的get方法同样直接返回实例变量的值。
    return (*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___RetainTest));
}

// 声明objc_setProperty函数,这是一个外部定义的运行时函数。
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

// 对于retain属性的set方法
static void _I_propertyTest_setNsstring___RetainTest_(propertyTest * self, SEL _cmd, NSString *nsstring___RetainTest) {
    // retain属性的set方法使用objc_setProperty运行时函数,该函数负责调用retain和release。
    // 第四个参数为false表示不需要复制,第五个参数为false表示不需要原子性操作。
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___RetainTest), (id)nsstring___RetainTest, false, false);
}

// 对于copy属性的get方法
static NSString * _I_propertyTest_nsstring___CopyTest(propertyTest * self, SEL _cmd) {
    // copy属性的get方法直接返回实例变量的值。
    return (*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___CopyTest));
}

// 对于copy属性的set方法
static void _I_propertyTest_setNsstring___CopyTest_(propertyTest * self, SEL _cmd, NSString *nsstring___CopyTest) {
    // copy属性的set方法使用objc_setProperty运行时函数,该函数负责调用copy和release。
    // 第四个参数为false表示不需要retain,第五个参数为true表示需要复制。
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___CopyTest), (id)nsstring___CopyTest, false, true);
}

// 声明objc_getProperty函数,这是一个外部定义的运行时函数。
extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);

// 对于原子性copy属性(atomic copy)的get方法
static NSString * _I_propertyTest_nsstring___AtomicCopyTest(propertyTest * self, SEL _cmd) {
    // 原子性copy属性的get方法使用objc_getProperty运行时函数,该函数负责原子性读取。
    // 第四个参数为true表示需要原子性操作。
    typedef NSString * _TYPE;
    return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___AtomicCopyTest), true);
}

// 对于原子性copy属性(atomic copy)的set方法
static void _I_propertyTest_setNsstring___AtomicCopyTest_(propertyTest * self, SEL _cmd, NSString *nsstring___AtomicCopyTest) {
    // 原子性copy属性的set方法使用objc_setProperty运行时函数,该函数负责调用copy和release,同时保证原子性。
    // 第四个参数为true表示需要原子性操作,第五个参数为true表示需要复制。
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___AtomicCopyTest), (id)nsstring___AtomicCopyTest, true, true);
}

// 结束propertyTest类的实现
@end
  • strong、copy、retain 的get方法是根据地址偏移找到对应的实例变量直接返回
  • atomic的get方法用到了objc_getProperty方法
  • strong 的set根据地址偏移找到对应的实例变量直接赋值
  • retain、copy、atomic 的set方法 用到了objc_setProperty

objc_setProperty

void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
    objc_setProperty_non_gc(self, _cmd, offset, newValue, atomic, shouldCopy);
}

这个方法有六个参数,self指对象自身;_cmd是方法选择器;ptrdiff_t 是一个标准整型,用于表示指针之间的差值。offset是实例变量的偏移量;newValue用来赋值给属性的新值;atomic表示是否需要原子性操作;shouldCopy 代表是否应该复制属性值,如果为1,则执行拷贝操作。

objc_setProperty调用了objc_setProperty_non_gc

void objc_setProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
  //判断是否需要进行普通拷贝。如果 shouldCopy 非零且不等于 MUTABLE_COPY,那么需要拷贝 newValue。
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
  // 判断是否需要进行可变拷贝(深拷贝)。如果 shouldCopy 等于 MUTABLE_COPY,那么需要可变拷贝 newValue。
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}

这里调用了 reallySetProperty, 此函数会根据提供的参数执行属性值的设置,包括处理原子性操作和拷贝行为。该函数的最后两个参数由shouldCopy决定。

  • 如果shouldCopy=0 那么 copy = NO,mutableCopy = NO

  • 如果shouldCopy=1 那么 copy = YES,mutableCopy = NO

  • 如果shouldCopy=MUTABLE_COPY 那么 copy = NO,mutableCopy = YES

reallySetProperty

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    // 检查 offset 是否为零。如果是零,则这意味着我们正在设置类而不是属性。
    if (offset == 0) {
        // 使用 object_setClass 函数来改变对象的类。
        object_setClass(self, newValue);
        return;
    }
    
    // 用于存储旧属性值的变量。
    id oldValue;
    
    // 计算属性槽的地址,这是对象中存储属性值的位置。
    id *slot = (id*) ((char*)self + offset);
    
    // 如果 copy 为真,则调用 copyWithZone: 方法创建一个新对象作为属性值。
    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } 
    // 如果 mutableCopy 为真,则调用 mutableCopyWithZone: 方法创建一个可变拷贝作为属性值。
    else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } 
    // 否则,保留 newValue 的引用计数,除非它已经与现有的属性值相同。
    else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }
    
    // 如果设置是非原子的,直接替换旧值。
    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } 
    // 如果设置是原子的,使用 spinlock 锁来保证线程安全。
    else {
        // 获取指向 slot 对应的 spinlock 的引用。
        spinlock_t& slotlock = PropertyLocks[slot];
        // 锁定 spinlock。
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;
        // 解锁 spinlock。
        slotlock.unlock();
    }
    
    // 释放旧属性值的引用计数。
    objc_release(oldValue);
}
  • 如果 offset 是 0,说明我们不是在设置普通的属性,而是在尝试更改对象的类,这时调用 object_setClass 函数。
  • 计算属性值在对象内存布局中的具体位置
  • 如果 copy 为真,则调用 copyWithZone: 方法进行不可变拷贝。
  • 如果 mutableCopy 为真,则调用 mutableCopyWithZone: 方法创建一个可变拷贝。
  • 如果两者都为假,检查新值是否与旧值相同;如果不相同,保留新值的引用计数。
  • 如果 atomic 为假,直接用新值替换旧值。
  • 如果 atomic 为真,使用 spinlock 锁来确保在多线程环境中设置属性的操作是线程安全的。
  • 最后,释放旧属性值的引用计数,以便正确管理内存。

从上面的源码可以得出:

copy关键字的实现最终还是调用了copyWithZone方法,用copy修饰的属性,赋值的时候,不管本身是否是可变对象,赋值给属性之后都是不可变对象。

如果copy和mutableCopy都为NO,会调用 objc_retain

retain调用set方法

retain调用set方法时调用的还是reallySetProperty函数,传入的参数为atomic参数为NO,copy参数为NO,mutableCopy也为NO, 最终的实现相当于是:

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{

    id oldValue;
    id *slot = (id*) ((char*)self + offset);
    if (*slot == newValue) return;
    newValue = objc_retain(newValue);

    oldValue = *slot;
    *slot = newValue;
    
    objc_release(oldValue);
}

copy调用set方法

copy调用的还是reallySetProperty函数,传入的参数为atomic参数为NO,copy参数为YES,mutableCopy为NO, 最终的实现相当于是:

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    id oldValue;
    //取出变量
    id *slot = (id*) ((char*)self + offset);
        newValue = [newValue copyWithZone:nil];
           oldValue = *slot;
        *slot = newValue;
    objc_release(oldValue);
}

atomic和copy调用set方法

调用的还是reallySetProperty函数,传入的参数为atomic参数为YES,copy参数为YES,mutableCopy为NO, 最终的实现相当于是:

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    id oldValue;
   
    id *slot = (id*) ((char*)self + offset);


        newValue = [newValue copyWithZone:nil];
   

        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
  
    objc_release(oldValue);
}

objc_getProperty

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic)
{
    return objc_getProperty_non_gc(self, _cmd, offset, atomic);
}

这里跟objc_setProperty方法一样都相当于接口函数,调用更深层的objc_getProperty_non_gc方法

id objc_getProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic)
{
    // 如果 offset 是 0,这通常意味着我们试图获取的是类本身,而非普通属性。
    if (offset == 0) {
        // 调用 object_getClass 函数来获取对象的类。
        return object_getClass(self);
    }

    // 计算属性值所在的内存地址。
    id *slot = (id*) ((char*)self + offset);

    // 如果设置为非原子操作,直接返回属性槽中的值,不涉及引用计数操作。
    if (!atomic) return *slot;

    // 原子操作,需要锁定以防止并发修改。
    // 获取指向属性槽对应的 spinlock 锁。
    spinlock_t& slotlock = PropertyLocks[slot];
    // 锁定 spinlock。
    slotlock.lock();

    // 读取属性槽中的值并增加其引用计数,确保即使在读取后被其他线程释放也能安全访问。
    id value = objc_retain(*slot);

    // 解锁 spinlock。
    slotlock.unlock();

    // 为了性能考虑,我们安全地在 spinlock 外部执行 autorelease,这样可以避免锁的竞争。
    // objc_autoreleaseReturnValue 将值放入 autorelease pool 中,当 pool 清理时会自动释放。
    return objc_autoreleaseReturnValue(value);
}
  • 如果 offset 是 0,这表明我们可能试图获取对象的类,而不是属性值。此时调用 object_getClass 函数来获取对象的类。
  • 计算属性值在对象内存布局中的具体位置
  • 如果 atomicNO,那么函数直接返回属性槽中的值,没有引用计数的增加或减少操作。
  • 获取指向属性槽对应的 spinlock 锁,锁定 spinlock
  • 读取属性槽中的值,并通过 objc_retain 函数增加其引用计数
  • 解锁 spinlock,结束对属性值修改的锁定
  • 使用 objc_autoreleaseReturnValue 函数将 value 放入 autorelease pool 中,这会在适当的时机自动释放 value 的引用,从而避免内存泄漏

可以看到atomic为yes的时候,对应属性的set、get方法用到了spinlock_t锁,保证了set、get方法的线程安全,但是并不能保证其他操作的线程安全,比如对属性进行进行release操作。

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值