🐈

設定画面からアプリの処理を切り分ける

2023/03/10に公開

環境

  • Xcode 14.2

Settings.bundleを追加

File」→「New」→「File...」から「Settings Bundle」を選択する

Root.plistの変更

  1. Settings.bundle内にあるRoot.plistを編集する
  2. Preference ItemsにToggle Switchを追加する
    3. Title、Identifierを適当に設定する

Configファイルを作成

  1. UserDefaultに保存するソースコードを追加する
UserDefaultConfig.swift
import Foundation

@propertyWrapper
struct UserDefault<T> {
    let key: String
    let defaultValue: T

    var wrappedValue: T {
        get {
            UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

enum UserDefaultConfig {
    // UserdefaultのkeyはPreference Itemsで設定したIdentifierにする
    @UserDefault(key: "IS_STUB", defaultValue: false)
    static var isStub: Bool
}

上記の設定で、設定画面にパラメータが追加される

UserDefaultの値によって処理を切り分ける

例)レスポンスでStubを返すか、そのままAPIに投げる

LoginModel
import Foundation

protocol LoginModelInput: AnyObject {
    func login(loginInfo: LoginInfo) async throws -> LoginResponse
}

class LoginModel: LoginModelInput {
    func login(loginInfo: LoginInfo) async throws -> LoginResponse {
        if UserDefaultConfig.isStub {
            StubURLProtocol.requestHandler = { request in
                let response = HTTPURLResponse(
                    url: request.url!, statusCode: 200, httpVersion: "HTTP/2", headerFields: [:]
                )!
                guard
                    let jsonDataURL = Bundle.main.url(forResource: "sample", withExtension: "json"),
                    let jsonData = try? String(contentsOf: jsonDataURL)
                else {
                    fatalError("sample.json not found")
                }
                let data = jsonData.data(using: .utf8)
                return (response, data)
            }

            // URLSession を作る
            let config = URLSessionConfiguration.default
            config.protocolClasses = [StubURLProtocol.self]
            let session = URLSession(configuration: config)

            // APIClient を作る
            let client = APIClient(session: session)

            // request 投げる
            let request = LoginInfoRequest(logInfo: loginInfo)
            let response = try await client.send(request)

            return response
        } else {
            do {
                let request = LoginInfoRequest(logInfo: loginInfo)
                let response = try await APIClient().send(request)
                return response
            } catch APIError.networkError {
                fatalError("ネットワークエラー")
            } catch APIError.decodingError {
                fatalError("デコードエラー")
            }
        }
    }
}

注意点

このままだとリリースビルドをした際にも開発用のパラメータが出てしまうため、Active Compilation Conditionsを用いて、Build Phaseの一番最後にRelese Buildの際にはSettings.bundleを削除するScriptを作成する必要がある

Discussion