日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

iOS 中 copy 的原理

 泰榮林黑皮 2020-07-19

Python實(shí)戰(zhàn)社群

Java實(shí)戰(zhàn)社群

長按識別下方二維碼,按需求添加

掃碼關(guān)注添加客服

進(jìn)Python社群▲

掃碼關(guān)注添加客服

進(jìn)Java社群

作者丨伯陽

來源丨知識小集(zsxjtip)


前幾天在群里和朋友討論起 copy 的各種細(xì)節(jié),發(fā)現(xiàn)有些地方依然模糊不清,便查閱文檔,翻看源碼,以解心中之惑。

本文測試代碼請點(diǎn)擊 https://github.com/BiBoyang/BoyangBlog/tree/master/CopyTest

屬性中的 copy

我們知道,屬性中的 copy,其實(shí)是不保留新值的,而是將其拷貝一份,當(dāng)使用不可變對象的時(shí)候,可以用它來保護(hù)封裝性,確保它不會隨意的變動。那么它是如何實(shí)現(xiàn)的呢?

在 objc-accessor.mm 中,有 property 中 copy 的實(shí)現(xiàn)。

這里有兩個(gè)函數(shù) objc_copyStruct 和 objc_copyCppObjectAtomic,分別對應(yīng)結(jié)構(gòu)體的拷貝和對象的拷貝。具體代碼如下:

/**
* 結(jié)構(gòu)體拷貝
* src:源指針
* dest:目標(biāo)指針
* size:大小
* atomic:是否是原子操作
* hasStrong:可能是表示是否是strong修飾
*/

void objc_copyStruct(void *dest, const void *src, ptrdiff_t size, BOOL atomic, BOOL hasStrong __unused) {
spinlock_t *srcLock = nil;
spinlock_t *dstLock = nil;
// >> 如果是原子操作,則加鎖
if (atomic) {
srcLock = &StructLocks[src];
dstLock = &StructLocks[dest];
spinlock_t::lockTwo(srcLock, dstLock);
}
// >> 實(shí)際的拷貝操作
memmove(dest, src, size);

// >> 解鎖
if (atomic) {
spinlock_t::unlockTwo(srcLock, dstLock);
}
}

/**
* 對象拷貝
* src:源指針
* dest:目標(biāo)指針
* copyHelper:對對象進(jìn)行實(shí)際拷貝的函數(shù)指針,參數(shù)是src和dest
*/


void objc_copyCppObjectAtomic(void *dest, const void *src, void (*copyHelper) (void *dest, const void *source)) {
// >> 獲取源指針的對象鎖
spinlock_t *srcLock = &CppObjectLocks[src];
// >> 獲取目標(biāo)指針的對象鎖
spinlock_t *dstLock = &CppObjectLocks[dest];
// >> 對源對象和目標(biāo)對象進(jìn)行上鎖
spinlock_t::lockTwo(srcLock, dstLock);

// let C++ code perform the actual copy.
// >> 調(diào)用函數(shù)指針對應(yīng)的函數(shù),讓C++進(jìn)行實(shí)際的拷貝操作
copyHelper(dest, src);
// >> 解鎖
spinlock_t::unlockTwo(srcLock, dstLock);
}

從上述代碼中,我們可以得出結(jié)論:

  1. 對結(jié)構(gòu)體進(jìn)行 copy,直接對結(jié)構(gòu)體指針?biāo)赶虻膬?nèi)存進(jìn)行拷貝即可;

  2. 對對象進(jìn)行 copy,則會傳入的源指針和目標(biāo)對象同時(shí)進(jìn)行加鎖,然后在去進(jìn)行拷貝操作,所以我們可以知道,copy 操作是線程安全的。

不過這里的 copyHelper(dest, src);找不到實(shí)現(xiàn)方法,則有些遺憾。

深淺拷貝

  • 淺拷貝:只創(chuàng)建一個(gè)新的指針,指向原指針指向的內(nèi)存;

  • 深拷貝:創(chuàng)建一個(gè)新的指針,并開辟新的內(nèi)存空間,內(nèi)容拷貝自原指針指向的內(nèi)存,并指向它。

