【プログラミング初心者】Swift基礎~値型・参照型~

はじめに

クラスと構造体の違いについて説明します。
Swift基礎~構造体・クラス~という記事で使った構造体やクラスを使って説明していくのでまずそちらを参照して頂ければと思います。

今回の内容はC言語でいうところのポインタ、動的メモリ確保です。
今対象にしているSwiftなどの言語では意識することが少ない内容となります。
言い換えるとそこまで理解していなくてもSwiftならある程度実装できるので難しければ保留にしておいてもいいかもしれません。

メモリについて

前提知識として少しメモリの話をします。

まずメモリを扱うには確保開放という操作が必要になります。
そのままの意味ですが確保すると「今からこの領域を使います」というようにそのデータが対象のメモリを専有します。開放はその専有をやめることです。使用されているメモリは他から急に書き換えられると困るので開放されるまでは他からは使えません。

よく一口にメモリと言っていますが、OSはこのメモリをスタック領域ヒープ領域に分けて管理しています。
スタック領域とヒープ領域は、どちらも一時的に確保されるメモリ領域ですがそれぞれ管理方法が異なります。

  • スタック領域
    • メモリがA→B→Cの順で確保された場合、C→B→Aの順で解放される。メモリをいわゆるスタックで管理している。
      処理ブロック({}で囲まれたブロック)を抜けると解放されるという特長がある。
      ヒープよりも割り当てられている領域が小さい。
  • ヒープ領域
    • 確保されたメモリは明示的に解放しないとずっと留まり続ける。そのため気をつけていなければメモリリークを引き起こしクラッシュの原因になる。
      自由なサイズのメモリを確保できるというのが特長。

変数の参照先は全てスタックに格納された値です。
ここまでは前提知識として覚えておいてください。

クラスと構造体の違い

クラスと構造体は若干プログラムの定義の仕方が異なる部分もありますが基本的には同じように扱えます。
ですが根本的に異なる部分があります。
それは実体化したときのメモリの取られ方です。

構造体、クラスはそれぞれ値型参照型と呼ばれます。

値型

まず値型と呼ばれる構造体について。
Swift基礎~構造体・クラス~で作ったCompany構造体を例にして説明していきます。
以前変数のメモリの取られ方について説明しました。
このときはIntを題材にしましたが、定義を見ればわかりますが実はIntも構造体です。
なのでメモリの長さは異なりますが、以前説明したメモリの取られ方と基本的には同じです。
詳細な取られ方はわかりませんが構造体は以下のようなイメージでメモリが確保されます。

値型、つまり構造体の場合値の全てがスタック領域に確保されます。
例えば以下のような宣言をした場合

var company: Company = Company(name: "株式会社山田商事",
                               president: "山田太郎",
                               earnings: 1000000,
                               departments: [])

以下のようなイメージになります。

これは特に疑問なくイメージできるかと思います。

今度は以下の場合を見てみましょう。

var company: Company = Company(name: "株式会社山田商事",
                               president: "山田太郎",
                               earnings: 1000000,
                               departments: [])
var tmpCompany = company

この場合はどうでしょう?
これもスタックに確保されるのですが、新しく確保された領域にコピーされた値が格納されます。

値型の場合変数には値そのものが入っています。
そのため新しくtmpCompanyを宣言した場合もcompanyが代入されます。

確認してみましょう。
以下のコードで出力を見てみます。

var company: Company = Company(name: "株式会社山田商事",
                               president: "山田太郎",
                               earnings: 1000000,
                               departments: [developDepartment, salesDepartment])
var tmpCompany = company
company.name = "nameを更新"
print("company: " + company.name)
print("tmpCompany: " + tmpCompany.name)
company: nameを更新
tmpCompany: 株式会社山田商事

tmpCompany.nameが変更されていないことがわかりますね。
つまり別の領域として確保されたということです。

値型の場合スタックに値がコピーされ続けるのでデータ量の多い構造体の場合メモリを大きく食いつぶす可能性があります。
スタック領域はヒープ領域に比べて容量が小さいのですぐ使い切ってしまいます。
この場合スタックを使い切ってしまったエラーをスタックオーバーフローといいます。
プログラムは当然クラッシュします。

参照型

一方参照型と呼ばれるクラスはヒープ領域に確保されます。
変数は全てスタック領域に確保された値と先述しました。
では参照型の変数にはどんな値が確保されるのでしょうか?

