OC底层 - nonatomic 和 atomic 分析
区别原理
nonatomic 是非原子性,不会保证get/set操作完整性。
atomic是原子性,会保证get/set操作完整性。
具体区别,我举个例子:比如一个name属性,如果设置为noatomic,此时2条线程A、B同时对name进行set,一条线程C对name进行get,那么get到的数据,不一定会是什么结果,可能是原始值,也可能是线程A或者线程B设置的值。
如果用atomic来修饰name,那么不会造成数据读取的错乱。因为atomic会在get和set方法加上互斥锁spinlock_t(注意“using spinlock_t = mutex_tt
atomic一定能保证线程安全吗?
atomic不能保证线程安全。只能保证get/set的方法线程安全。比如再多一条线程进行name release操作,或者其他操作。就会造成问题了,甚至crash。
探索底层编译
@interface MyTextObject : NSObject
@property (nonatomic, copy) NSString *name1;
@property (copy) NSString *name2;
@property (atomic, copy) NSString *name3;
@property NSString *name4;
@end
以上4个属性在底层编译的区别:
通过clang命令,生成MyTextObject.cpp
// @implementation MyTextObject
static NSString * _Nonnull _I_MyTextObject_name1(MyTextObject * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_MyTextObject$_name1)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_MyTextObject_setName1_(MyTextObject * self, SEL _cmd, NSString * _Nonnull name1) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct MyTextObject, _name1), (id)name1, 0, 1); }
extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);
static NSString * _Nonnull _I_MyTextObject_name2(MyTextObject * self, SEL _cmd) { typedef NSString * _Nonnull _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct MyTextObject, _name2), 1); }
static void _I_MyTextObject_setName2_(MyTextObject * self, SEL _cmd, NSString * _Nonnull name2) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct MyTextObject, _name2), (id)name2, 1, 1); }
static NSString * _Nonnull _I_MyTextObject_name3(MyTextObject * self, SEL _cmd) { typedef NSString * _Nonnull _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct MyTextObject, _name3), 1); }
static void _I_MyTextObject_setName3_(MyTextObject * self, SEL _cmd, NSString * _Nonnull name3) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct MyTextObject, _name3), (id)name3, 1, 1); }
static NSString * _Nonnull _I_MyTextObject_name4(MyTextObject * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_MyTextObject$_name4)); }
static void _I_MyTextObject_setName4_(MyTextObject * self, SEL _cmd, NSString * _Nonnull name4) { (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_MyTextObject$_name4)) = name4; }
// @end
可以看到name1、name2、name3的set方法里面都执行了objc_setProperty方法,name4的set方法是直接通过内存首地址进行偏移来设置值的。
我们在来分析一下objc_setProperty方法
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) {
///如果是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);
}
void objc_setProperty(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);
}
其中有一个atomic的参数,那么就证明name2,默认是atomic。
另外为什么name4是直接通过内存地址偏移来赋值呢,通过探索llvm
llvm::FunctionCallee getOptimizedSetPropertyFn(bool atomic, bool copy) {
CodeGen::CodeGenTypes &Types = CGM.getTypes();
ASTContext &Ctx = CGM.getContext();
// void objc_setProperty_atomic(id self, SEL _cmd,
// id newValue, ptrdiff_t offset);
// void objc_setProperty_nonatomic(id self, SEL _cmd,
// id newValue, ptrdiff_t offset);
// void objc_setProperty_atomic_copy(id self, SEL _cmd,
// id newValue, ptrdiff_t offset);
// void objc_setProperty_nonatomic_copy(id self, SEL _cmd,
// id newValue, ptrdiff_t offset);
SmallVector<CanQualType,4> Params;
CanQualType IdType = Ctx.getCanonicalParamType(Ctx.getObjCIdType());
CanQualType SelType = Ctx.getCanonicalParamType(Ctx.getObjCSelType());
Params.push_back(IdType);
Params.push_back(SelType);
Params.push_back(IdType);
Params.push_back(Ctx.getPointerDiffType()->getCanonicalTypeUnqualified());
llvm::FunctionType *FTy =
Types.GetFunctionType(
Types.arrangeBuiltinFunctionDeclaration(Ctx.VoidTy, Params));
const char *name;
if (atomic && copy)
name = "objc_setProperty_atomic_copy";
else if (atomic && !copy)
name = "objc_setProperty_atomic";
else if (!atomic && copy)
name = "objc_setProperty_nonatomic_copy";
else
name = "objc_setProperty_nonatomic";
return CGM.CreateRuntimeFunction(FTy, name);
}
可以发现会判断atomic和copy,然后通过runtime自动生成objc_setProperty相关函数进行处理。而name4再赋值的时候走的是objc_strong的函数。如果用weak修饰,那么走的是objc_weak的流程。
这些都是llvm在编译的时候进行的优化处理。