我們分別使用 copy 和 strong,對 NSString 和 NSMutableString進(jìn)行兩兩分配;以及對 NSArray 和 NSMutableArray 進(jìn)行兩兩分配,可以得到一個(gè)結(jié)果。

測試的源碼在這里 https://github.com/BiBoyang/BoyangBlog/blob/master/CopyTest/CopyTest/ViewController.m ,可以查看測試的代碼。

通過一系列測試,我得到了一個(gè)這樣的結(jié)論。

非容器對象:

可不可變對象copy類型深淺拷貝返回對象是否可變
不可變對象copy淺拷貝不可變
可變對象copy深拷貝不可變
不可變對象mutableCopy深拷貝可變
可變對象mutableCopy深拷貝可變
  • 這里的源字符串如果是 Tagged Pointer 類型,即 NSTaggedPointerString,會有些有趣的情況,不過并不影響結(jié)果??梢栽谖恼履┪膊榭?。

  • 注意:接收 copy 結(jié)果的對象,也需要是可變的并且屬性關(guān)鍵字是 strong,才可以進(jìn)行修改,也就是可變,兩個(gè)條件一個(gè)不符合則無法改變。

容器對象:

可不可變對象copy類型深淺拷貝返回對象是否可變內(nèi)部元素信息Info
不可變對象copy淺拷貝不可變內(nèi)部元素是淺拷貝集合地址不變
可變對象copy淺拷貝不可變內(nèi)部元素是淺拷貝集合地址改變
看起來是深拷貝,實(shí)際不是
不可變對象mutableCopy淺拷貝可變內(nèi)部元素是淺拷貝集合地址改變
看起來是深拷貝,實(shí)際不是
可變對象mutableCopy淺拷貝可變內(nèi)部元素是淺拷貝集合地址改變
看起來是深拷貝,實(shí)際不是
  • 除了不可變對象使用 copy,其他的 copy 和 mutableCopy,都是開辟了一個(gè)新的集合空間,但是內(nèi)部的元素的指針還是指向源地址;

  • 有的人將集合地址改變的拷貝稱之為深拷貝,但是這個(gè)其實(shí)是非常錯(cuò)誤的理解,深拷貝就是全層次的拷貝。

從源碼中探究答案

上面的測試更多的是我們自己去一個(gè)一個(gè)的測試,更底層的實(shí)現(xiàn)原理,還是要看源碼。

對于字符串,我們雖然因?yàn)?Foundation.framework 并未開源找不到源碼,但是我們依舊可以去查閱開源的 CoreFoundation.framework 源碼。因?yàn)?CoreFoundation 和 Foundation 的對象是 Toll-free bridge 的,所以,可以從 CoreFoundation 的源代碼進(jìn)行了解。

我們進(jìn)入到這里查閱相關(guān)代碼 CFString.h,里面給出了 CFStringCreateCopy 和 CFStringCreateMutableCopy 這兩個(gè)方法,分別對應(yīng) copy 和 mutableCopy;以及 CFArray.c,里面給出了 CFArrayCreateCopy 和 CFArrayCreateMutableCopy 兩個(gè)方法,分別對應(yīng) copy 和 mutableCopy。

CFStringCreateCopy

/**
* >> 字符串的 copy 操作
*/

CFStringRef CFStringCreateCopy(CFAllocatorRef alloc, CFStringRef str) {
// CF_OBJC_FUNCDISPATCHV(__kCFStringTypeID, CFStringRef, (NSString *)str, copy);

/*
* 如果該字符串不是可變的,并且與我們使用的分配器具有相同的分配器,
并且這些字符是內(nèi)聯(lián)的,或者由該字符串擁有,或者該字符串是常量。
然后保留而不是制作真實(shí)副本。
*/

__CFAssertIsString(str);
// >> 判斷源字符串是否是 mutable
if (!__CFStrIsMutable((CFStringRef)str) && // If the string is not mutable
((alloc ? alloc : __CFGetDefaultAllocator()) == __CFGetAllocator(str)) && // and it has the same allocator as the one we're using
(__CFStrIsInline((CFStringRef)str) || __CFStrFreeContentsWhenDone((CFStringRef)str) || __CFStrIsConstant((CFStringRef)str))) { // and the characters are inline, or are owned by the string, or the string is constant
// >> 使用引用計(jì)數(shù)加一來代替真正的copy,也就是這里是淺拷貝。
if (!(kCFUseCollectableAllocator && (0))) CFRetain(str); // Then just retain instead of making a true copy
return str;
}


if (__CFStrIsEightBit((CFStringRef)str)) {
const uint8_t *contents = (const uint8_t *)__CFStrContents((CFStringRef)str);
return __CFStringCreateImmutableFunnel3(alloc, contents + __CFStrSkipAnyLengthByte((CFStringRef)str), __CFStrLength2((CFStringRef)str, contents), __CFStringGetEightBitStringEncoding(), false, false, false, false, false, ALLOCATORSFREEFUNC, 0);
} else {
const UniChar *contents = (const UniChar *)__CFStrContents((CFStringRef)str);
return __CFStringCreateImmutableFunnel3(alloc, contents, __CFStrLength2((CFStringRef)str, contents) * sizeof(UniChar), kCFStringEncodingUnicode, false, true, false, false, false, ALLOCATORSFREEFUNC, 0);
}
}

