在iOS中,atomic表示一個類的屬性getter/setter具有原子性。那么iOS底層是如何保證這種原子性的呢?
我們有一個類A,它有一個屬性X具有atomic:
@property (atomic, strong) X *x;
當我們為這個這個X屬性賦值的時候:-[A setX:]的匯編代碼如下:
1 0x10e320f00 <+0>: push rbp 2 0x10e320f01 <+1>: mov rbp, rsp 3 0x10e320f04 <+4>: sub rsp, 0x20 4 0x10e320f08 <+8>: mov qword ptr [rbp - 0x8], rdi 5 0x10e320f0c <+12>: mov qword ptr [rbp - 0x10], rsi 6 0x10e320f10 <+16>: mov qword ptr [rbp - 0x18], rdx 7 0x10e320f14 <+20>: mov rsi, qword ptr [rbp - 0x10] 8 0x10e320f18 <+24>: mov rax, qword ptr [rbp - 0x8] 9 0x10e320f1c <+28>: mov rcx, qword ptr [rbp - 0x18] 10 0x10e320f20 <+32>: mov rdi, rax 11 0x10e320f23 <+35>: mov rdx, rcx 12 0x10e320f26 <+38>: mov ecx, 0x18 13 0x10e320f2b <+43>: call 0x10e321312 ; symbol stub for: objc_setProperty_atomic 注意這行 14 0x10e320f30 <+48>: add rsp, 0x20 15 0x10e320f34 <+52>: pop rbp 16 0x10e320f35 <+53>: ret
上面的匯編代碼第13行顯示,setX里面調用了objc rumtime函數objc_setProperty_atomic函數。打開objc_setProperty_atomic源碼:
void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset) { reallySetProperty(self, _cmd, newValue, offset, true, false, false); }
1 static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) 2 { 3 if (offset == 0) { 4 object_setClass(self, newValue); 5 return; 6 } 7 8 id oldValue; 9 id *slot = (id*) ((char*)self + offset); 10 11 if (copy) { 12 newValue = [newValue copyWithZone:NULL]; 13 } else if (mutableCopy) { 14 newValue = [newValue mutableCopyWithZone:NULL]; 15 } else { 16 if (*slot == newValue) return; 17 newValue = objc_retain(newValue); 18 } 19 20 if (!atomic) { 21 oldValue = *slot; 22 *slot = newValue; 23 } else { //如果是atomic屬性修飾,會加上自旋鎖 24 spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)]; 25 _spin_lock(slotlock); 26 oldValue = *slot; 27 *slot = newValue; 28 _spin_unlock(slotlock); 29 } 30 31 objc_release(oldValue); 32 }
通過源碼發現,objc_setProperty_atomic函數調用了reallySetProperty函數,而reallySetProperty函數的23行,如果是atomic屬性修飾的,為這個變量賦值會加上自旋鎖。
下面我們看下訪問X這個變量時的getter方法:-[A x],匯編代碼如下:
1 -> 0x10c09fed0 <+0>: push rbp 2 0x10c09fed1 <+1>: mov rbp, rsp 3 0x10c09fed4 <+4>: mov qword ptr [rbp - 0x8], rdi 4 0x10c09fed8 <+8>: mov qword ptr [rbp - 0x10], rsi 5 0x10c09fedc <+12>: mov rsi, qword ptr [rbp - 0x10] 6 0x10c09fee0 <+16>: mov rdi, qword ptr [rbp - 0x8] 7 0x10c09fee4 <+20>: mov edx, 0x18 8 0x10c09fee9 <+25>: mov ecx, 0x1 9 0x10c09feee <+30>: pop rbp 10 0x10c09feef <+31>: jmp 0x10c0a02fa ; symbol stub for: objc_getProperty 寄存器ecx為1,表示傳遞給objc_getProperty的第4個參數為YES,而這個參數剛好表示是否是atomic的
上面匯編代碼第10行,getter方法訪問了objc運行時的objc_getProperty方法,我們查看源碼:
1 id 2 objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) 3 { 4 return objc_getProperty_non_gc(self, _cmd, offset, atomic); 5 }
1 id objc_getProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) { 2 if (offset == 0) { 3 return object_getClass(self); 4 } 5 6 // Retain release world 7 id *slot = (id*) ((char*)self + offset); 8 if (!atomic) return *slot; 9 10 // Atomic retain release world 11 // Atomic屬性訪問會加鎖 12 spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)]; 13 _spin_lock(slotlock); 14 id value = objc_retain(*slot); 15 _spin_unlock(slotlock); 16 17 // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock. 18 return objc_autoreleaseReturnValue(value); 19 }
從上面getter方法匯編碼可以知道傳遞給objc_getProperty第四個參數為YES,也就是atomic參數為YES。而objc_getProperty調用了objc_getProperty_non_gc函數,這個函數里面第12行,如果是atomic屬性,訪問的時候會加鎖。
測試的時候發現,如果atomic修飾的是諸如整型這些簡單類型的變量,setter/getter時都是使用對象首地址+偏移量完成,而不會調用objc_getProperty函數和objc_setProperty_atomic函數。可能是因為對這些變量的操作,本身就是原子性的。
