iOS・AndroidSwift

【Swift】selfって書けるとちょっと嬉しい

iOS・Android
この記事は約6分で読めます。

selfを使える限り使っていきたいという感覚があるのですが、ちゃんと言語化したいなと思って記事を書いています。

selfのユースケース

selfを定型的に使いがちなシーンをいくつかピックアップしてみました。

delegate設定時

まず思い浮かぶのは、delegateの設定時ではないでしょうか。

デリゲートパターンでは、デリゲートを受ける側のクラスが、デリゲート提供元クラスのインターフェースのメソッドを呼び出すことで、特定のイベントに応じた処理を委譲します。

この時、提供元クラスのdelegateプロパティに、受ける側のclassを記述します。

class SomeClass: SomeDelegate {
    let anotherClass = AnotherClass()
    
    func setupDelegate() {
        anotherClass.delegate = self
    }
}

selfの嬉しさを体感したいので、使わないとどうなるか試してみましょう。SomeClass内で別Class をインスタンス化し、それをデリゲートに設定します。

class SomeClass {
    let anotherClass = AnotherClass()
    let extraClass = ExtraClass()
    
    func setupDelegate() {
        anotherClass.delegate = extraClass
    }
}

上記の例では、SomeClassが直接デリゲートとして機能せず、別のインスタンス(ExtraClass)をデリゲートとして設定しています。

せっかくプロトコルに基づいてインターフェースの振る舞いを定め、インターフェースを抽象化できたのに、登場人物が増えてしまいました。
上記のコードは一概に間違いとは言えませんが、可読時の負荷が上がる心地を覚えます。ExtraClassAnotherClass の関係性に気を配りつつ読まなければならなくなってしまうので、苦しいです。

クロージャー内におけるselfキャプチャ

クロージャー内での self のキャプチャは、特に非同期処理やイベントハンドラ・コールバックを定義する際に頻繁に遭遇します。

class MyViewController: UIViewController {
    var myProperty: String = ""

    func fetchData() {
        DispatchQueue.global().async { [weak self] in
            // 非同期処理を行う
            let data = "データの取得"
            
            DispatchQueue.main.async {
                // UIの更新はメインスレッドで行う
                self?.myProperty = data
                self?.updateUI()
            }
        }
    }
    
    func updateUI() {
        // UIの更新処理
    }
}

fetchData メソッド内で非同期にデータを取得し、UIを更新する処理を行っています。

非同期処理内のクロージャーで self[weak self] としてキャプチャしています。これは、MyViewController のインスタンスへの弱参照をクロージャーに持たせることで、強参照サイクルを防ぐための措置です。

ここでは、selfnil になる可能性があるため、self? でオプショナルチェーンを使ってメソッドやプロパティにアクセスしています。

この例は、むしろselfを使うしかない状況かもしれません。

enum 内の自己参照

この記事を書く発端になったユースケースその1です。

enumを宣言したのち、switch selfを使って値に応じて振る舞いを決定する関数を実装するときのselfです。

enum UserCategory: String {
    case normalAccount = "通常アカウント"
    case officialAccount = "企業用アカウント"
    
    var sendable: Bool {
        switch self {
        case .normalAccount:
            return true
        case .officialAccount:
            return false
        }
    }
}

enumの中に振る舞いを隠蔽できていることがまず嬉しいですね。

加えて、自分自身の型(self)に基づいて振る舞いを変えているので、機にする範囲が小さくてありがたいです。

抽象class内で、別classが保持するプロパティに自身を代入する

この記事を書く発端になったユースケースそのです。

これは実例を見た方がわかりやすいでしょう。


final class Game {
    
}

extension Game {
    func makeGameDetail() -> GameDetail {
        return GameDetail(parent: self)
    }
}

final class GameDetail {
    let parent: Game
}

このケースでは、、Game インスタンスが self を使って GameDetail インスタンスを作成し、2つのクラス間の関連を設定しています。

これにより、GameDetail はその「親」である Game インスタンスを知ることができます。

class間の関係性を明瞭かつ簡潔に表現できるので、self があると嬉しさを覚えます。

サンプルコードを踏まえ、総括

selfが宣言されていると、気を配る範囲が小さくなり、安心する点がまず一つ。

また、self を使うことでコードの意図を明確にし、オブジェクト間の関連性を容易に表現できる点にも、「嬉しさ」を覚えていることがわかりました。

selfを使うことで、結果的にSwiftらしい書き方ができる側面もあるような心地を覚えています。selfを適切に使うことで、コードの明確性、可読性、そして保守性が向上していることは、上記のサンプルコードからも明らかです。

改めて、selfの使い所・使い時に注意しつつ、コードを書きたいなと実感させられました。