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が役立つかもしれません
アンドリューグラント

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 2013

@AndrewGrant @MarkAmeryと@Genericrichをバックアップする必要があります。コレクションで使用されるルートクラスとそのすべての要素がコピー可能であれば、ディープコピーは適切に定義されます。これは、NSArray(およびその他のobjcコレクション)には当てはまりません。要素がを実装しない場合、copy「ディープコピー」には何を配置する必要がありますか?要素が別のコレクションである場合、copy実際には(同じクラスの)コピーは生成されません。したがって、特定のケースで必要なコピーのタイプについて議論することは完全に有効だと思います。
Nikolai Ruhe

@NikolaiRuhe要素がNSCopying/を実装しない場合、-copyそれはコピー可能ではありません。そのため、そのコピーを作成しようとしないでください。これは、それが持つように設計された機能ではないためです。Cocoaの実装に関して、コピー不可能なオブジェクトは、関連付けられているCバックエンドの状態を持っていることが多いため、オブジェクトの直接コピーをハッキングすると、競合状態が発生したり、悪化したりする可能性があります。したがって、「「ディープコピー」に何を入れるか」と答えるには、保持された参照。非NSCopyingオブジェクトがあるときにどこにでも置くことができる唯一のもの。
Slipp D. Thompson 2017

回答:


210

ディープコピーに関するAppleのドキュメントでは、次のように明示的に述べています。

1レベルの深いコピーのみが必要な場合:

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

上記のコードは、メンバーが古い配列のメンバーの浅いコピーである新しい配列を作成します。

ネストされたデータ構造全体(リンクされたAppleのドキュメントが真のディープコピーと呼ぶもの)を深くコピーする必要がある場合、このアプローチでは不十分です。そのためのその他の回答はこちらをご覧ください。


正解のようです。APIは、各要素が[要素copyWithZone:]メッセージを取得すると述べています。実際に[NSMutableArray copyWithZone:nil]を送信してもディープコピーではない場合は、配列の配列がこのメソッドを使用して正しくコピーされない可能性があります。
Ed Marty、

7
これが期待どおりに機能するとは思いません。アップルのドキュメントから:「copyWithZone:メソッドは浅いコピーを実行します。任意の深さのコレクションがある場合、フラグパラメータにYESを渡すと、サーフェスの下の最初のレベルの不変のコピーが実行されます。NO を渡すと、最初のレベルは影響を受けません。どちらの場合でも、より深いすべてのレベルの可変性は影響を受けません
Joe D'Andrea

7
これは深いコピーではありません
Daij-Djan

9
これは不完全な答えです。これにより、1レベルのディープコピーが作成されます。配列内により複雑な型がある場合、深いコピーは提供されません。
Cameron Lowell Palmer

7
これcopyWithZone:、受信クラスでの実装方法に応じて、ディープコピーになる場合があります。
devios1 2014年

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を使用すると、パフォーマンスの面で非常に重いソリューションになります。NSCopyingプロトコルを使用する汎用のNSArrayカテゴリーメソッドを作成すると、トリックが実行され、不変オブジェクトの単純な「保持」と可変オブジェクトの実際の「コピー」が発生します。
Nikita Zhuk

費用に注意するのは良いことですが、NSCodingは、initWithArray:copyItems:メソッドで使用されるNSCopyingよりも本当に高価なのでしょうか?このアーカイブ/アーカイブ解除の回避策は、NSCodingに準拠していてNSCopyingに準拠していないコントロールクラスの数を考えると非常に便利なようです。
Wienke

このアプローチは絶対に使用しないことを強くお勧めします。シリアル化は、単にメモリをコピーするよりも速くはありません。
ブレット

1
カスタムオブジェクトがある場合は、必ずNSCodingプロトコルに準拠するようにencodeWithCoderとinitWithCoderを実装してください。
user523234 2013年

当然、シリアライゼーションを使用するときは、プログラマーの裁量が強く推奨されます。
VH-NZZ 2015

34

デフォルトでコピーすると、浅いコピーになります

これは、呼び出しcopycopyWithZone:NULLデフォルトゾーンでのコピーとも呼ばれているためです。copyコールは深いコピーにはなりません。ほとんどの場合それはあなたに浅いコピーを与えるでしょうが、いずれにせよそれはクラスに依存します。徹底的な議論のために、私はApple DeveloperサイトのCollections Programming Topicsをお勧めします。

initWithArray:CopyItems:1レベルのディープコピーを提供します

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

NSCoding ディープコピーを提供するためのアップル推奨の方法です

真のディープコピー(配列の配列)の場合NSCoding、オブジェクトが必要であり、オブジェクトをアーカイブ/アーカイブ解除します。

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

1
これは正しいです。メモリやパフォーマンスに関する人気や時期尚早に最適化されたエッジケースに関係なく。余談ですが、ディープコピーのためのこのser-derserハックは、他の多くの言語環境で使用されています。obj重複排除がない限り、これにより、元のディスクから完全に分離された適切なディープコピーが保証されます。

1
ここではあまりrottableアップルのドキュメントのURLのだdeveloper.apple.com/library/mac/#documentation/cocoa/conceptual/...は

7

Dictonaryの場合

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

アレイ用

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


4

いいえ、このためのフレームワークには何も組み込まれていません。Cocoaコレクションは(copyまたはarrayWithArray:メソッドを使用して)浅いコピーをサポートしますが、ディープコピーの概念についても話しません。

これは、コレクションのコンテンツに独自のカスタムオブジェクトが含まれるようになると、「ディープコピー」の定義が難しくなるためです。「ディープコピーは」どういう意味すべてのオブジェクトグラフ内のオブジェクトがへの一意の参照の相対的であるすべての元のオブジェクトグラフ内のオブジェクト?

架空のNSDeepCopyingプロトコルがあった場合、これを設定してすべてのオブジェクトで決定を下すことができますが、残念ながらありません。グラフ内のほとんどのオブジェクトを制御している場合は、このプロトコルを自分で作成して実装できますが、必要に応じてFoundationクラスにカテゴリを追加する必要があります。

キー付きアーカイブ/アーカイブ解除の使用を示唆する@A​​ndrewGrantの回答は、任意のオブジェクトに対してこれを達成するためのパフォーマンスは高くありませんが、正しくクリーンな方法です。この本はこれまでのところまで進んでいるので、ディープコピーをサポートするためにカテゴリをすべてのオブジェクトに追加することをお勧めします。


2

JSON互換データのディープコピーを実現しようとする場合の回避策があります。

単にJSONオブジェクトNSDataNSArray使用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.