返回首页

Research on the underlying implementation of iOS copy modifier

Take a look at the underlying implementation of the copy, strong, retain, atmoic, and nonatomic modifiers

Regarding the functions and differences of the commonly used @property attribute modifiers such as copy, strong, retain, etc., there are many online discussions, but I haven’t found one that explains clearly. Some of them are even wrong, or others have made it clear, but I don’t understand it properly. As the saying goes, 源码面前,了无秘密, just look at the source code to see how it is implemented. Once you understand the source code, you will never deviate from its original meaning. Here we mainly look at the implementation of copy.

1. Take a look at the strong, retain, copy, atomic, nonatomic c++ source code

@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

Use clang -rewrite-objc propertyTest.m to generate c++ source code as shown below. The comments are very clear.

// @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

####Summary: The get methods of strong, copy, and retain find the corresponding instance variables based on the address offset and return them directly. The atomic get method uses the objc_getProperty method

strong set finds the corresponding instance variable based on the address offset and assigns it directly The retain, copy, and atomic set methods use objc_setProperty

2. How are objc_getProperty and objc_setProperty implemented?

objc_getProperty and objc_setProperty were not found in the generated .cpp file. Found it in the source code of 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);
}

Let’s look at the objc_setProperty method first. This method has a total of 6 values. The first two parameters are common to the oc method. If you are not sure, you can look at the runtime. There is already a lot of information on the Internet. The third parameter offset is the offset relative to self, which is used to find the corresponding member variable. The last three parameters are id newValue, BOOL atomic, and signed char. shouldCopy, the parameter naming is very clear, no need to explain anymore, objc_setProperty calls objc_setProperty_non_gc, the parameters are the same. Take a look at the implementation of 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);
}

You can see that the function objc_setProperty_non_gc parameter calls the reallySetProperty function. As the last parameter, the reallySetProperty function has a total of 7 parameters. The first 5 parameters correspond to the first 5 parameters of objc_setProperty_non_gc. The values of the last two parameters are determined by shouldCopy. The above code shows that the value of shouldCopy here is 0 or 1. Let’s consider these two situations first:

If shouldCopy=0 then copy = NO, mutableCopy = NO If shouldCopy=1 then copy = YES, mutableCopy = NO

Let’s look at the implementation of 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);
}

If shouldCopy=0 then copy = NO, mutableCopy = NO If shouldCopy=1 then 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方法

If both copy and mutableCopy are NO, it will be called objc_retain

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

We can also see from the above that when the attribute modified with copy is assigned, regardless of whether it is a mutable object or not, it will be an immutable object after being assigned to the attribute.

Let’s take a look at the relevant code of atomic

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

You can see that the spinlock_t lock is used when atomic is yes.

The retain set method is called as follows

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

According to the above function calling process, we can get: reallySetProperty function The atomic parameter is NO, the copy parameter is NO, and mutableCopy is also NO. The final implementation is equivalent to

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

The final implementation of the copy set method is as follows:

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

The atomic copy set method is as follows

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 Take a look at the implementation of objc_getProperty

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

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

From the above, you can see that when atomic is yes, the set and get methods of the corresponding attributes use the spinlock_t lock, which ensures the thread safety of the set and get methods, but does not guarantee the thread safety of other operations, such as release operations on attributes.

FAQ

读完之后,下一步看什么

如果还想继续了解,可以从下面几个方向接着读。

Related

继续阅读

这里整理了同分类、同标签或同类问题的文章。