Swift

【Swift】struct(構造体)とは?|class(クラス)との違いを併せて,順に使い方を整理します!

記事内に商品プロモーションを含む場合があります

Swiftで開発をしてて,class(クラス)・struct(構造体)って聞くけど,どのように作成するの?
class(クラス)とstruct(構造体)の違いってなに?
この記事ではSwiftにおける構造体を順に整理しながら説明・整理します.

Swiftで開発していてわからなくなった際に,見に来てくれると嬉しいです!

struct(構造体)の完成形

今回のゴール

さっそくstruct(構造体)を完成させたときのコードを見てみましょう.

struct Town {
    var townName: String
    var population: Int
    var citizens: [String]
    var resources: [String: Int]

    init(townName: String, population: Int, citizens: [String], resources: [String: Int]) {
        self.townName = townName
        self.population = population
        self.citizens = citizens
        self.resources = resources
    }
    
    func fortify() {
        print("Defenses increased")
    }
}

var myTown = Town(townName: "California", population: 1000, citizens: ["Ohtani", "Kawamura"], resources: ["Coconuts": 100, "wheat": 70])
var anotherTown = Town(townName: "NewYork", population: 2300, citizens: ["Taylor", "Ariana"], resources: ["Orange": 90, "Apple": 20])

今回の最終ゴールはこのような形になります.
すこし複雑でそれぞれのコードが何を意味しているのでしょうか.
順に説明していきます!

struct(構造体)の作り方

① 構造体の作成

struct Town {
    var townName = "California"
    var population = 1000
    var citizens = ["Ohtani", "Kawamura"]
    var resources = ["Coconuts": 100, "wheat": 70]
}

var myTown = Town()

まずは,structを宣言します.
ここでは,構造体の書き方を確認できればOKです!

これにより,
myTown.townNameとすれば,California
myTown.citizensとすれば,[“Ohtani”, “Kawamura”]
のように構造体を使用できるようになります!

② メソッドの作成・理解

struct Town {
    var townName = "California"
    var population = 1000
    var citizens = ["Ohtani", "Kawamura"]
    var resources = ["Coconuts": 100, "wheat": 70]
    
    func fortify() {
        print("Defenses increased")
    }
}

var myTown = Town()

続いては,メソッドです.
ずばり,構造体に関連する関数 = メソッドです.
この関数では,構造体の中で使用するメリットがあまり有りませんが,
構造体が複雑になってくれるにつれて,非常に便利になってくる場合があります!

イニシャライザ (init) の作成と一般化

struct Town {
    var townName: String
    var population: Int
    var citizens: [String]
    var resources: [String: Int]

    init(name: String, peopleNum: Int, citizensList: [String], resourcesList: [String: Int]) {
        townName = name
        population = peopleNum
        citizens = citizensList
        resources = resourcesList
    }
    
    func fortify() {
        print("Defenses increased")
    }
}

var myTown = Town(name: "California", peopleNum: 1000, citizensList: ["Ohtani", "Kawamura"], resourcesList: ["Coconuts": 100, "wheat": 70])
var anotherTown = Town(name: "NewYork", peopleNum: 2300, citizensList: ["Taylor", "Ariana"], resourcesList: ["Orange": 90, "Apple": 20])

続いては,イニシャライザです.
先程の②までで,構造体の大まかな使い方については理解できたと思います.
しかし,街は街でもいろいろな種類の街がありますよね?
構造体は,大本の設計図の部分にあたるため,それぞれの街に対応できるようにする必要があります.
それが,イニシャライザ (init) です.

イメージは,initという関数を構造体の中で宣言構造体(設計図)を使いたいときに引数として,その街の特有のものを渡すといった流れです.
(厳密には,initは関数ではありません.役割や動作が若干異なります)
上記コードを見ると,理解しやすいかと思います!

struct Town {
    var townName: String
    var population: Int
    var citizens: [String]
    var resources: [String: Int]

    init(townName: String, population: Int, citizens: [String], resources: [String: Int]) {
        self.townName = townName
        self.population = population
        self.citizens = citizens
        self.resources = resources
    }
    
    func fortify() {
        print("Defenses increased")
    }
}

var myTown = Town(townName: "California", population: 1000, citizens: ["Ohtani", "Kawamura"], resources: ["Coconuts": 100, "wheat": 70])
var anotherTown = Town(townName: "NewYork", population: 2300, citizens: ["Taylor", "Ariana"], resources: ["Orange": 90, "Apple": 20])

先ほどまでのイニシャライザ (init)をもっとスタイリッシュにする方法があります!
プロパティを引数の名前を同じにしたい&同じにしたときのエラーを防ぐ

