copy
copy末誓,引用計數(shù)會+1.然而設(shè)置新值并不會保留舊值里初,而是將其拷貝。
NSString對象為什么盡量用copy來修飾?
我們通過代碼查看copy和strong修飾的區(qū)別
#import "ViewController.h"
@interface ViewController ()
// copy字符串
@property (nonatomic, copy) NSString *myCopyStr;
// 強引用str
@property (nonatomic, strong) NSString *strongStr;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableString *testStr = [[NSMutableString alloc] initWithString:@"測試"];
// NSString *testStr = @"測試";
self.myCopyStr = testStr;
self.strongStr = testStr;
NSLog(@"testStr : 指針地址 %p", testStr);
NSLog(@"myCopyStr : 指針地址 %p", _myCopyStr);
NSLog(@"strongStr : 指針地址 %p", _strongStr);
NSLog(@"----------------------------------------------------");
[testStr appendString:@"sss"];
NSLog(@"testStr : 指針地址 %p ,內(nèi)容%@", testStr , testStr);
NSLog(@"myCopyStr : 指針地址 %p ,內(nèi)容%@", _myCopyStr,_myCopyStr);
NSLog(@"strongStr : 指針地址 %p ,內(nèi)容%@", _strongStr,_strongStr);
}
打印結(jié)果
2018-07-17 14:52:35.066351+0800 property[51796:5806435] testStr : 指針地址 0x600000253c20
2018-07-17 14:52:35.066535+0800 property[51796:5806435] myCopyStr : 指針地址 0x600000037d40
2018-07-17 14:52:35.066662+0800 property[51796:5806435] strongStr : 指針地址 0x600000253c20
2018-07-17 14:52:35.066793+0800 property[51796:5806435] ----------------------------------------------------
2018-07-17 14:52:35.067159+0800 property[51796:5806435] testStr : 指針地址 0x600000253c20 ,內(nèi)容測試sss
2018-07-17 14:52:35.067315+0800 property[51796:5806435] myCopyStr : 指針地址 0x600000037d40 ,內(nèi)容測試
2018-07-17 14:52:35.067464+0800 property[51796:5806435] strongStr : 指針地址 0x600000253c20 ,內(nèi)容測試sss
結(jié)論:我們可以看出: 通過strong修飾的字符串医清,strongStr和testStr指向同一塊內(nèi)存地址袱衷,testStr修改對應(yīng)的值敬察,也會相對應(yīng)修改了strong修飾的字符串strongStr薪捍。而copy是重新拷貝了一份,申請了一塊獨立的內(nèi)存抽兆,無法被影響。
接下來我們看看源碼
id
object_copy(id oldObj, size_t extraBytes)
{
return _object_copyFromZone(oldObj, extraBytes, malloc_default_zone());
}
static id
_object_copyFromZone(id oldObj, size_t extraBytes, void *zone)
{
if (oldObj->isTaggedPointerOrNil()) return oldObj;
// fixme this doesn't handle C++ ivars correctly (#4619414)
Class cls = oldObj->ISA(/*authenticated*/true);
size_t size;
id obj = _class_createInstanceFromZone(cls, extraBytes, zone,
OBJECT_CONSTRUCT_NONE, false, &size);
if (!obj) return nil;
// Copy everything except the isa, which was already set above.
uint8_t *copyDst = (uint8_t *)obj + sizeof(Class);
uint8_t *copySrc = (uint8_t *)oldObj + sizeof(Class);
size_t copySize = size - sizeof(Class);
memmove(copyDst, copySrc, copySize); // 拷貝對象的內(nèi)存數(shù)據(jù)
fixupCopiedIvars(obj, oldObj); // 處理對象的ARC
return obj;
}
最終調(diào)用的源碼
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);
}
copy和strong修飾的屬性在底層編譯的不一致族淮,主要還是llvm中對其進(jìn)行了不同的處理的結(jié)果辫红。copy的賦值是通過objc_setProperty,而strong的賦值時通過self + 內(nèi)存平移(即將指針通過平移移至name所在的位置祝辣,然后賦值)贴妻,然后還原成 strong類型
strong & copy 在底層調(diào)用objc_storeStrong,本質(zhì)是新值retain蝙斜,舊值release
strong
strong: 強引用名惩,會使引用計數(shù)+1.setter方法賦值時,會保留新值孕荠,并釋放舊值娩鹉,然后在將新值設(shè)置。
weak
弱引用稚伍,引用計數(shù)不增加弯予。setter方法賦值時,即不保留新值个曙,也不釋放舊值锈嫩。當(dāng)對象被銷毀時,屬性值會自動置nil垦搬。
assign
用于基本數(shù)據(jù)類型呼寸。CGFloat,NSInteger等
unsafe_unretained
作用于OC對象,引用計數(shù)不增加猴贰。當(dāng)對象被銷毀時对雪,屬性值不會清空,正如字面上的意思糟趾,不安全慌植。
retain
1.判斷是否為nonpointer ->散列表
2.操作引用計數(shù)
a: 如果不是 nonpointer -> 散列表
spinlock_t slock; 開解鎖
RefcountMap refcnts; 引用計數(shù)表
weak_table_t weak_table; 弱應(yīng)用表
b: 是否正在釋放 如果正在釋放就不需要操作引用計數(shù)了
c: extra_rc + 1 滿了 - 散列表
d: carry 滿 extra_rc 滿/2 -> extra_rc 滿/2 -> 散列表 (開鎖關(guān)鎖)
retain源碼
ALWAYS_INLINE id
objc_object::rootRetain()
{
return rootRetain(false, RRVariant::Fast);
}
ALWAYS_INLINE bool
objc_object::rootTryRetain()
{
return rootRetain(true, RRVariant::Fast) ? true : false;
}
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
oldisa = LoadExclusive(&isa.bits);
if (variant == RRVariant::FastOrMsgSend) {
// These checks are only meaningful for objc_retain()
// They are here so that we avoid a re-load of the isa.
if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
ClearExclusive(&isa.bits);
if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
return swiftRetain.load(memory_order_relaxed)((id)this);
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}
}
if (slowpath(!oldisa.nonpointer)) {
// a Class is a Class forever, so we can perform this check once
// outside of the CAS loop
if (oldisa.getDecodedClass(false)->isMetaClass()) {
ClearExclusive(&isa.bits);
return (id)this;
}
}
do {
transcribeToSideTable = false;
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain(sideTableLocked);
}
// don't check newisa.fast_rr; we already called any RR overrides
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
if (slowpath(tryRetain)) {
return nil;
} else {
return (id)this;
}
}
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);
// extra_rc++
//蘋果為什么要這么設(shè)計?义郑?蝶柿?
//因為存儲到散列表中需要開鎖解鎖操作,所以這里只放一半并且蘋果也解釋了 slowpath是很小的可能性
// x 很可能不為 0非驮,希望編譯器進(jìn)行優(yōu)化
#define fastpath(x) (__builtin_expect(bool(x), 1))
// x 很可能為 0交汤,希望編譯器進(jìn)行優(yōu)化
#define slowpath(x) (__builtin_expect(bool(x), 0))
if (slowpath(carry)) { //操作散列表 extra_rc 如果滿了 放一半到散列表里面 newisa.extra_rc = RC_HALF;
// newisa.extra_rc++ overflowed
if (variant != RRVariant::Full) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
if (variant == RRVariant::Full) {
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
} else {
ASSERT(!transcribeToSideTable);
ASSERT(!sideTableLocked);
}
return (id)this;
}
NONPOINTER_ISA
蘋果將 isa 設(shè)計成了聯(lián)合體,在 isa 中存儲了與該對象相關(guān)的一些內(nèi)存的信息,原因也如上面所說芙扎,并不需要 64 個二進(jìn)制位全部都用來存儲指針星岗。
來看一下 isa 的結(jié)構(gòu):
// x86_64 架構(gòu)
struct {
uintptr_t nonpointer : 1; // 0:普通指針,1:優(yōu)化過戒洼,使用位域存儲更多信息
uintptr_t has_assoc : 1; // 對象是否含有或曾經(jīng)含有關(guān)聯(lián)引用
uintptr_t has_cxx_dtor : 1; // 表示是否有C++析構(gòu)函數(shù)或OC的dealloc
uintptr_t shiftcls : 44; // 存放著 Class俏橘、Meta-Class 對象的內(nèi)存地址信息
uintptr_t magic : 6; // 用于在調(diào)試時分辨對象是否未完成初始化
uintptr_t weakly_referenced : 1; // 是否被弱引用指向
uintptr_t deallocating : 1; // 對象是否正在釋放
uintptr_t has_sidetable_rc : 1; // 是否需要使用 sidetable 來存儲引用計數(shù)
uintptr_t extra_rc : 8; // 引用計數(shù)能夠用 8 個二進(jìn)制位存儲時,直接存儲在這里
};
// arm64 架構(gòu)
struct {
uintptr_t nonpointer : 1; // 0:普通指針圈浇,1:優(yōu)化過寥掐,使用位域存儲更多信息
uintptr_t has_assoc : 1; // 對象是否含有或曾經(jīng)含有關(guān)聯(lián)引用
uintptr_t has_cxx_dtor : 1; // 表示是否有C++析構(gòu)函數(shù)或OC的dealloc
uintptr_t shiftcls : 33; // 存放著 Class、Meta-Class 對象的內(nèi)存地址信息
uintptr_t magic : 6; // 用于在調(diào)試時分辨對象是否未完成初始化
uintptr_t weakly_referenced : 1; // 是否被弱引用指向
uintptr_t deallocating : 1; // 對象是否正在釋放
uintptr_t has_sidetable_rc : 1; // 是否需要使用 sidetable 來存儲引用計數(shù)
uintptr_t extra_rc : 19; // 引用計數(shù)能夠用 19 個二進(jìn)制位存儲時磷蜀,直接存儲在這里
}
注意這里的 has_sidetable_rc 和 extra_rc召耘,has_sidetable_rc 表明該指針是否引用了 sidetable 散列表,之所以有這個選項褐隆,是因為少量的引用計數(shù)是不會直接存放在 SideTables 表中的污它,對象的引用計數(shù)會先存放在 extra_rc 中,當(dāng)其被存滿時庶弃,才會存入相應(yīng)的 SideTables 散列表中衫贬,SideTables 中有很多張 SideTable,每個 SideTable 也都是一個散列表虫埂,而引用計數(shù)表就包含在 SideTable 之中祥山。
SideTables
引用計數(shù)要么存放在 isa 的 extra_rc 中,要么存放在引用計數(shù)表中掉伏,而引用計數(shù)表包含在一個叫 SideTable 的結(jié)構(gòu)中缝呕,它是一個散列表,也就是哈希表斧散。而 SideTable 又包含在一個全局的 StripeMap 的哈希映射表中供常,這個表的名字叫 SideTables。
散列表(Hash table鸡捐,也叫哈希表)栈暇,是根據(jù)建(Key)而直接訪問在內(nèi)存存儲位置的數(shù)據(jù)結(jié)構(gòu)。也就是說箍镜,它通過一個關(guān)于鍵值得函數(shù)源祈,將所需查詢的數(shù)據(jù)映射到表中一個位置來訪問記錄,這加快了查找速度色迂。這個映射函數(shù)稱作散列函數(shù)香缺,存放記錄的數(shù)組稱作散列表
// SideTables
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
// SideTable
struct SideTable {
spinlock_t slock; // 自旋鎖
RefcountMap refcnts; // 引用計數(shù)表
weak_table_t weak_table; // 弱引用表
// other code ...
};
weak_table_t weak_table; 弱應(yīng)用表
散列表 在內(nèi)存里面有多張 + 最多能夠多少張?歇僧?图张?
回答:
一個 SideTables 包含眾多 SideTable,每個 SideTable 中又包含了三個元素,spinlock_t 自旋鎖祸轮、RefcountMap 引用計數(shù)表兽埃、weak_table_t 弱引用表。所以既然 SideTables 是一個哈希映射的表适袜,為什么不用 SideTables 直接包含自旋鎖柄错,引用計數(shù)表和弱引用表呢?這是因為在眾多線程同時訪問這個 SideTable 表的時候痪蝇,為了保證數(shù)據(jù)安全鄙陡,需要給其加上自旋鎖,如果只有一張 SideTable 的表躏啰,那么所有數(shù)據(jù)訪問都會出一個進(jìn)一個,單線程進(jìn)行耙册,非常影響效率给僵,雖然自旋鎖已經(jīng)是效率非常高的鎖,這會帶來非常不好的用戶體驗详拙。針對這種情況帝际,將一張 SideTable 分為多張表的 SideTables,再各自加鎖保證數(shù)據(jù)的安全饶辙,這樣就增加了并發(fā)量蹲诀,提高了數(shù)據(jù)訪問的效率,這就是為什么一個 SideTables 下涵蓋眾多 SideTable 表的原因弃揽。
retain總結(jié)
首先判斷是否非nonpointer指針,直接操作散列表脯爪,進(jìn)行引用計數(shù)+1的操作。
如果是nonpointer矿微,操作extra_rc進(jìn)行常規(guī)的引用計數(shù)+1操作痕慢,當(dāng)然這里還有一個判斷。如果屬性值正在進(jìn)行釋放涌矢,也不需要進(jìn)行引用計數(shù)的操作掖举,同時這里還有一個細(xì)節(jié)要注意,蘋果設(shè)計了一個算法是比較到位的娜庇,在真機上只有8位塔次,如果說8位滿了,需要額外借助散列表來存儲名秀,它會把extra_rc滿狀態(tài)的一半存儲2的7次方到散列表中励负,剩下的2的7次方還是存在nonpointer的extra_rc中,來進(jìn)行正常的引用計數(shù)操作泰偿。