🔬

【SwiftData】DeleteRule 観測隊 (.cascade .nullify .deny .noAction)

2024/06/01に公開

1:1

片方向 A->B

サンプルコード

@Model
final class TestA {
    var name: String
    @Relationship(deleteRule: .cascade) var testB: TestB?
    //以下略
@Model
final class TestB {
    var name: String
    //以下略

A内のBにはBインスタンスを登録する。

A内のBの宣言 B内のAの宣言 Aを削除したとき Bを削除したとき
.cascade - A消える
B消える
問題なし
A消えない
B消える
A内のBにアクセスするとEXC_BREAKPOINT
.nullify - A消える
B消えない
問題なし
A消えない
B消える
A内のBにアクセスするとEXC_BREAKPOINT
.deny - A消える
B消えない
エラー大量、実行は継続
A消えない
B消える
A内のBにアクセスするとEXC_BREAKPOINT
.noAction - A消える
B消えない
問題なし
A消えない
B消える
A内のBにアクセスするとEXC_BREAKPOINT

双方向 C<->D

サンプルコード

@Model
final class TestC {
    var name: String
    @Relationship(deleteRule: .cascade) var testD: TestD?
    //以下略
@Model
final class TestD {
    var name: String
    @Relationship(deleteRule: .cascade) var testC: TestC?
    //以下略

C内のDにはDインスタンスを登録する。

C内のDの宣言 D内のCの宣言 Cを削除したとき Dを削除したとき
.cascade .cascade 両方消える
問題なし
両方消える
問題なし
.cascade .nullify 両方消える
問題なし
C消えない
D消える
問題なし
.cascade .deny 両方消える
問題なし
EXC_BAD_ACCESS
.cascade .noAction 両方消える
問題なし
EXC_BAD_ACCESS
.nullify .nullify C消える
D消えない
問題なし
C消えない
D消える
問題なし
.nullify .deny C消える
D消えない
問題なし
EXC_BAD_ACCESS
.nullify .noAction C消える
D消えない
問題なし
EXC_BAD_ACCESS
.deny .deny EXC_BAD_ACCESS EXC_BAD_ACCESS
.deny .noAction EXC_BAD_ACCESS EXC_BAD_ACCESS
.noAction .noAction EXC_BAD_ACCESS EXC_BAD_ACCESS

1:N

片方向 E->F

サンプルコード

@Model
final class TestE {
    var name: String
    @Relationship(deleteRule: .noAction) var testF: [TestF] = []
    //以下略
@Model
final class TestF {
    var name: String
    //以下略

E内のFに2つ入れる。

E内のFの宣言 F内のEの宣言 Eを削除したとき Fを削除したとき
.cascade - E消える
F消える
問題なし
F消える
EのFにアクセスするとEXC_BREAKPOINT
.nullify - E消える
F消えない
問題なし
F消える
EのFにアクセスするとEXC_BREAKPOINT
.deny - E消える
F消えない
エラー大量
F消える
EのFにアクセスするとEXC_BREAKPOINT
.noAction - E消える
F消えない
問題なし
F消える
EのFにアクセスするとEXC_BREAKPOINT

片方向 G<-H

サンプルコード

@Model
final class TestG {
    var name: String
    //以下略
@Model
final class TestH {
    var name: String
    @Relationship(deleteRule: .cascade) var testG: TestG?
    //以下略

複数のHから同じGに接続。

G内のHの宣言 H内のGの宣言 Gを削除したとき Hを削除したとき
- .cascade G消える
HのGにアクセスするとEXC_BREAKPOINT
G消える
H消える
もうひとつのHのGにアクセスするとEXC_BREAKPOINT
- .nullify G消える
HのGにアクセスするとEXC_BREAKPOINT
G消えない
H消える
問題なし
- .deny G消える
HのGにアクセスするとEXC_BREAKPOINT
G消えない
H消える
エラー大量、実行は継続
- .noAction G消える
HのGにアクセスするとEXC_BREAKPOINT
G消えない
H消える
問題なし

双方向 I<->J

サンプルコード

@Model
final class TestI {
    var name: String
    @Relationship(deleteRule: .noAction) var testJ: [TestJ] = []
    //以下略
@Model
final class TestJ {
    var name: String
    @Relationship(deleteRule: .noAction) var testI: TestI?
    //以下略

複数のJから同じIに接続。

I内のJの宣言 J内のIの宣言 Iを削除したとき Jを削除したとき
.cascade .cascade I消える
J全て消える
問題なし
J消える
I消える
Iに関連する他のJ消える
問題なし
.cascade .nullify I消える
J全て消える
問題なし
消したJだけ消える
I消えない
Iに関連する他のJ消えない
問題なし
.cascade .deny I消える
J全て消える
問題なし
消したJだけ消える
I消えない
Iに関連する他のJ消えない
エラー大量、実行は継続
.cascade .noAction I消える
J全て消える
問題なし
消したJだけ消える
I消えない
Iに関連する他のJ消えない
IのJにアクセスするとEXC_BREAKPOINT
.nullify .nullify I消える
J消えない
問題なし
消したJだけ消える
I消えない
Iに関連する他のJ消えない
問題なし
.nullify .deny I消える
J消えない
問題なし
消したJだけ消える
I消えない
Iに関連する他のJ消えない
エラー大量、実行は継続
.nullify .noAction I消える
J消えない
問題なし
消したJだけ消える
I消えない
Iに関連する他のJ消えない
IのJにアクセスするとEXC_BREAKPOINT
.deny .deny I消える
J消えない
エラー大量、実行は継続
消したJだけ消える
I消えない
Iに関連する他のJ消えない
エラー大量、実行は継続
.deny .noAction I消える
J消えない
エラー大量、実行は継続
消したJだけ消える
I消えない
Iに関連する他のJ消えない
IのJにアクセスするとEXC_BREAKPOINT
.noAction .noAction I消える
J消えない
JのIにアクセスするとEXC_BREAKPOINT
消したJだけ消える
I消えない
Iに関連する他のJ消えない
IのJにアクセスするとEXC_BREAKPOINT

N:N

片方向参照 K->L

サンプルコード

@Model
final class TestK {
    var name: String
    @Relationship(deleteRule: .cascade) var testL: [TestL] = []
    //以下略
@Model
final class TestL {
    var name: String
    //以下略

K内のLに2つ入れる。

K内のLの宣言 L内のKの宣言 Kを削除したとき Lを削除したとき
.cascade - K消える
L消える
問題なし
L消える
KのLにアクセスするとEXC_BREAKPOINT
.nullify - K消える
L消えない
問題なし
L消える
KのLにアクセスするとEXC_BREAKPOINT
.deny - K消える
L消えない
エラー大量
L消える
KのLにアクセスするとEXC_BREAKPOINT
.noAction - K消える
L消えない
問題なし
L消える
KのLにアクセスするとEXC_BREAKPOINT

双方向参照 inverse設定なし O<->P

サンプルコード

@Model
final class TestO {
    var name: String
    @Relationship(deleteRule: .cascade) var testP: [TestP] = []
    //以下略
@Model
final class TestP {
    var name: String
    @Relationship(deleteRule: .cascade) var testO: [TestO] = []
    //以下略

2つのOと2つのPがすべて組み合わさっている。

O内のPの宣言 P内のOの宣言 Oを削除したとき Pを削除したとき
.cascade .cascade 連鎖すべて消える 連鎖すべて消える
.cascade .nullify 消したO消える
消したOと繋がっているPは消える
消えたPと繋がっている他のOは消えない
残ったOのPにアクセスするとEXC_BREAKPOINT
消したP消える
消したPと繋がっているOは消えない
Oの消えたPにアクセスするとEXC_BREAKPOINT
.cascade .deny 消したO消える
消したOと繋がっているPは消える
消えたPと繋がっている他のOは消えない
エラー大量、実行継続
消したP消える
消したPと繋がっているOは消えない
エラー大量、実行継続
.cascade .noAction 消したO消える
消したOと繋がっているPは消える
消えたPと繋がっている他のOは消えない
残ったOのPにアクセスするとEXC_BREAKPOINT
消したP消える
消したPと繋がっているOは消えない
Oの消したPにアクセスするとEXC_BREAKPOINT
.nullify .nullify 消したO消える
消したOと繋がっているPは消えない
Pの消したOにアクセスするとEXC_BREAKPOINT
消したP消える
消したPと繋がっているOは消えない
Oの消したPにアクセスするとEXC_BREAKPOINT
.nullify .deny 消したO消える
消したOと繋がっているPは消えない
Pの消したOにアクセスするとEXC_BREAKPOINT
消したP消える
消したPと繋がっているOは消えない
エラー大量、実行継続
.nullify .noAction 消したO消える
消したOと繋がっているPは消えない
Pの消したOにアクセスするとEXC_BREAKPOINT
消したP消える
消したPと繋がっているOは消えない
Oの消したPにアクセスするとEXC_BREAKPOINT
.deny .deny 消したO消える
消したOと繋がっているPは消えない
エラー大量、実行継続
消したP消える
消したPと繋がっているOは消えない
エラー大量、実行継続
.deny .noAction 消したO消える
消したOと繋がっているPは消えない
エラー大量、実行継続
消したP消える
消したPと繋がっているOは消えない
Oの消したPにアクセスするとEXC_BREAKPOINT
.noAction .noAction 消したO消える
消したOと繋がっているPは消えない
Pの消したOにアクセスするとEXC_BREAKPOINT
消したP消える
消したPと繋がっているOは消えない
Oの消したPにアクセスするとEXC_BREAKPOINT

双方向参照 inverse設定付き Q<->R

サンプルコード

@Model
final class TestQ {
    var name: String
    @Relationship(deleteRule: .cascade, inverse: \TestR.testQ) var testR: [TestR] = []
    //以下略
@Model
final class TestR {
    var name: String
    @Relationship(deleteRule: .cascade) var testQ: [TestQ] = []
    //以下略

2つのQと2つのRがすべて組み合わさっている。

Q内のRの宣言 R内のQの宣言 Qを削除したとき Rを削除したとき
.cascade .cascade 連鎖すべて消える 連鎖すべて消える
.cascade .nullify 消したQ消える
消したQと繋がっているRは消える
消えたRと繋がっている他のQは消えない
問題なし
消したR消える
消したRと繋がっているQは消えない
問題なし
.cascade .deny 消したQ消える
消したQと繋がっているRは消える
消えたRと繋がっている他のQは消えない
エラー大量、実行継続
消したR消える
消したRと繋がっているQは消えない
エラー大量、実行継続
.cascade .noAction 消したQ消える
消したQと繋がっているRは消える
消えたRと繋がっている他のQは消えない
残ったQのRにアクセスするとEXC_BREAKPOINT
消したR消える
消したRと繋がっているQは消えない
Qの消したRにアクセスするとEXC_BREAKPOINT
.nullify .cascade 消したQ消える
消したQと繋がっているRは消えない
問題なし
消したR消える
消したRと繋がっているQは消える
消えたQと繋がっている他のRは消えない
問題なし
.nullify .nullify 消したQ消える
消したQと繋がっているRは消えない
問題なし
消したR消える
消したRと繋がっているQは消えない
問題なし
.nullify .deny 消したQ消える
消したQと繋がっているRは消えない
問題なし
消したR消える
消したRと繋がっているQは消えない
エラー大量、実行継続
.nullify .noAction 消したQ消える
消したQと繋がっているRは消えない
問題なし
消したR消える
消したRと繋がっているQは消えない
Qの消したRにアクセスするとEXC_BREAKPOINT
.deny .cascade 消したQ消える
消したQと繋がっているRは消えない
エラー大量、実行継続
消したR消える
消したRと繋がっているQは消える
消えたQと繋がっている他のRは消えない
エラー大量、実行継続
.deny .nullify 消したQ消える
消したQと繋がっているRは消えない
エラー大量、実行継続
消したR消える
消したRと繋がっているQは消えない
問題なし
.deny .deny 消したQ消える
消したQと繋がっているRは消えない
エラー大量、実行継続
消したR消える
消したRと繋がっているQは消えない
エラー大量、実行継続
.deny .noAction 消したQ消える
消したQと繋がっているRは消えない
エラー大量、実行継続
消したR消える
消したRと繋がっているQは消えない
Qの消したRにアクセスするとEXC_BREAKPOINT
.noAction .cascade 消したQ消える
消したQと繋がっているRは消えない
Rの消したQにアクセスするとEXC_BREAKPOINT
消したR消える
消したRと繋がっているQは消える
消えたQと繋がっている他のRは消えない
残ったRのQにアクセスするとEXC_BREAKPOINT
.noAction .nullify 消したQ消える
消したQと繋がっているRは消えない
Rの消したQにアクセスするとEXC_BREAKPOINT
消したR消える
消したRと繋がっているQは消えない
問題なし
.noAction .deny 消したQ消える
消したQと繋がっているRは消えない
Rの消したQにアクセスするとEXC_BREAKPOINT
消したR消える
消したRと繋がっているQは消えない
エラー大量、実行継続
.noAction .noAction 消したQ消える
消したQと繋がっているRは消えない
Rの消したQにアクセスするとEXC_BREAKPOINT
消したR消える
消したRと繋がっているQは消えない
Qの消したRにアクセスするとEXC_BREAKPOINT

接続なしの状態について

それぞれのパターンで接続なし、つまり参照を表す変数が

  • nil
  • 配列が空

であれば、削除しても、削除したものだけが消えて、他に影響をあたえず問題ない。

まとめ、考察

denyが指定してあるものはUI上で消えてもデータは残っているかも

N対Nの両nullifyにするときはinverseの設定が必須

配列の要素を削除する時

ここまではインスタンスを丸ごと削除するときの挙動を見てきたが、N:1やN:Nのときは配列で他の要素を保持することもある。配列の要素を削除するときの挙動は
.nullify は想定通りの挙動をするが、.cascade.nullify の挙動をするようである。

他の記事

主に配列の要素を削除するときなど図入りで書きました。

https://zenn.dev/samekard_dev/articles/d0ed1582528f57

Discussion