2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

循環参照?強参照?なにそれ?

Posted at

この記事で得られる情報

  • Swiftの強参照についてふわっと理解できる
  • SwiftのARCについてもふわっと理解できる
  • Swiftの循環参照についてもふわっと理解できる
  • Swiftでメモリリークしないように実装できるようになる(可能性が上がる)

初めに

コードレビューいただいた時によく

「ここ循環参照してるから、片方弱参照にして」

なんて指摘をもらいます。

その度に「は?とりあえずweakつければいっか」ってやっていました。
しかし、これでは良くないと思い改めて循環参照について調べてきました。

強参照ってなに?

まずは強参照についてふわっと学びましょう。

強参照とは、これ使っているよっていうメモのようなものです。

例えば、部屋に箱が2つ置いてあり、箱Aには「箱B使用中」と書かれたメモが入っています。

このメモが参照と言われるもので、箱Aが箱Bを使っている(参照している)状態といえます。

通常「箱B使用中」と書かれたメモがあったら、それは強参照だと思ってもらって構いません。

「箱B使ってるから絶対に捨てるなよ」と強く言われているようなものですからね…

では実際にコードを見てみましょう。

class ClassA {
	var b: ClassB?
}

class ClassB {}

var boxA = ClassA()
var boxB = ClassB()

// 箱Aが箱Bを使っている(参照している)
boxA.b = boxB

これがSwiftにおける強参照となります。

ARCってなに?

先ほど同様、箱Aと箱Bが部屋に置いてあります。

あの箱邪魔だなあ、と思っていた佐藤さんはどちらの箱も捨てようとしますが

箱Aには「箱B使用中」というメモがありました。なので、箱Bを捨てることはできません。

しかし、佐藤さんは閃きます。「箱Aを捨ててしまえば、箱B使用中というメモも捨てることができるから、その瞬間箱Bも使用中じゃなくなるよね」

ということで佐藤さんは、箱Aを捨てた後に箱Bを捨てました。

現実世界でこんなことをしたら佐藤さんは非難されますが、Swiftの世界では「不要なものを勝手に捨ててくれるいいやつ」です。この佐藤さんのような存在をSwiftではARCと言います。

※ふわっと理解なので、ARCについて詳しく知りたい方は公式ドキュメントをご覧ください

循環参照ってなに?

循環参照とは、お互いがお互いを参照しあっていることを言います。

先ほどの例で例えると

箱Aと箱Bが部屋に置いてあります。

あの箱邪魔だなあ、と思っていた佐藤さんはどちらの箱も捨てようとしますが

箱Aには「箱B使用中」というメモがあり、箱Bには「箱A使用中」というメモがあります。

この場合、どちらの箱も使用中ということになり、箱を捨てることができません。

言い換えれば、お互いの箱がお互いの箱を使用(参照)しあっている状態で、捨てることができません。

これを循環参照と言います。

では実際にコードを見てみましょう。

class ClassA {
    var b: ClassB?
}

class ClassB {
    var a: ClassA?
}

var boxA: ClassA? = ClassA()
var boxB: ClassB? = ClassB()

// 箱Aが箱Bを使っている
boxA?.b = boxB
// 箱Bが箱Aを使っている
boxB?.a = boxA

この、お互いがお互いを使っている(正確には参照している)状態を循環参照と言います。

循環参照しているときは箱を捨てることができないので、部屋が片付けられない状態となります。

これをメモリリークと言います。

ちょっと難しいけど大事な話

先ほどのコードで循環参照を説明しました。

では、以下のように箱を捨てるとどうなるでしょうか。

class ClassA {
    var b: ClassB?
}

class ClassB {
    var a: ClassA?
}

var boxA: ClassA? = ClassA()
var boxB: ClassB? = ClassB()

// 箱Aが箱Bを使っている
boxA?.b = boxB
// 箱Bが箱Aを使っている
boxB?.a = boxA

// 箱Aを捨てる
boxA = nil
// 箱Bを捨てる
boxB = nil

「箱Aも箱Bも捨てたから部屋が綺麗!」となりそうですよね。

しかし実際は違います。

「箱Aが使っている箱Bが、箱Aを使っているから綺麗にならない」というのが正解で、これこそが循環参照の正体です。

「?????」

そうなるのも仕方ないです。これは私の説明が問題で、私は

「問題を身近なものに例えて説明する」

ことを大切にしているのですが、そうすると循環参照の説明が難しすぎるので、誤って認識されることを恐れずに説明させていただきました。

メモリリークしないようにするには?

では部屋を綺麗にするにはどうすれば良いか、ですが

「箱Aが使っている箱Bが、箱Aを使っている」というのが原因なので「箱Aが使っている箱B」を捨てます。

同じように「箱Bが使っている箱A」も捨ててしまいましょう。

具体的には以下のようにします。

class ClassA {
    var b: ClassB?
}

class ClassB {
    var a: ClassA?
}

var boxA: ClassA? = ClassA()
var boxB: ClassB? = ClassB()

// 箱Aが箱Bを使っている
boxA?.b = boxB
// 箱Bが箱Aを使っている
boxB?.a = boxA

// 箱Aの箱Bを捨てる
boxA?.b = nil
// 箱Bの箱Aを捨てる
boxB?.a = nil
// 箱Aを捨てる
boxA = nil
// 箱Bを捨てる
boxB = nil

こうすることで循環参照することなく、部屋を綺麗にすることができます。

弱参照でもできるよ

メモリリークを回避する方法として弱参照も挙げられます。むしろこっちのやり方がメインかもしれません

ここでも2つの箱AとBを例に説明します。

箱Aには「箱B使っているけど、箱Aを使わなくなったら箱Bも捨てていいよ」と書いてあります。

箱Bには「箱A使用中」と書いてあります。

佐藤さんは「箱A捨てたら箱Bも捨てれるじゃん♪」と閃き、部屋を綺麗にすることができました。

これが弱参照です。

では、実際に弱参照を使用したコードを見てみましょう。

class ClassA {
    weak var b: ClassB?
}

class ClassB {
    var a: ClassA?
}

var boxA: ClassA? = ClassA()
var boxB: ClassB? = ClassB()

// 箱Aが箱Bを使っている
boxA?.b = boxB
// 箱Bが箱Aを使っている
boxB?.a = boxA

// 箱Aを捨てる
boxA = nil
// 箱Bを捨てる
boxB = nil

このようにすると、箱Aを捨てると同時に箱Bも捨てられる状態となり、循環参照を回避することができます。

以上、この記事ではSwiftの強参照、ARC、循環参照についてふわっと理解できる内容を提供しました。

また、Swiftでメモリリークを防ぐための具体的な実装方法についても学びました。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?