從上面代碼我們可以得到幾條信息:

  1. CFStringCreateCopy 函數(shù),返回的字符串是否返回新的對象,要看源字符串是 immutable 還是 mutable 的;

  2. 如果源字符串是 mutable 的,會開辟一片新的內(nèi)存,生成一個(gè)新的 immutable 對象返回,創(chuàng)建使用 __CFStringCreateImmutableFunnel3 方法,這個(gè)是深拷貝;

  3. 如果源字符串是 immutable 的,且是內(nèi)聯(lián)的、string 所持有或者是常量,那么只對源 CFStringRef 對象引用計(jì)數(shù)加一,這個(gè)是淺拷貝

CFStringCreateMutableCopy

/*
* >>>> 字符串的 copy 操作
*/

CFMutableStringRef CFStringCreateMutableCopy(CFAllocatorRef alloc, CFIndex maxLength, CFStringRef string) {
CFMutableStringRef newString;

// CF_OBJC_FUNCDISPATCHV(__kCFStringTypeID, CFMutableStringRef, (NSString *)string, mutableCopy);

__CFAssertIsString(string);

newString = CFStringCreateMutable(alloc, maxLength);
// 將源對象的內(nèi)容,放到新創(chuàng)建的newString中
__CFStringReplace(newString, CFRangeMake(0, 0), string);

return newString;
}

在這里,可以知道,在 CFStringCreateMutableCopy 函數(shù)里,不再需要判斷源對象是否是 mutable,直接創(chuàng)建一個(gè)新的對象,然后將源內(nèi)容拷貝一份放到新的對象里,這里也就是深拷貝

CFArrayCreateCopy

CFArrayRef CFArrayCreateCopy(CFAllocatorRef allocator, CFArrayRef array) {
return __CFArrayCreateCopy0(allocator, array);
}

CF_PRIVATE CFArrayRef __CFArrayCreateCopy0(CFAllocatorRef allocator, CFArrayRef array) {

CFArrayRef result;
// >>>> CFArrayCallBacks變量,用于存放數(shù)組元素的回調(diào)
const CFArrayCallBacks *cb;

// >>>> 存放數(shù)組元素的結(jié)構(gòu)體指針
struct __CFArrayBucket *buckets;
CFAllocatorRef bucketsAllocator;
void* bucketsBase;

// >>>> 獲取源數(shù)組元素的總個(gè)數(shù)
CFIndex numValues = CFArrayGetCount(array);
CFIndex idx;
if (CF_IS_OBJC(CFArrayGetTypeID(), array)) {
cb = &kCFTypeArrayCallBacks;
} else {
cb = __CFArrayGetCallBacks(array);
}

// >>>> 初始化以一個(gè)不可變數(shù)組
result = __CFArrayInit(allocator, __kCFArrayImmutable, numValues, cb);
cb = __CFArrayGetCallBacks(result); // GC: use the new array's callbacks so we don't leak.
buckets = __CFArrayGetBucketsPtr(result);
bucketsAllocator = isStrongMemory(result) ? allocator : kCFAllocatorNull;
bucketsBase = CF_IS_COLLECTABLE_ALLOCATOR(bucketsAllocator) ? (void *)auto_zone_base_pointer(objc_collectableZone(), buckets) : NULL;
for (idx = 0; idx < numValues; idx++) {
const void *value = CFArrayGetValueAtIndex(array, idx);
if (NULL != cb->retain) {
value = (void *)INVOKE_CALLBACK2(cb->retain, allocator, value);
}
__CFAssignWithWriteBarrier((void **)&buckets->_item, (void *)value);
buckets++;
}

// >>>> //設(shè)定數(shù)組的長度count
__CFArraySetCount(result, numValues);
return result;
}

