Python實(shí)戰(zhàn)社群 Java實(shí)戰(zhàn)社群 掃碼關(guān)注添加客服 進(jìn)Python社群▲ 掃碼關(guān)注添加客服 進(jìn)Java社群▲ 作者丨伯陽
來源丨知識(shí)小集(zsxjtip)
前幾天在群里和朋友討論起 copy 的各種細(xì)節(jié),,發(fā)現(xiàn)有些地方依然模糊不清,,便查閱文檔,,翻看源碼,,以解心中之惑,。 本文測試代碼請點(diǎn)擊 https://github.com/BiBoyang/BoyangBlog/tree/master/CopyTest 屬性中的 copy我們知道,屬性中的 copy,,其實(shí)是不保留新值的,,而是將其拷貝一份,,當(dāng)使用不可變對象的時(shí)候,,可以用它來保護(hù)封裝性,確保它不會(huì)隨意的變動(dòng)。那么它是如何實(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é)論: 對結(jié)構(gòu)體進(jìn)行 copy,直接對結(jié)構(gòu)體指針?biāo)赶虻膬?nèi)存進(jìn)行拷貝即可,; 對對象進(jìn)行 copy,,則會(huì)傳入的源指針和目標(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,會(huì)有些有趣的情況,,不過并不影響結(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); } }
從上面代碼我們可以得到幾條信息: CFStringCreateCopy 函數(shù),,返回的字符串是否返回新的對象,要看源字符串是 immutable 還是 mutable 的,; 如果源字符串是 mutable 的,,會(huì)開辟一片新的內(nèi)存,,生成一個(gè)新的 immutable 對象返回,創(chuàng)建使用 __CFStringCreateImmutableFunnel3 方法,,這個(gè)是深拷貝; 如果源字符串是 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)容拷貝一份放到新的對象里,這里也就是深拷貝,。 CFArrayCreateCopyCFArrayRef 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; }
CFArrayCreateMutableCopyCFMutableArrayRef 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ù)組的拷貝,,都是會(huì)調(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
|