Sao chép sâu một NSArray


119

Có chức năng tích hợp nào cho phép tôi sao chép sâu NSMutableArraykhông?

Tôi nhìn xung quanh, một số người nói [aMutableArray copyWithZone:nil]hoạt động như bản sao sâu. Nhưng tôi đã thử và nó có vẻ là một bản sao nông cạn.

Ngay bây giờ tôi đang thực hiện thủ công sao chép với một forvòng lặp:

//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];
            }
        }
    }
}

nhưng tôi muốn một giải pháp gọn gàng hơn, ngắn gọn hơn.


44
@Genericrich bản sao sâu và nông là những thuật ngữ được định nghĩa khá rõ trong phát triển phần mềm. Google.com có ​​thể trợ giúp
Andrew Grant,

1
có thể một số nhầm lẫn là do hành vi của các -copytập hợp bất biến đã thay đổi giữa Mac OS X 10.4 và 10.5: developer.apple.com/library/mac/releasenotes/Cocoa/… (cuộn xuống "Tập hợp bất biến và hành vi sao chép")
user102008

1
@AndrewGrant Suy nghĩ sâu hơn và về sự tôn trọng, tôi không đồng ý rằng bản sao sâu là một thuật ngữ được định nghĩa rõ ràng. Tùy thuộc vào nguồn bạn đọc, không rõ liệu việc đệ quy không giới hạn vào cấu trúc dữ liệu lồng nhau có phải là yêu cầu của hoạt động 'bản sao sâu' hay không. Nói cách khác, bạn sẽ nhận được những câu trả lời mâu thuẫn về việc liệu hoạt động sao chép tạo ra một đối tượng mới có các thành viên là bản sao cạn của các thành viên của đối tượng gốc có phải là hoạt động 'sao chép sâu' hay không. Xem stackoverflow.com/a/6183597/1709587 để biết một số thảo luận về điều này (trong ngữ cảnh Java, nhưng nó có liên quan giống nhau).
Mark Amery

@AndrewGrant Tôi phải sao lưu @MarkAmery và @Genericrich. Một bản sao sâu được xác định rõ nếu lớp gốc được sử dụng trong một tập hợp và tất cả các phần tử của nó đều có thể sao chép được. Đây không phải là trường hợp của NSArray (và các bộ sưu tập objc khác). Nếu một phần tử không triển khai copy, cái gì sẽ được đưa vào "bản sao sâu"? Nếu phần tử là một tập hợp khác, copykhông thực sự mang lại một bản sao (của cùng một lớp). Vì vậy, tôi nghĩ rằng việc tranh luận về loại bản sao muốn có trong trường hợp cụ thể là hoàn toàn hợp lệ.
Nikolai Ruhe

@NikolaiRuhe Nếu một phần tử không triển khai NSCopying/ -copythì nó không thể sao chép được— vì vậy bạn đừng bao giờ cố tạo bản sao của nó, vì đó không phải là khả năng mà nó được thiết kế để có. Về cách triển khai của Cocoa, các đối tượng không thể sao chép thường có một số trạng thái phụ trợ C mà chúng được gắn vào, vì vậy việc hack bản sao trực tiếp của đối tượng có thể dẫn đến điều kiện chủng tộc hoặc tệ hơn. Vì vậy, để trả lời "những gì sẽ được đưa vào 'bản sao sâu'" - Một tham chiếu được giữ lại. Thứ duy nhất bạn có thể đặt ở bất cứ đâu khi bạn có một NSCopyingvật không phải là đối tượng.
Slipp D. Thompson,

Câu trả lời:


210

Như tài liệu của Apple về các bản sao sâu nói rõ:

Nếu bạn chỉ cần bản sao sâu một cấp :

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

Đoạn mã trên tạo một mảng mới có các thành viên là bản sao cạn của các thành viên của mảng cũ.

Lưu ý rằng nếu bạn cần sao chép sâu toàn bộ cấu trúc dữ liệu lồng nhau - cái mà các tài liệu của Apple được liên kết gọi là bản sao sâu thực sự - thì cách làm này sẽ không đủ. Vui lòng xem các câu trả lời khác ở đây cho điều đó.


