การคัดลอก NSArray อย่างละเอียด


119

มีฟังก์ชั่นในตัวที่ช่วยให้ฉันคัดลอกแบบลึกได้NSMutableArrayหรือไม่?

ฉันมองไปรอบ ๆ บางคนบอกว่า[aMutableArray copyWithZone:nil]งานเป็นสำเนาลึก แต่ฉันลองแล้วดูเหมือนว่าจะเป็นการลอกแบบตื้น ๆ

ตอนนี้ฉันกำลังทำสำเนาด้วยตัวเองด้วยการforวนซ้ำ:

//deep copy a 9*9 mutable array to a passed-in reference array

-deepMuCopy : (NSMutableArray*) array 
    toNewArray : (NSMutableArray*) arrayNew {

    [arrayNew removeAllObjects];//ensure it's clean

    for (int y = 0; y<9; y++) {
        [arrayNew addObject:[NSMutableArray new]];
        for (int x = 0; x<9; x++) {
            [[arrayNew objectAtIndex:y] addObject:[NSMutableArray new]];

            NSMutableArray *aDomain = [[array objectAtIndex:y] objectAtIndex:x];
            for (int i = 0; i<[aDomain count]; i++) {

                //copy object by object
                NSNumber* n = [NSNumber numberWithInt:[[aDomain objectAtIndex:i] intValue]];
                [[[arrayNew objectAtIndex:y] objectAtIndex:x] addObject:n];
            }
        }
    }
}

แต่ฉันต้องการโซลูชันที่สะอาดและรวบรัดกว่านี้


44
@Genericrich สำเนาลึกและตื้นเป็นคำศัพท์ที่กำหนดไว้ค่อนข้างดีในการพัฒนาซอฟต์แวร์ Google.com อาจช่วยได้
Andrew Grant

1
ความสับสนอาจเป็นเพราะพฤติกรรมของ-copyคอลเลกชันที่เปลี่ยนรูปไม่ได้เปลี่ยนไประหว่าง Mac OS X 10.4 และ 10.5: developer.apple.com/library/mac/releasenotes/Cocoa/… (เลื่อนลงไปที่ "ไม่เปลี่ยนรูปคอลเล็กชันและพฤติกรรมการคัดลอก")
user102008

1
@AndrewGrant เกี่ยวกับความคิดเพิ่มเติมและด้วยความเคารพฉันไม่เห็นด้วยที่สำเนาลึกเป็นคำที่กำหนดไว้อย่างดี ขึ้นอยู่กับแหล่งที่มาที่คุณอ่านไม่ชัดเจนว่าการเรียกซ้ำแบบไม่ จำกัด ในโครงสร้างข้อมูลที่ซ้อนกันเป็นข้อกำหนดของการดำเนินการ 'สำเนาลึก' หรือไม่ กล่าวอีกนัยหนึ่งคุณจะได้รับคำตอบที่ขัดแย้งกันว่าการดำเนินการคัดลอกที่สร้างวัตถุใหม่ที่มีสมาชิกเป็นสำเนาตื้นของสมาชิกของวัตถุต้นฉบับเป็นการดำเนินการ 'สำเนาลึก' หรือไม่ ดูstackoverflow.com/a/6183597/1709587สำหรับการสนทนาบางส่วนเกี่ยวกับเรื่องนี้ (ในบริบท Java แต่เกี่ยวข้องเหมือนกันทั้งหมด)
Mark Amery

@AndrewGrant ฉันต้องสำรอง @MarkAmery และ @Genericrich สำเนาแบบลึกถูกกำหนดไว้อย่างดีหากคลาสรูทที่ใช้ในคอลเลกชันและองค์ประกอบทั้งหมดสามารถคัดลอกได้ กรณีนี้ไม่ใช่กับ NSArray (และคอลเลกชัน objc อื่น ๆ ) หากองค์ประกอบไม่ได้ใช้งานcopyจะต้องใส่อะไรลงไปใน "สำเนาลึก"? หากองค์ประกอบเป็นคอลเล็กชันอื่นcopyจะไม่ส่งสำเนา (ของคลาสเดียวกัน) ดังนั้นฉันคิดว่ามันถูกต้องสมบูรณ์ที่จะโต้แย้งเกี่ยวกับประเภทของสำเนาที่ต้องการในกรณีเฉพาะ
Nikolai Ruhe

