iOS copy 修饰符底层实现探究

看一看copy、strong、retain、atmoic、nonatomic修饰符的底层实现

Posted by Dan on April 8, 2018

对于经常用的@property属性修饰符 copy、strong、retain等,到底有什么作用和不同,网上说的很多,但是没找到一个说的清楚的,有的说的甚至是错的,或者是别人说清楚了,我没有理解到位,正所谓源码面前,了无秘密,直接看源码怎么实现的,理解了源码,万变不离其宗。这里主要先看下copy的实现。

1. 看看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++源码看如下,注释写的很清楚了,

// @implementation propertyTest

//nsstring___StrongTest get方法
static NSString * _I_propertyTest_nsstring___StrongTest(propertyTest * self, SEL _cmd)
{
    //strong get 根据地址偏移找到对应的实例变量  直接返回
    return (*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___StrongTest));
}

//nsstring___StrongTest set方法
static void _I_propertyTest_setNsstring___StrongTest_(propertyTest * self, SEL _cmd, NSString *nsstring___StrongTest)
{
    //strong set 根据地址偏移找到对应的实例变量  直接赋值
    (*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___StrongTest)) = nsstring___StrongTest;
}

 //////===============
//nsstring___RetainTest get方法
static NSString * _I_propertyTest_nsstring___RetainTest(propertyTest * self, SEL _cmd)
{
    //retain get 根据地址偏移找到对应的实例变量  直接返回
    return (*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___RetainTest));
}

//由于下面要用objc_setProperty,声明objc_setProperty在外部文件定义
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_propertyTest_setNsstring___RetainTest_(propertyTest * self, SEL _cmd, NSString *nsstring___RetainTest)
{
    //retain set方法 ,和strong不一样,这里用到了objc_setProperty
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___RetainTest), (id)nsstring___RetainTest, 0, 0);
}

 //////===============
//nsstring___CopyTest get方法
static NSString * _I_propertyTest_nsstring___CopyTest(propertyTest * self, SEL _cmd)
{
    //copy get 根据地址偏移找到对应的实例变量  直接返回
    return (*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___CopyTest));
}

//nsstring___CopyTest set方法
static void _I_propertyTest_setNsstring___CopyTest_(propertyTest * self, SEL _cmd, NSString *nsstring___CopyTest)
{
    //copy set方法 ,和strong不一样,和retain一样, 用到了objc_setProperty
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___CopyTest), (id)nsstring___CopyTest, 0, 1);
}

 //////===============
//由于下面要用objc_getProperty,声明objc_getProperty在外部文件定义
extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);

//nsstring___AtomicCopyTest get方法
static NSString * _I_propertyTest_nsstring___AtomicCopyTest(propertyTest * self, SEL _cmd)
{
    //atomic get方法  用到了objc_getProperty
    typedef NSString * _TYPE;
    return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___AtomicCopyTest), 1);
}

//nsstring___AtomicCopyTest set方法
static void _I_propertyTest_setNsstring___AtomicCopyTest_(propertyTest * self, SEL _cmd, NSString *nsstring___AtomicCopyTest)
{
     //atomic set方法 同样用到了 objc_setProperty
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___AtomicCopyTest), (id)nsstring___AtomicCopyTest, 1, 1);
}
// @end

####总结: strong、copy、retain 的get方法是根据地址偏移找到对应的实例变量 直接返回 atomic get方法用到了objc_getProperty方法

strong set 根据地址偏移找到对应的实例变量 直接赋值 retain、copy、 atomic set方法 用到了objc_setProperty

2.objc_getProperty、objc_setProperty是怎么实现的呢?

objc_getProperty、objc_setProperty在生成的.cpp文件中没有找到, 在objc的源码中找到了

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

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);
}

先来看 objc_setProperty方法,这个方法一共有6个值,前两个参数是oc方法都有的,不清楚的可以去看下runtime,网上已经有很多资料了,第三个参数offset,是相对于self的偏移,用来找对应的成员变量的,最后三个参数依次是id newValue, BOOL atomic, signed char shouldCopy,参数命名很清楚,不用再解释了,objc_setProperty调用了objc_setProperty_non_gc,参数一样。 看下objc_setProperty_non_gc的实现

void objc_setProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}

可以看到函数objc_setProperty_non_gc参数调用的是reallySetProperty函数,做后一个参数,reallySetProperty函数一共有7个参数,前5个参数分别对应objc_setProperty_non_gc的前5个参数,后面两个参数的值由shouldCopy决定,上面的代码看到我们这里的shouldCopy的值是0或1,这里先考虑这两种情况:

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

下面看下reallySetProperty 的实现

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

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

if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

上面就是copy关键字的实现了,copy == YES,调用[newValue copyWithZone:nil],返回的是不可变对应,最终还是调用了copyWithZone方法

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

objc_retain 实现如下
id objc_retain(id obj) { return [obj retain]; }

从上面我们也能看到,用copy修饰的属性,赋值的时候,不管本身是否是可变对象,赋值给属性之后都是不可变对象。

下面看下atomic的相关代码

 if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

可以看到atomic为yes的时候用到了spinlock_t锁

retain set方法的调用如下

 objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___RetainTest), (id)nsstring___RetainTest, 0, 0);

根据上面的函数调用流程可以得到: 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方法 最终实现如下:

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方法如下

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);
}

3 看下objc_getProperty的实现

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

objc_getProperty_non_gc的实现

id objc_getProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) {
        return object_getClass(self);
    }

    // Retain release world
    id *slot = (id*) ((char*)self + offset);
    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);
}

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