それは,selfを使うことです!
selfがついているものは,構造体のプロパティ名(最初に宣言した部分)を参照しています.

以上で,構造体は完成です!
ぜひ順に理解しながら,最終形に慣れていきましょう!

応用編にはなるのですが,
・実はinitはなくてもいい??
・では,initを用いるメリットとは??

については,こちらの記事で説明しています.
→→ 【Swift】initのメリット|カスタムイニシャライザとメンバーワイズイニシャライザの違い
より深くstruct(構造体)を理解したい方は,ぜひご覧ください!

struct(構造体)とclass(クラス)の違い

最後におまけです.
class(クラス)とstruct(構造体)の違いは何でしょう?

色々と違いはありますが,何点か簡単に説明します.

1. 値型 (struct) と 参照型 (class)

構造体 (struct)値型です.
これは,変数に構造体のインスタンスを代入すると,そのインスタンスがコピーされることを意味します.
一方で,クラス (class)参照型です.
これは,クラスのインスタンスを変数に代入すると,そのインスタンスの参照が渡され,同じオブジェクトを指し続けます.

struct PersonStruct {
    var name: String
}

class PersonClass {
    var name: String
    init(name: String) {
        self.name = name
    }
}

var structPerson1 = PersonStruct(name: "Alice")
var structPerson2 = structPerson1
structPerson2.name = "Bob"

print(structPerson1.name) // "Alice" (コピーなので元のインスタンスは変わらない)
print(structPerson2.name) // "Bob"

var classPerson1 = PersonClass(name: "Alice")
var classPerson2 = classPerson1
classPerson2.name = "Bob"

print(classPerson1.name) // "Bob" (参照を共有しているので、どちらも変わる)
print(classPerson2.name) // "Bob"

参照とコピーの違いは,上記のコード例を見るとわかりやすいと思います!

2. 継承の可否

クラス (class)継承が可能です.
つまり,あるクラスを基にして新しいクラスを作ることができます.
継承によって,基本クラスの機能を新しいクラスに追加したり、変更したりすることができます。

class Animal {
    var name: String
    init(name: String) {
        self.name = name
    }

    func makeSound() {
        print("Some sound")
    }
}

class Dog: Animal {
    override func makeSound() {
        print("Woof!")
    }
}

let dog = Dog(name: "Buddy")
dog.makeSound() // "Woof!"

一方,構造体 (struct) は継承できません.構造体は他の構造体やクラスを親として持つことができません.

3. ミュータビリティ (mutability) (型の違いによるもの)

構造体 (struct)値型です.一方で,クラス (class)参照型です.
そのため,型による違いとして,mutationがあります.

構造体 (struct) のインスタンスを変更するには,そのインスタンスがvarで宣言されていなければなりません.
また,構造体内のメソッドがプロパティを変更する場合,
そのメソッドにmutatingキーワードを付ける必要があります

一方で,クラス (class) はインスタンスがletであっても,インスタンス内のプロパティを変更できます(ただし、そのプロパティ自体がvarとして宣言されている場合)。

// struct
struct Counter {
    var value = 0

    // mutatingメソッドを使って値を変更
    mutating func increment() {
        value += 1
    }

    mutating func decrement() {
        value -= 1
    }
}

var counter = Counter() // varでインスタンス作成
counter.increment()      // 値を変更
print(counter.value)     // 1

// class
class CounterClass {
    var value = 0

    func increment() {
        value += 1
    }

    func decrement() {
        value -= 1
    }
}

let counter = CounterClass() // letでインスタンス作成
counter.increment()          // 値を変更
print(counter.value)         // 1

間違えて,structでletでインスタンスを作成した後に,値を変更しようとする場合や,
structでmutationをつけていない場合は,エラーが発生します...

それぞれの使い方・活用

class(クラス)とstruct(構造体)の違いは他にもありますが,何点か重要なものをピックアップしました.

では,それぞれをどのように使い分けるのか??
ざっくりまとめると,
値のコピーが重要,またはデータが不変(イミュータブル)である場合は構造体を使います.
→→ 例えば,座標やサイズ,範囲などのデータには構造体が適しています.

共有するオブジェクト複雑な継承関係が必要な場合はクラスを使います.
→→ 例えば,UI要素やデータモデルなど,複数の場所で同じオブジェクトを参照する必要がある場合にはクラスが適しています.

おわりに

このブログでは,SwiftやFlutterなどのプログラミング,応用情報技術レベルのコンピュータサイエンス・CSについて日々学びつつアウトプットしています.
間違いや改善点があれば,ご指摘いただけますと幸いです!