@NikolaiRuhe หากองค์ประกอบไม่ได้ใช้NSCopying/ -copyแสดงว่าไม่สามารถคัดลอกได้ - ดังนั้นคุณไม่ควรพยายามทำสำเนาเพราะนั่นไม่ใช่ความสามารถที่ออกแบบมาให้มี ในแง่ของการใช้งาน Cocoa วัตถุที่ไม่สามารถคัดลอกได้มักจะมีสถานะแบ็กเอนด์ C ที่เชื่อมโยงอยู่ดังนั้นการแฮ็กสำเนาโดยตรงของวัตถุอาจนำไปสู่สภาพการแข่งขันหรือแย่ลง ดังนั้นเพื่อตอบว่า "สิ่งที่จะใส่ลงใน 'สำเนาลึก'" - การอ้างอิงที่เก็บรักษาไว้ สิ่งเดียวที่คุณสามารถใส่ได้ทุกที่เมื่อคุณมีสิ่งที่ไม่ใช่NSCopyingวัตถุ
Slipp D.Thompson

คำตอบ:


210

ตามที่เอกสารของ Apple เกี่ยวกับสำเนาลึกระบุไว้อย่างชัดเจน:

หากคุณต้องการเพียงสำเนาลึกระดับเดียว :

NSMutableArray *newArray = [[NSMutableArray alloc] 
                             initWithArray:oldArray copyItems:YES];

โค้ดด้านบนสร้างอาร์เรย์ใหม่ที่มีสมาชิกเป็นสำเนาตื้นของสมาชิกของอาร์เรย์เก่า

โปรดทราบว่าหากคุณต้องการคัดลอกโครงสร้างข้อมูลที่ซ้อนกันอย่างละเอียด - สิ่งที่เอกสาร Apple ที่เชื่อมโยงเรียกว่าสำเนาลึกจริงวิธีนี้จะไม่เพียงพอ โปรดดูคำตอบอื่น ๆ ที่นี่สำหรับสิ่งนั้น


ดูเหมือนจะเป็นคำตอบที่ถูกต้อง API ระบุว่าแต่ละองค์ประกอบได้รับข้อความ [element copyWithZone:] ซึ่งอาจเป็นสิ่งที่คุณเห็น หากคุณเห็นว่าการส่ง [NSMutableArray copyWithZone: nil] ไม่ได้คัดลอกในระดับลึกอาร์เรย์อาร์เรย์อาจคัดลอกไม่ถูกต้องโดยใช้วิธีนี้
Ed Marty

7
ฉันไม่คิดว่าจะได้ผลตามที่คาดไว้ จากเอกสารของ Apple: "วิธี copyWithZone: ดำเนินการคัดลอกตื้น ๆหากคุณมีการรวบรวมความลึกโดยพลการการส่ง YES สำหรับพารามิเตอร์ flag จะทำสำเนาที่ไม่เปลี่ยนรูปของระดับแรกที่อยู่ใต้พื้นผิวหากคุณผ่าน NO ความไม่แน่นอนของ ระดับแรกจะไม่ได้รับผลกระทบไม่ว่าในกรณีใดความไม่แน่นอนของระดับที่ลึกกว่าทั้งหมดจะไม่ได้รับผลกระทบ "คำถาม SO เกี่ยวกับสำเนาที่ไม่แน่นอนในระดับลึก
Joe D'Andrea

7
นี่ไม่ใช่สำเนาลึก
Daij-Djan

9
นี่เป็นคำตอบที่ไม่สมบูรณ์ ส่งผลให้ได้สำเนาลึกระดับเดียว หากมีประเภทที่ซับซ้อนมากขึ้นภายใน Array จะไม่มีสำเนาลึก
Cameron Lowell Palmer

7
นี้สามารถเป็นสำเนาลึกขึ้นอยู่กับว่าcopyWithZone:จะดำเนินการในระดับที่ได้รับ
devios1

62

