Swift 構造体とクラスの違い(初心者向け)

構造体は値型, クラスは参照型

つまり、構造体のインスタンスは値そのものを保持するのに対し、クラスのインスタンスは値が入っているメモリへの参照を保持します。 これらの違いは実際にプログラムを実行すると分かりやすいので確かめてみましょう。

まずは構造体から

struct Pokemon {
    var type: String
    var hp: Int
    var attack: Int
    var defense: Int
}

var pikatyu = Pokemon(type: "Electrical", hp: 100, attack: 60, defense: 40)
var metamon = Pokemon(type: "Normal", hp: 80, attack: 50,defense: 50)

metamon = pikatyu
metamon.attack = 100
print("metamonAttack = ", metamon.attack)
print("pikatyuAttack = ", pikatyu.attack)

実行結果

metamonAttack =  100
pikatyuAttack =  60

実行結果は上のようにmetamonattackは100に更新され、pikatyuは60のままです。まあ、自然な挙動ですね。

ではクラスではどうでしょうか? クラスで同様のコードを実行した時の挙動を示します。(ちなみに下のコードのinit()ように、クラスでは非オプショナル型プロパティは、宣言時に値を設定するか、イニシャライザの中で初期値を与える必要があります。)

class Pokemon {
    var type: String
    var hp: Int
    var attack: Int
    var defense: Int
    
    init(type: String, hp: Int, attack: Int, defense: Int){
        self.type = type
        self.hp = hp
        self.attack = attack
        self.defense = defense
    }
}

var pikatyu = Pokemon(type: "Electrical", hp: 100, attack: 60, defense: 40)
var metamon = Pokemon(type: "Normal", hp: 80, attack: 50,defense: 50)

metamon = pikatyu
metamon.attack = 100
print("metamonAttack = ", metamon.attack)
print("pikatyuAttack = ", pikatyu.attack)

実行結果

metamonAttack =  100
pikatyuAttack =  100

結果はmetamonpikatyu双方のattackが100となりました。もしmetamonattackだけ更新したかったのなら、これは意図した状態とは違いますね。 クラスでは、metamonpikatyuは同じメモリへの参照を保持しているので、metamon.attack = 100は双方の指しているメモリの中にあるattackプロパティの値を書き換える命令となり、pikatyu.attackも100という値を返すようになります。

(補足) 当然ですが下のコードように、pikatyumetamon別のインスタンスとして生成した場合には、同じPokemonクラスのインスタンスとはいえ、値は別物です。それぞれ値を格納するメモリは別のものが用意されます。ただ、上で見てきたような他のインスタンスをコピー(関数の引数なども含む)して生成したインスタンスと混在したり、処理が複雑になった場合には、混乱を招きそうですね。

class Pokemon {
    var type: String
    var hp: Int
    var attack: Int
    var defense: Int
    
    init(type: String, hp: Int, attack: Int, defense: Int){
        self.type = type
        self.hp = hp
        self.attack = attack
        self.defense = defense
    }
}

var pikatyu = Pokemon(type: "Electrical", hp: 100, attack: 60, defense: 40)
let metamon = Pokemon(type: "Normal", hp: 100, attack: 60, defense: 40)
metamon.attack = 100
print("metamonAttack = ", metamon.attack)
print("pikatyuAttack = ", pikatyu.attack)
metamonAttack =  100
pikatyuAttack =  60


これまで見てきたように、構造体のインスタンスは明示的に変更しない限りプロパティの値は変更されませんので、より安全な方法と言えます。その一方で、クラスではインスタンスがメモリへの参照のみ保持するので、ある値を複数のインスタンス同士で共有したいなどの事情がある場合はクラスを使うと良さそうです。