Có vẻ là câu trả lời chính xác. API cho biết mỗi phần tử nhận được một thông báo [element copyWithZone:], có thể là những gì bạn đang thấy. Nếu thực tế bạn thấy rằng việc gửi [NSMutableArray copyWithZone: nil] không phải là bản sao sâu, thì một mảng mảng có thể không sao chép chính xác bằng phương pháp này.
Ed Marty

7
Tôi không nghĩ rằng điều này sẽ hoạt động như mong đợi. Từ tài liệu của Apple: "Phương thức copyWithZone: thực hiện sao chép nông . Nếu bạn có tập hợp độ sâu tùy ý, việc chuyển YES cho tham số cờ sẽ thực hiện một bản sao bất biến của mức đầu tiên bên dưới bề mặt. Nếu bạn chuyển KHÔNG, khả năng thay đổi của cấp độ đầu tiên không bị ảnh hưởng. Trong cả hai trường hợp, khả năng biến đổi của tất cả các cấp độ sâu hơn đều không bị ảnh hưởng. "Câu hỏi SO liên quan đến các bản sao có thể thay đổi sâu.
Joe D'Andrea

7
đây KHÔNG PHẢI là một bản sao sâu
Daij-Djan

9
Đây là một câu trả lời không đầy đủ. Điều này dẫn đến một bản sao sâu một cấp. Nếu có nhiều kiểu phức tạp hơn trong Mảng, nó sẽ không cung cấp bản sao sâu.
Cameron Lowell Palmer

7
Đây có thể là một bản sao sâu, tùy thuộc vào cách copyWithZone:được triển khai trên lớp nhận.
defos1

62

Cách duy nhất tôi biết để dễ dàng thực hiện việc này là lưu trữ và sau đó hủy lưu trữ ngay mảng của bạn. Nó giống như một chút hack, nhưng thực sự được đề xuất rõ ràng trong Tài liệu Apple về việc sao chép bộ sưu tập , trong đó nêu rõ:

Nếu bạn cần một bản sao sâu đích thực, chẳng hạn như khi bạn có một mảng mảng, bạn có thể lưu trữ và sau đó hủy lưu trữ bộ sưu tập, với điều kiện tất cả nội dung đều tuân theo giao thức NSCoding. Ví dụ về kỹ thuật này được hiển thị trong Liệt kê 3.

Liệt kê 3 Một bản sao sâu đích thực

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

Điểm bắt buộc là đối tượng của bạn phải hỗ trợ giao diện NSCoding, vì giao diện này sẽ được sử dụng để lưu trữ / tải dữ liệu.

Phiên bản Swift 2:

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

12
Sử dụng NSArchiver và NSUnarchiver là một giải pháp rất nặng về hiệu suất, nếu các mảng của bạn lớn. Viết một phương thức danh mục NSArray chung sử dụng giao thức NSCopying sẽ thực hiện thủ thuật, gây ra một 'giữ lại' đơn giản các đối tượng bất biến và 'bản sao' thực của các đối tượng có thể thay đổi.
Nikita Zhuk

Thật tốt khi thận trọng về chi phí, nhưng liệu NSCoding có thực sự đắt hơn NSCopying được sử dụng trong initWithArray:copyItems:phương pháp này không? Giải pháp lưu trữ / hủy lưu trữ này có vẻ rất hữu ích, xem xét có bao nhiêu lớp điều khiển tuân theo NSCoding nhưng không tuân theo NSCopying.
Wienke

Tôi thực sự khuyên bạn không bao giờ sử dụng cách tiếp cận này. Việc tuần tự hóa không bao giờ nhanh hơn chỉ sao chép bộ nhớ.
Brett

1
Nếu bạn có các đối tượng tùy chỉnh, hãy đảm bảo triển khai encodeWithCoder và initWithCoder để tuân thủ giao thức NSCoding.
user523234

