alt

こんな感じで、Album1つに対してTrackが複数つながったRelationshipをもっている

調査

removeFromEntity()とdelete()の違い

なんがねぇがと調べていたが、Album+CoreDataProperties.swiftファイル(エンティティを作ったら自動生成される)に

@objc(removeTracksObject:)
    @NSManaged public func removeFromTracks(_ value: Track)

というメソッドが登録されていた。remove?じゃあ以下のようなdelete()との違いは?今までCoreDataからデータを削除するのに使っていたのだが……

参考:

CoreDataをTableViewで扱う(リストの並び替え、削除との連携について)(Xcode12.4、Swift 5.3.2)(April 22, 2021)

static func delete(track: Track) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            abort()
        }

        let managedContext = appDelegate.persistentContainer.viewContext
        managedContext.delete(track) // delete()を呼んだ後もsaveContext()されるまでは確定されない
    }

swift - CoreData removeFrom vs delete - Stack Overflowにあるように

  • removeFromEntity()はリレーションのみ削除
  • delete()はデータ自体を削除

実装の雰囲気

したがって、1つのalbum: Albumに対してリレーションをもっているtracks: [Track]に対して、tracks[i]のリレーションを変更する場合、移動先のアルバムをtargetAlbum: Albumとして

album.removeFromTracks(tracks[i]) // 現在のAlbumとrelationshipを削除
tracks[i].index = Int16(targetAlbum.count) // (CoreData上は並び順が保存されないのでAttributeとしてindexを加えている場合、`tracks[i].index`を変更する。この場合は移動先末尾に加えられることになる。)
targetAlbum.addToTracks(tracks[i]) // 移動先のAlbumに加える
tracks.remove(at: i) // albumもちのtracksからは削除

// 移動したtracks[i]より後ろの要素分のみ、CoreDataのindexを更新する
let filteredTracks = tracks.lazy.filter({ $0.index > Int16(i) })
for (index, filteredTracks) in zip(filteredTracks.indices, filteredTracks) { // filteredTracks.enumerated()ではindexが必ず0から始まってしまうのでzip()を使う
    filteredTracks.index = Int16(index)
}

細かくコメントを書いたのでとくに解説はなし。

まとめ

ググルの適当だったからか、直接リレーションを操作しているような記事には行き着かなかった。なので自分の思いつき。まあ無難な実装ではないか?