วิธีเดียวที่ฉันรู้ได้อย่างง่ายดายคือการเก็บถาวรและยกเลิกการเก็บอาร์เรย์ของคุณทันที รู้สึกเหมือนเป็นการแฮ็กเล็กน้อย แต่จริงๆแล้วมีการแนะนำอย่างชัดเจนในเอกสารของ Apple เกี่ยวกับการคัดลอกคอลเล็กชันซึ่งระบุว่า:

หากคุณต้องการสำเนาลึกจริงเช่นเมื่อคุณมีอาร์เรย์อาร์เรย์คุณสามารถเก็บถาวรและยกเลิกการเก็บรวบรวมได้โดยให้เนื้อหาทั้งหมดเป็นไปตามโปรโตคอล NSCoding ตัวอย่างของเทคนิคนี้แสดงไว้ในรายการ 3

รายการ 3 สำเนาลึกจริง

NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
          [NSKeyedArchiver archivedDataWithRootObject:oldArray]];

สิ่งที่จับได้คือวัตถุของคุณต้องรองรับอินเทอร์เฟซ NSCoding เนื่องจากจะใช้ในการจัดเก็บ / โหลดข้อมูล

เวอร์ชัน Swift 2:

let trueDeepCopyArray = NSKeyedUnarchiver.unarchiveObjectWithData(
    NSKeyedArchiver.archivedDataWithRootObject(oldArray))

12
การใช้ NSArchiver และ NSUnarchiver เป็นโซลูชันที่มีประสิทธิภาพสูงมากหากอาร์เรย์ของคุณมีขนาดใหญ่ การเขียนเมธอดหมวดหมู่ NSArray ทั่วไปซึ่งใช้โปรโตคอล NSCopying จะทำกลอุบายทำให้ 'คง' ของวัตถุที่ไม่เปลี่ยนรูปแบบง่ายๆและ 'สำเนา' ของจริงที่ไม่สามารถเปลี่ยนแปลงได้
Nikita Zhuk

ควรระมัดระวังเรื่องค่าใช้จ่าย แต่ NSCoding จะแพงกว่า NSCopying ที่ใช้ในinitWithArray:copyItems:วิธีนี้หรือไม่? วิธีแก้ปัญหาการเก็บถาวร / การยกเลิกการเก็บถาวรนี้ดูเหมือนจะมีประโยชน์มากโดยพิจารณาว่ามีคลาสควบคุมกี่คลาสที่เป็นไปตาม NSCoding แต่ไม่ใช่ NSCopying
Wienke

ฉันขอแนะนำอย่างยิ่งว่าคุณไม่ควรใช้วิธีนี้ Serialization ไม่เคยเร็วไปกว่าการคัดลอกหน่วยความจำ
Brett

1
หากคุณมีอ็อบเจ็กต์ที่กำหนดเองตรวจสอบให้แน่ใจว่าได้ใช้ encodeWithCoder และ initWithCoder เพื่อให้สอดคล้องกับโปรโตคอล NSCoding
user523234

เห็นได้ชัดว่าควรใช้ดุลยพินิจของโปรแกรมเมอร์อย่างยิ่งเมื่อใช้การทำให้เป็นอนุกรม
VH-NZZ

34

การคัดลอกโดยค่าเริ่มต้นจะให้สำเนาตื้น

นั่นเป็นเพราะการโทรcopyเหมือนกับที่copyWithZone:NULLเรียกว่าการคัดลอกด้วยโซนเริ่มต้น การcopyโทรไม่ได้ส่งผลให้เกิดสำเนาลึก ในกรณีส่วนใหญ่จะให้สำเนาตื้น ๆ แต่ในกรณีใด ๆ ก็ขึ้นอยู่กับชั้นเรียน สำหรับการสนทนาอย่างละเอียดฉันขอแนะนำCollections Programming Topicsบนไซต์ Apple Developer

initWithArray: CopyItems: ให้สำเนาลึกระดับเดียว

NSArray *deepCopyArray = [[NSArray alloc] initWithArray:someArray copyItems:YES];

NSCoding เป็นวิธีที่แนะนำโดย Apple ในการจัดทำสำเนาแบบลึก

สำหรับสำเนาลึกจริง (Array of Arrays) คุณจะต้องNSCodingและเก็บถาวร / ยกเลิกการเก็บวัตถุ:

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];

