对于经常用的@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操作。