甘いものが好きです

iOS App開発時に感じた疑問や課題、その他の雑感などを書いていきます。

NSManagedObjectオブジェクトをdeep copyする方法

NSManagedObjectオブジェクトをdeep copyする方法を考えてみた。NSManagedObjectはNSCopyingプロトコルに適合していない。AttributeやRelationshipを個別にチェックしてコピーしていく方法が無難だろうと思う。*1*2

具体的な手順としては次のようになる。

  1. コピー対象のNSManagedObjectオブジェクト(以下では「コピー元オブジェクト」と呼ぶ)と同じエンティティのNSManagedObjectオブジェクト(以下では「コピー先オブジェクト」と呼ぶ)を新規作成する。
  2. コピー元オブジェクトのAttributeを取得し、それぞれのコピーをコピー先オブジェクトのAttributeとして設定する。
  3. コピー元オブジェクトのRelationshipを取得し、それぞれのRelationshipに対し必要に応じて同エンティティのコピーを作成して、コピー先オブジェクトのRelationshipとして設定する。

この処理に対応するコードは次のようになる。

// 呼び出し元で保存する。
- (NSManagedObject *)copyCompanyObjectWithObjectID:(NSManagedObjectID *)objID context:(NSManagedObjectContext *)context {
    
    if (objID == nil) {
        NSLog(@"Failed to copy company object. Object ID is nil.");
        return nil;
    }
    if (context == nil) {
        NSLog(@"Failed to copy company object. Context is nil.");
        return nil;
    }
    
    // 1. 
    // コピー元オブジェクトと同じエンティティのNSManagedObjectオブジェクトを新規作成する。
    // fetchCompanyObjectWithIdentifier:context および insertNewCompanyObjectWithContext:の
    // 実装はここでは省略する。
    NSManagedObject *orgObj = [self fetchCompanyObjectWithIdentifier:objID context:context];
    NSManagedObject *newObj = [self insertNewCompanyObjectWithContext:context];
    
    // 2. 
    // コピー元オブジェクトのAttributeを取得し、
    // それらのコピーをコピー先オブジェクトのAttributeとして設定する。
    NSDictionary *attrDict = [[orgObj entity] attributesByName];
    for (NSString *key in [attrDict allKeys]) {
        id value = [orgObj valueForKey:key];
        [newObj setValue:value forKey:key];
    }
    
    // 3. 
    // コピー元オブジェクトのRelationshipを取得し、
    // それぞれのRelationshipに対し必要に応じて同エンティティのコピーを作成して、
    // コピー先オブジェクトのRelationshipとして設定する。
    NSDictionary *relDict = [[orgObj entity] relationshipsByName];
    for (NSString *key in [relDict allKeys]) {
        // ここでは例としてOrdered To-many Relationshipのひとつをコピーしている。
        if ([key isEqualToString:@"employees"]) {
            NSOrderedSet *employeeSet = [orgObj valueForKey:key];
            for (NSInteger i = 0; i < employeeSet.count; i++) {
                // copyEmployeeObjectWithObjectID:context:の実装はここでは省略するが、
                // copyCompanyObjectWithObjectID:context:と同様に定義しておけばよい。
                NSManagedObject *orgEmployeeObj = employeeSet[i];
                NSManagedObject *newEmployeeObj = [self copyEmployeeObjectWithObjectID:[orgEmployeeObj objectID] context:context];
                [newObj addEmployeesObject:newEmployeeObj];
            }
        }
    }
    
    return newObj;
}

*1:Fetched Propertyは普段扱わないのでここでは触れていない。

*2:もう少し効率的な書き方ができるかもしれないけれど……。