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é)論: 對結(jié)構(gòu)體進(jìn)行 copy,直接對結(jié)構(gòu)體指針?biāo)赶虻膬?nèi)存進(jìn)行拷貝即可; 對對象進(jìn)行 copy,則會傳入的源指針和目標(biāo)對象同時(shí)進(jìn)行加鎖,然后在去進(jìn)行拷貝操作,所以我們可以知道,copy 操作是線程安全的。
不過這里的 copyHelper(dest, src); 找不到實(shí)現(xiàn)方法,則有些遺憾。 深淺拷貝
我們分別使用 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 | 深拷貝 | 可變 |
容器對象: 可不可變對象 | copy類型 | 深淺拷貝 | 返回對象是否可變 | 內(nèi)部元素信息 | Info |
---|
不可變對象 | copy | 淺拷貝 | 不可變 | 內(nèi)部元素是淺拷貝 | 集合地址不變 | 可變對象 | copy | 淺拷貝 | 不可變 | 內(nèi)部元素是淺拷貝 | 集合地址改變 看起來是深拷貝,實(shí)際不是 | 不可變對象 | mutableCopy | 淺拷貝 | 可變 | 內(nèi)部元素是淺拷貝 | 集合地址改變 看起來是深拷貝,實(shí)際不是 | 可變對象 | mutableCopy | 淺拷貝 | 可變 | 內(nèi)部元素是淺拷貝 | 集合地址改變 看起來是深拷貝,實(shí)際不是 |
從源碼中探究答案上面的測試更多的是我們自己去一個(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 的,會開辟一片新的內(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ù)組的拷貝,都是會調(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
|