答えはヒープに確保された領域の先頭アドレスです。

Swift基礎~構造体・クラス~作ったHumanクラスを例にして説明します。

以下のコードでオブジェクトを生成し変数に格納します。

var human = Human(name: "山田太郎", age: 25)

クラスの場合ヒープ領域に確保されると言いましたが、その確保のされ方は値型がスタックに確保される時と同じです。
違うのはHuman(name: "山田太郎", age: 25)が返す値が生成された領域の先頭アドレスということだけです。
つまりhumanにはオブジェクトのアドレスが格納されています。
図で表すと以下の様になります。

このように値そのものではなく、オブジェクトへの参照を変数に格納することから参照型と呼ばれています。
(C言語ではポインタ型と言われ、この場合Humanポインタ型と呼びます)

では以下のようにhumanを新しい変数に格納した場合はどのようになるでしょう?

var human = Human(name: "山田太郎", age: 25)
var tmpHuman = human

何度か言っているように変数はスタックに確保されます。
そして値型どうように変数宣言をすると新しくスタック領域に値のコピーが作成されます。
つまり以下のようになります。

このように変数にはアドレスという値がコピーされ、実際指し示すアドレスは同じオブジェクトなのでhumantmpHumanは同じものと見なすことができます。

確認してみましょう。
以下のコードで出力を見てみます。

var human = Human(name: "山田太郎", age: 25)
var tmpHuman = human
human.name = "nameを更新"
print("human: " + human.name)
print("tmpHuman: " + tmpHuman.name)
human: nameを更新
tmpHuman: nameを更新

humanを更新するとtmpHumanも一緒に更新されました。
つまり同一オブジェクトを参照していることがわかりますね。

このように参照型の場合は変数に代入するときアドレスのみがコピーされます。
これを浅いコピーという意味でシャローコピー(shallow copy)と言います。
反対に値型の場合内部のメンバ変数全てが別領域に同じ値としてコピーされるので、深いコピーディープコピー(deep copy)と呼びます。

自分がやっている実装はshallow copyなのかdeep copyなのかをしっかり意識しておかなければ、いつの間にか書き換わっていたり書き換えたと思っていたのに書き換わっていなかったということになりかねません。
気をつけて実装してください。

ガベージコレクション

少し話が変わります。
冒頭のヒープ領域の説明で以下のように説明しました。

確保されたメモリは明示的に解放しないとずっと留まり続ける。

C言語の場合不要になった時free(変数)というように明示的に開放処理を行います。

Swiftではどうやって解放すればいいのでしょうか?
結論から言うとSwiftでは解放する必要がありません。
というよりはシステム的に自動的に解放されるようになっています。

この自動で解放してくれる仕組みのことをガベージコレクション(garbage collection:GC)と呼びます。

GCではヒープで確保した領域に対して参照カウンタというものを持ちます。
そのままの意味で何個の変数から参照されているのかカウントするものです。

例えば先程使った例でいうと

var human = Human(name: "山田太郎", age: 25)
var tmpHuman = human

というように2つの変数から参照しました。
この段階で参照カウンタは2となります。

ここから

tmpHuman = nil

とします。
nilはどこのアドレスも指していないという意味です。
そうすると参照カウンタが-1され1となります。
この参照カウンタが0になったタイミングでオブジェクトは解放されます。

これがGCという機能です。

またtmpHuman = nilのようにしなくても問題ありません。
変数は全てスタック、スタックは処理ブロックを抜けると解放されると説明しました。

つまり{}を抜けると変数は解放され、参照カウントが自動的に-1されるというわけです。
そのためSwiftではあまり解放を意識する機会はありません。

ITの基本知識として押さえる程度でいいかと思います。

最後に

今回クラスと構造体の違いについて説明しました。
このあたりは知らないと思わぬバグを引き起こす原因になりうるので知っていてほしい内容ではあります。
配列などは参照型なのか値型なのか言語によって分かれるところで、新しい言語を触るとよくバグる部分だったりします。

ですが意外とプログラマの中にもあまりしっかりと理解せずコーディングしている人も多いように感じます。
最近の言語は便利になったため知らなくてもコーディングできてしまうためかと思います。
なので最初のうちはわからなくても問題ないかと思います。

ですがいずれ身につけてもらえるとプログラムを深く理解できるようになるかと思います。

コメント

タイトルとURLをコピーしました