1
นี่ถูกต้องแล้ว โดยไม่คำนึงถึงความนิยมหรือ edge-case ที่ปรับให้เหมาะสมก่อนเวลาอันควรเกี่ยวกับหน่วยความจำประสิทธิภาพ นอกจากนี้แฮ็ค ser-derser สำหรับสำเนาลึกนี้ยังใช้ในสภาพแวดล้อมภาษาอื่น ๆ อีกมากมาย หากไม่มี obj dedupe สิ่งนี้รับประกันว่าสำเนาลึกที่ดีจะแยกออกจากต้นฉบับโดยสิ้นเชิง

1
นี่คือผู้พัฒนา

7

สำหรับ Dictonary

NSMutableDictionary *newCopyDict = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)objDict, kCFPropertyListMutableContainers);

สำหรับ Array

NSMutableArray *myMutableArray = (NSMutableArray *)CFPropertyListCreateDeepCopy(NULL, arrData, kCFPropertyListMutableContainersAndLeaves);


4

ไม่ไม่มีสิ่งที่สร้างขึ้นในกรอบสำหรับสิ่งนี้ คอลเลกชันโกโก้สนับสนุนสำเนาตื้น ๆ (ด้วยวิธีcopyการหรือarrayWithArray:วิธีการ) แต่ไม่ได้พูดถึงแนวคิดการคัดลอกแบบลึก

เนื่องจาก "สำเนาลึก" เริ่มยากที่จะกำหนดเนื่องจากเนื้อหาในคอลเลกชันของคุณเริ่มรวมถึงวัตถุที่คุณกำหนดเอง "สำเนาลึก" หมายความว่าทุกวัตถุในกราฟออบเจ็กต์เป็นการอ้างอิงที่ไม่ซ้ำกันเมื่อเทียบกับทุกวัตถุในกราฟวัตถุดั้งเดิมหรือไม่

หากมีNSDeepCopyingโปรโตคอลสมมุติบางอย่างคุณสามารถตั้งค่านี้และตัดสินใจในวัตถุทั้งหมดของคุณได้ แต่น่าเสียดายที่ไม่มี หากคุณควบคุมออบเจ็กต์ส่วนใหญ่ในกราฟของคุณคุณสามารถสร้างโปรโตคอลนี้ด้วยตัวเองและใช้งานได้ แต่คุณต้องเพิ่มหมวดหมู่ในคลาสพื้นฐานตามความจำเป็น

คำตอบของ @ AndrewGrant ที่แนะนำการใช้การเก็บถาวร / การยกเลิกการเก็บถาวรแบบคีย์เป็นวิธีที่ไม่มีประสิทธิภาพ แต่ถูกต้องและสะอาดในการบรรลุสิ่งนี้สำหรับวัตถุโดยพลการ หนังสือเล่มนี้ไปไกลแล้วดังนั้นขอแนะนำให้เพิ่มหมวดหมู่ให้กับวัตถุทั้งหมดที่รองรับการคัดลอกแบบลึก


2

ฉันมีวิธีแก้ปัญหาหากพยายามทำสำเนาลึกสำหรับข้อมูลที่เข้ากันได้กับ JSON

เพียงแค่ใช้NSDataของNSArrayใช้NSJSONSerializationแล้วสร้างวัตถุ JSON นี้จะสร้างสำเนาใหม่และสดที่สมบูรณ์ของการNSArray/NSDictionaryที่มีการอ้างอิงหน่วยความจำใหม่ของพวกเขา

แต่ตรวจสอบให้แน่ใจว่าอ็อบเจ็กต์ของ NSArray / NSDictionary และลูก ๆ ของพวกเขาต้องเป็นอนุกรม JSON

NSData *aDataOfSource = [NSJSONSerialization dataWithJSONObject:oldCopy options:NSJSONWritingPrettyPrinted error:nil];
NSDictionary *aDictNewCopy = [NSJSONSerialization JSONObjectWithData:aDataOfSource options:NSJSONReadingMutableLeaves error:nil];

1
คุณต้องระบุNSJSONReadingMutableContainersสำหรับกรณีการใช้งานในคำถามนี้
Nikolai Ruhe
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.