Rõ ràng là ý kiến ​​của lập trình viên được khuyến khích khi sử dụng tuần tự hóa.
VH-NZZ

34

Sao chép theo mặc định cung cấp một bản sao nông

Đó là bởi vì việc gọi copygiống như copyWithZone:NULLcòn gọi là sao chép với vùng mặc định. Cuộc copygọi không dẫn đến một bản sao sâu. Trong hầu hết các trường hợp, nó sẽ cung cấp cho bạn một bản sao cạn, nhưng trong mọi trường hợp, nó phụ thuộc vào lớp. Để thảo luận kỹ lưỡng, tôi giới thiệu Chủ đề Lập trình Bộ sưu tập trên trang web Nhà phát triển của Apple.

initWithArray: CopyItems: cung cấp bản sao sâu một cấp

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

NSCoding là cách Apple đề xuất để cung cấp một bản sao sâu

Đối với một bản sao sâu thực sự (Mảng của Mảng), bạn sẽ cần NSCodingvà lưu trữ / hủy lưu trữ đối tượng:

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

1
Chính xác. Bất kể mức độ phổ biến hay các trường hợp cạnh được tối ưu hóa sớm về bộ nhớ, hiệu suất. Ngoài ra, bản hack ser-derser cho bản sao sâu này được sử dụng trong nhiều môi trường ngôn ngữ khác. Trừ khi có sự loại trừ đối tượng, điều này đảm bảo một bản sao sâu tốt hoàn toàn tách biệt với bản gốc.

1
Dưới đây là một ít rottable của Apple doc url developer.apple.com/library/mac/#documentation/cocoa/conceptual/...

7

Đối với Văn phòng phẩm

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

Đối với mảng

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


4

Không, không có thứ gì đó được tích hợp sẵn trong các khuôn khổ cho việc này. Các bộ sưu tập ca cao hỗ trợ các bản sao nông (với copyhoặc các arrayWithArray:phương pháp) nhưng thậm chí không nói về khái niệm bản sao sâu.

Điều này là do "bản sao sâu" bắt đầu trở nên khó xác định khi nội dung của bộ sưu tập của bạn bắt đầu bao gồm các đối tượng tùy chỉnh của riêng bạn. "Bản sao sâu" có nghĩa là mọi đối tượng trong biểu đồ đối tượng là một tham chiếu duy nhất so với mọi đối tượng trong biểu đồ đối tượng gốc?

Nếu có một số NSDeepCopyinggiao thức giả định , bạn có thể thiết lập điều này và đưa ra quyết định trong tất cả các đối tượng của mình, nhưng rất tiếc là không có. Nếu bạn đã kiểm soát hầu hết các đối tượng trong biểu đồ của mình, bạn có thể tự tạo và triển khai giao thức này, nhưng bạn cần thêm một danh mục vào các lớp Foundation nếu cần.

Câu trả lời của @ AndrewGrant đề xuất việc sử dụng lưu trữ / hủy lưu trữ có khóa là một cách không hiệu quả nhưng đúng và rõ ràng để đạt được điều này cho các đối tượng tùy ý. Cuốn sách này thậm chí còn đi xa hơn vì vậy đề xuất thêm một danh mục cho tất cả các đối tượng thực hiện chính xác điều đó để hỗ trợ sao chép sâu.


2

Tôi có một cách giải quyết nếu cố gắng đạt được bản sao sâu cho dữ liệu tương thích JSON.

Đơn giản chỉ cần mất NSDatacủa NSArraysử dụng NSJSONSerializationvà sau đó tái JSON Object, điều này sẽ tạo ra một bản sao hoàn chỉnh mới và trong lành của NSArray/NSDictionaryvới sự tham khảo bộ nhớ mới của họ.

Nhưng hãy đảm bảo rằng các đối tượng của NSArray / NSDictionary và các đối tượng con của chúng phải có thể tuần tự hóa JSON.

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

1
Bạn phải chỉ định NSJSONReadingMutableContainerscho trường hợp sử dụng trong câu hỏi này.
Nikolai Ruhe
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.