CFArrayCreateMutableCopy

CFMutableArrayRef CFArrayCreateMutableCopy(CFAllocatorRef allocator, CFIndex capacity, CFArrayRef array) {
return __CFArrayCreateMutableCopy0(allocator, capacity, array);
}

CF_PRIVATE CFMutableArrayRef __CFArrayCreateMutableCopy0(CFAllocatorRef allocator, CFIndex capacity, CFArrayRef array) {
CFMutableArrayRef result;
const CFArrayCallBacks *cb;
CFIndex idx, numValues = CFArrayGetCount(array);
UInt32 flags;
if (CF_IS_OBJC(CFArrayGetTypeID(), array)) {
cb = &kCFTypeArrayCallBacks;
}
else {
cb = __CFArrayGetCallBacks(array);
}
// 將標(biāo)記設(shè)置為雙端隊(duì)列
flags = __kCFArrayDeque;
// 創(chuàng)建新的不可變數(shù)組
result = (CFMutableArrayRef)__CFArrayInit(allocator, flags, capacity, cb);
// 設(shè)置數(shù)組的容量
if (0 == capacity) _CFArraySetCapacity(result, numValues);

for (idx = 0; idx < numValues; idx++) {
const void *value = CFArrayGetValueAtIndex(array, idx);
// 將元素對象添加到新的數(shù)組列表中
CFArrayAppendValue(result, value);
}
return result;
}

從上面兩份代碼,我們可以可以:
1. immutable 和 mutable 數(shù)組的拷貝,都是會調(diào)用 __CFArrayInit 函數(shù)去創(chuàng)建一個(gè)新的對象;
2. 內(nèi)部元素實(shí)際上還都是指向源數(shù)組;
3. 不可變數(shù)組的 copy 沒有體現(xiàn)在代碼中,個(gè)人猜測可能是實(shí)現(xiàn)過于簡單,所以也就沒有在這里實(shí)現(xiàn)。

其他的容器,類似 CFDictionary、CFSet 等,也是類似的結(jié)果。

真正的深拷貝

可以發(fā)現(xiàn),其實(shí)如果嚴(yán)格按照深拷貝的定義,集合的 copy 和 mutableCopy 其實(shí)都是淺拷貝。那么該如何實(shí)現(xiàn)真正的深拷貝呢?有兩個(gè)辦法:

一. 使用對象的序列化拷貝:

//數(shù)組內(nèi)對象是指針復(fù)制
NSArray *deepCopyArray = [[NSArray alloc] initWithArray:array];
//真正意義上的深復(fù)制,數(shù)組內(nèi)對象是對象復(fù)制
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:array]];

二. 自己實(shí)現(xiàn) copy 協(xié)議

也就是 NSCopying,NSMutableCopying。

特殊情況

在測試的時(shí)候,發(fā)現(xiàn)如果這個(gè)字符串是 isTaggedPointerString ,則有個(gè)特殊情況,不過貌似也沒什么用處。

可不可變對象copy類型深淺拷貝接收對象關(guān)鍵字返回對象是否可變
不可變對象copy淺拷貝
不可變
可變對象copy深拷貝
不可變
不可變對象mutableCopy深拷貝strong可變
不可變對象mutableCopy淺拷貝copy不可變
可變對象mutableCopy深拷貝strong可變
可變對象mutableCopy深拷貝copy不可變


參考

[1]https://opensource.apple.com/source/CF/CF-1151.16/
[2]https://opensource.apple.com/tarballs/CF/
[3]https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Collections/Articles/Copying.html#//apple_ref/doc/uid/TP40010162-SW3
[4]https://en./wiki/Object_copying#Deep_copy
[5]https:///questions/184710/what-is-the-difference-between-a-deep-copy-and-a-shallow-copy

程序員專欄

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多