atomic
和nonatomic
用于在定義 property 時(shí)指明其原子性:
-
atomic
表示是原子性的蔫慧,調(diào)用該 property 的 getter 和 setter 會(huì)保證對(duì)象的完整性蜂挪。多線程操作時(shí)滓走,任何調(diào)用都可以得到一個(gè)完整的對(duì)象忙上,因此速度較慢。 -
nonatomic
表示是非原子性的闲坎,調(diào)用該 property 的 getter 和 setter 不保證對(duì)象的完整性疫粥。多個(gè)線程對(duì)它進(jìn)行訪問(wèn),它可能會(huì)返回未初始化的對(duì)象腰懂,因此速度較快梗逮。
注意,以上討論僅對(duì)編譯器自動(dòng)生成的 getter/setter 有效绣溜。如果你自己實(shí)現(xiàn)了 getter/setter慷彤,無(wú)論使用 atomic/nonatomic,property 的原子性都將取決于你自己的實(shí)現(xiàn)怖喻。
使用 atomic 修飾的 property 是如何保證原子性的底哗?
對(duì)于任何沒(méi)有手動(dòng)實(shí)現(xiàn)的屬性,編譯器都會(huì)生成一個(gè)objc_getProperty_non_gc
的函數(shù)作為 getter锚沸,同時(shí)生成一個(gè)objc_setProperty_non_gc
的函數(shù)作為 setter跋选。
從objc_getProperty_non_gc
函數(shù)的源代碼可以看到[1]:
// objc-accessors.mm
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);
}
而objc_setProperty_non_gc
函數(shù)最終將調(diào)到reallySetProperty
函數(shù)[2]:
// objc-accessors.mm
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);
}
其中處理 atomic 的分支里,getter/setter 都使用了在PropertyLocks
中的128個(gè)自旋鎖(Spinlock)中的1個(gè)來(lái)給操作上鎖哗蜈。這是一種務(wù)實(shí)和快速的方式前标,最糟糕的情況下,如果遇到了哈希碰撞距潘,那么 setter 需要等待另一個(gè)和它無(wú)關(guān)的 setter 完成之后再進(jìn)行工作[3]炼列。
使用 atomic 修飾的 property 是線程安全的嗎?
考慮對(duì)一個(gè) nonatomic 的整形變量integer
進(jìn)行加1操作音比,可分為三個(gè)步驟:首先從內(nèi)存中取出原始值俭尖,然后加1,最后將修改后的值寫(xiě)回內(nèi)存洞翩。
現(xiàn)在有A
和B
兩個(gè)并發(fā)線程稽犁,分別對(duì)integer
進(jìn)行加1操作缭付,那么問(wèn)題來(lái)了:兩個(gè)線程都從變量中取出了原始值循未,假設(shè)17
秫舌,然后A
將值加1足陨,然后將結(jié)果18
寫(xiě)回內(nèi)存墨缘;同時(shí)B
也將值加1后將結(jié)果18
寫(xiě)回內(nèi)存镊讼。此時(shí)integer
被加1了兩次平夜,但最終值卻是18
。
這個(gè)問(wèn)題稱為競(jìng)態(tài)條件,atomic 通過(guò)在讀寫(xiě)時(shí)加入自旋鎖段直,能保證對(duì)象的完整性,保護(hù)你免于在 setter 中遭遇到競(jìng)態(tài)條件的困擾决侈。
但這并不代表使用 atomic 就是線程安全的颜及。考慮三個(gè)并發(fā)線程A
讯蒲,B
和C
,其中A
和B
對(duì)變量integer
進(jìn)行加1操作赁酝,C
從變量integer
中讀取數(shù)據(jù)酌呆。因?yàn)槿齻€(gè)線程是并發(fā)的搔耕,所以C
讀取數(shù)據(jù)的時(shí)機(jī)可能在其他兩個(gè)寫(xiě)入數(shù)據(jù)的線程A
、B
之前,或在他們兩個(gè)之間梨睁,也可能在他們兩個(gè)之后坡贺,導(dǎo)致C
讀到的數(shù)據(jù)可能是17
遍坟、18
或19
。
如何保證 property 的線程安全性政鼠?
沒(méi)有銀彈公般,具體問(wèn)題具體分析胡桨。@synchronized
和dispatch_queue
都是可選的解決方案,但也都各有利弊刽虹。這里就不展開(kāi)說(shuō)明了涌哲,那將又是一大塊篇幅尚镰。