Swift

【Swift】initのメリット|カスタムイニシャライザとメンバーワイズイニシャライザの違い

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

Swiftで開発をしてて,class(クラス)・struct(構造体)initを宣言するけど,どういった役割なの?
カスタムイニシャライザとメンバーワイズイニシャライザの違いってなに?
この記事ではSwiftでのstruct(構造体)を例にinitについて説明・整理します.

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

init(イニシャライザ)とは?

class(クラス)・struct(構造体)の基本・違い

まず初めに本記事では,class(クラス)・struct(構造体)の基本的な書き方はマスターしたけど,
initがなんなのか深く理解したい方
を対象としています.

class(クラス)・struct(構造体)の基本的な書き方はこちらの記事で記載しています!
→→ 【Swift】struct(構造体)とは?|class(クラス)との違いを併せて,順に使い方を整理します!

まだ,
class(クラス)・struct(構造体)を用いるときの書き方がよくわからない
class(クラス)・struct(構造体)の違いは??
っといった疑問を持つ方は,まず初めに上記記事を閲覧することをおすすめします!

カスタムイニシャライザとメンバーワイズイニシャライザの違い

さっそくstruct(構造体)における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])

// ============================================
// ② メンバーワイズイニシャライザ
struct Town {
    var townName: String
    var population: Int
    var citizens: [String]
    var resources: [String: Int]
    
    func fortify() {
        print("Defenses increased")
    }
}
var myTown = Town(townName: "California", population: 1000, citizens: ["Ohtani", "Kawamura"], resources: ["Coconuts": 100, "wheat": 70])

正解は,イニシャライザ を明示的に定義しているか否かです.
struct(構造体)をはじめて習ったときはとりあえずinitで初期化すると習ったかもしれませんが,実は,initを宣言しなくても問題はありません.

①のコードでは構造体 Town にカスタムイニシャライザを明示的に定義しています.
一方で,②のコードではイニシャライザを定義せずに
Swift が自動的に提供するメンバーワイズイニシャライザを使用しています.

わざわざinitを宣言しなくても,struct(構造体)は使用できるわけですね!

ちなみに,②のメンバーワイズイニシャライザの状態で,
デフォルトでストアドプロパティが入っているもの(townName等のすべてに値・初期値が与えられているもの)は,デフォルトイニシャライザとも呼ばれます.

では,initを宣言するメリットは??

Swift が自動的に提供するメンバーワイズイニシャライザを使用すれば,
わざわざinitを宣言しなくても,struct(構造体)は使用できます!
では,なぜinitを宣言する必要があるのでしょうか?

実は大きく4つのメリットがあります!

1. デフォルト値やカスタムロジックを追加できる

init を使用すると,各プロパティに対するデフォルト値を設定したり,
インスタンス作成時に特定のロジックを実行することができます.
例えば、struct(構造体)に予期せぬマイナス・非負値が入ってきた場合に,
0と置き換える事ができます!
入力のバリデーションや値の変換などが可能になるわけですね.

struct User {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        // 年齢が0未満の場合は0に設定するカスタムロジック
        self.name = name
        self.age = age >= 0 ? age : 0
    }
}

let user = User(name: "Alice", age: -5)
print(user.age) // 出力: 0

2. パラメータの柔軟性を提供

イニシャライザにおいて,異なるパラメータセットで複数の初期化方法を提供することができます.
下記の例のように,widthとheightが宣言されている場合でも,
長方形ならばそれぞれの辺,正方形ならば一辺の長さを用いて初期化することができます!

struct Rectangle {
    var width: Double
    var height: Double

    // メンバーワイズイニシャライザ
    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }

    // 正方形として初期化するカスタムイニシャライザ
    init(side: Double) {
        self.width = side
        self.height = side
    }
}

let rect1 = Rectangle(width: 10, height: 20)
let rect2 = Rectangle(side: 15) // 正方形

このように,異なるコンテキストに応じて異なる初期化方法を使い分けることが可能です.

3. 外部から見えないプロパティの初期化

「外部から見えないプロパティの初期化」とは,
構造体やクラスのプロパティに対してアクセス制御を行い,外部から直接操作できないようにすることを指します.
Swift ではアクセス制御を利用して,特定のプロパティやメソッドを外部から見えないように設定できます.

パッと聞いて理解しづらいですが,下記の例を見るとわかりやすいと思います.
下記の例は銀行の貯金残高に関するプログラムです.
account.balance = 1000のように,残高を直接書き換えようとすると,エラーになる(privateでbalanceが設定され値得るため)わけですね.
account.getBalance()と,メソッドを経由して残高を見る
or account.deposit(amount: 50)として,貯金のフローを正式にたどる必要があります.

struct BankAccount {
    private var balance: Double

    // 初期化
    init(initialDeposit: Double) {
        // 初期預金が0以上の場合のみ、残高に反映
        self.balance = initialDeposit >= 0 ? initialDeposit : 0
    }

    // 現在の残高を取得するメソッド
    func getBalance() -> Double {
        return balance
    }

    // 預金するメソッド
    mutating func deposit(amount: Double) {
        if amount > 0 {
            balance += amount
        }
    }
}

var account = BankAccount(initialDeposit: 100)
print(account.getBalance())  // 100

account.deposit(amount: 50)
print(account.getBalance())  // 150

// 直接 balance にアクセスするとエラーになる
// account.balance = 1000  // エラー: 'balance' is inaccessible due to 'private' protection level


private や fileprivate といったアクセス修飾子を使って
あるプロパティをその型の外から直接アクセスできないようにし,
init を通じてそのプロパティを初期化したり管理したりします。

4. プロパティの依存関係を調整

複数のプロパティが相互に依存している場合や,初期化時にプロパティ間で何らかの関係を持たせる場合に,init でそのロジックを管理できます.

struct Circle {
    var radius: Double
    var area: Double

    init(radius: Double) {
        self.radius = radius
        self.area = Double.pi * radius * radius
    }
}

let circle = Circle(radius: 5)
print(circle.area) // 面積が自動計算される

上記例では,円の半径(radius)のみで初期化して,半径と面積を持つ円の構造体を得ることができるようになっています.

まとめ

init を用いることで,標準のメンバーワイズイニシャライザではできない柔軟な初期化が可能になります.
デフォルトの初期化プロセスをカスタマイズしたり,特定のバリデーションや依存関係を持たせたい場合に,init を活用するメリットがあります.

おわりに

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