【Swift】シングルトンは悪くない、sharedを直接触るのが悪いんだ

## シングルトン

古来からあるデザインパターンの一種で

- **A: クラスのインスタンスをアプリ内で1つのみ存在させる**
- **B: static変数のsharedでどこからでもアクセスできる**

という効果があります。
「単一のリソースに対してアクセスするクラスは、複数生成すると並列アクセス等でバグが出るからシングルトンにしようね」なんて言われてたりします。例としては以下のようなクラスです。

- ログイン状態を管理するクラス
- DBを操作するクラス
- デバイスの機能 (Location, Bluetooth) を使うクラス

```swift
final class Auth {
    
    // static変数で自分自身を保持
    static let shared: Auth = .init()

    var isSignedIn: Bool { ... }
    func signIn() async throws { ... }

    // initをprivateにすることで、クラス外でインスタンスを生成できなくする
    // =上のshared以外のインスタンスは存在できないことを保証
    private init() {}
}
```

## シングルトン良くない論

https://qiita.com/mo12ino/items/abf2e31e34278ebea42c

さんざん言われてるものから個人的に特に良くないと感じる点を抽出

### 理由1: 蜜結合で単体テストできない

> ユニットテストを難しくする。外部から渡せないオブジェクトはモックにする事が難しい。

```swift
struct AuthRepository {
    var isSignedIn: Bool { 
        Auth.shared.isSignedIn // これ
    }

    func signIn() async throws { 
        try await Auth.shared.signIn() // これ
    }
}
```

この`AuthRepository`を単体テストする際、Authをスタブ/モックに切り替えたくても、これでは切り替えれません。

### 理由2: 依存関係が見えなくなる

> 依存関係を見えにくくし、コードが読みづらくなる。


```swift
let authRepository = AuthRepository() // 何も引数がない
```

`AuthRepository` を利用する側から見ると、initに何も引数がないので中でAuthが使われていることが分かりません。**オブジェクトが内部で何に依存しているかは、外から見えるようにしたほうが良い**と考えています。

:::note info
特に、シングルトンに依存や環境変数が必要な場合
自動で初期化はできないため、`shared`をIUOにして`setup`で依存を受け取り初期化する場合があります。

```swift
final class Auth {
    
    static let shared: Auth!

    let accessGroup: String
    
    private init(accessGroup: String) {
        self.accessGroup = accessGroup
    }

    static func setup(accessGroup: String) {
        Self.shared = Auth(accessGroup: accessGroup)
    }
}
```

この場合、`setup`をせずに`shared`にアクセスするとクラッシュしてしまいますが
先ほどの例のように`Auth`への依存が外から見えていないと、「`AuthRepository`を使う前に`Auth`の`setup`を呼ばなくてはいけない」と認知することすらできません。

テストする場合なんかは特に地獄で、「このクラス、中で何に依存してたっけな〜」っていうエスパーをしながらsetupしていくことになります。

```swift
// AuthRepositoryはAuth使ってるからinitしなきゃ...
Auth.setup(accessGroup: "")

let authRepository = AuthRepository()

// UserRepositoryはAPIとDBと...
API.setup(...)
DB.setup(...)
let userRepository = UserRepository()
```
:::

## 解決策： DIする

つまり、シングルトンしていたクラス: `Auth`をprotocolで抽象化し、かつ利用側： `AuthRepository`ではinitでインスタンスを受け取るようにしようということです。

```swift
struct AuthRepository {

    let auth: AuthProtocol

    init(auth: AuthProtocol) {
        self.auth = auth
    }

    var isSignedIn: Bool { 
        auth.isSignedIn
    }

    func signIn() async throws { 
        try await auth.signIn()
    }
}
```

これで、 **理由1: 蜜結合で単体テストできない**, **理由2: 依存関係が見えなくなる** の両方を解決できます。
```swift
final class AuthStub: AuthProtocol {
    var isSignedIn: Bool { 
        true
    }

    func signIn() async throws { }
}

// Stubの注入もできるし、依存も見える
let authRepository = AuthRepository(auth: AuthStub())
```

## シングルトンはやめなくてもいいんじゃない？

この記事の本題です。
上記で触れた **シングルトンは悪い点があるので、DIを利用するべき** というのは前提として
**「別にDIはシングルトンと矛盾しないんじゃないか？」** **「シングルトン自体をやめなくてもいいんじゃない？」**　という話です。

実際、限られたリソースにアクセスするクラスや、initで重い処理を行うようなクラスのインスタンスはアプリ内で一個だけにしておきたいケースがあります。（少なくとも自分はありました）

### 悪いのはsharedを直接触っていたこと

**理由1: 蜜結合で単体テストできない**, **理由2: 依存関係が見えなくなる**の2点に関しては
シングルトンの**A: クラスのインスタンスをアプリ内で1つのみ存在させる**に対する問題というよりかは、**B: static変数のsharedでどこからでもアクセスできる**に対する問題だと思います。

### 解決策2: sharedをDIする

実際、DIとシングルトンは共存できます。以下のようにsharedをDIすれば、**B: static変数のsharedでどこからでもアクセスできる**の問題を解決しつつシングルトンの**A: クラスのインスタンスをアプリ内で1つのみ存在させる**の利点だけを利用できます。

```swift
let authRepository = AuthRepository(auth: Auth.shared)
```

### 解決策3: これもシングルトン？

とはいう自分も、実はsharedのスタイルのシングルトンは使っていません。
ただ、以下のようにアプリで使う依存を管理するクラスを定義し、各画面(やRouter等）で依存解決に使っています。（`AppDependency`自体はバケツリレー）

これだとシングルトンに依存がある場合でもIUOにする必要がないですし、簡単にシングルトン/ファクトリーを切り替えることができます。

この仕様はAndroidのDIライブラリのkoinの`module`の定義を参考にしました

https://insert-koin.io/

```swift
protocol AppDependencyProtocol {
    func inject() -> AuthProtocol
    func inject() -> AuthRepositoryProtocol
}

final class AppDependency: AppDependencyProtocol {
    let auth = Auth(accessGroup: "")

    func inject() -> AuthProtocol {
        // 変数を返せばシングルトン
        auth
    }

    func inject() -> AuthRepositoryProtocol {
        // その場で生成すればファクトリー
        AuthRepository(auth: inject())
    }
}
```

「アプリ内でクラスのインスタンスを共有している」だけなので、デザインパターンの「シングルトン」とは違うかもしれませんが、シングルトンの目的の一つを達成しているので、広義では「シングルトン」なのかもしれません。

## まとめ

シングルトンには主に二つの恩恵があり

- **A: クラスのインスタンスをアプリ内で1つのみ存在させる**
- **B: static変数のsharedでどこからでもアクセスできる**

Bは問題が多いため、**シングルトンをやめてDIする意見（解決策1)** が見られるが、**sharedをDIしたり(解決策2)**、**sharedを使わないシングルトン（？）（解決策3)** を利用することでAだけの恩恵を利用できるのでこれは良いのではないかと思います。

ただ、A自体もデメリットだという意見も見られるので、Aがデメリットだと感じている場合はこの記事では解決しないかもしれません。

もっと良い方法・改善点があればコメントください！！