Bibi's DevLog ๐Ÿค“๐ŸŽ

Environment : ๊ฐ์ฒด์˜ ์˜์กด์„ฑ ํ™˜๊ฒฝ ๋งŒ๋“ค๊ธฐ ๋ณธ๋ฌธ

๐Ÿ“ฑ๐ŸŽ iOS

Environment : ๊ฐ์ฒด์˜ ์˜์กด์„ฑ ํ™˜๊ฒฝ ๋งŒ๋“ค๊ธฐ

๋น„๋น„ bibi 2022. 10. 21. 22:15

๊ด€๋ จ ์ €์žฅ์†Œ

Environment๋ž€?

๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ๋•Œ, ๊ทธ ๊ฐ์ฒด๊ฐ€ ํ•„์š”๋กœ ํ•˜๋Š” ์˜์กด์„ฑ์„ ์ฃผ์ž…ํ•ด ์ค€๋‹ค.
์ด ๋•Œ ํ•„์š”ํ•œ ์˜์กด์„ฑ์ด ๊ฐ์ฒด ์ „์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ ๊ทธ ๊ฐ์ฒด์˜ ๋ฉ”์„œ๋“œ ์ผ๋ถ€๋ผ๋ฉด, ๊ฐ์ฒด ์ „์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ ๋ฉ”์„œ๋“œ ์ผ๋ถ€๋งŒ์„ ์ฃผ์ž…ํ•ด ์ฃผ๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ์ด๋‹ค.
๊ทธ๋ž˜์„œ 'ํŠน์ • ๊ฐ์ฒด๊ฐ€ ํ•„์š”๋กœ ํ•˜๋Š” ํ™˜๊ฒฝ' ์˜๋ฏธ๋กœ ํ•ด์„ํ•˜์—ฌ Environment๋ผ๋Š” ์ด๋ฆ„์„ ์ดํ•ดํ•ด ๋ณด์•˜๋‹ค.

์‚ฌ์šฉ ๋ฐฐ๊ฒฝ

๊ด€๋ จ PR

https://user-images.githubusercontent.com/67407678/197100314-5ea903e8-53b9-453d-84e3-650fa3903a3d.png

Environment ์‚ฌ์šฉ ์ „์—๋Š” ์˜์กด์„ฑ ์ฃผ์ž…์— ํ•„์š”ํ•œ ์š”์†Œ๋“ค์ด ๋ชจ๋‘ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ๊ฑฐ๋‚˜, ๋‹ค๋ฅธ ๊ฐ์ฒด ๋‚ด์˜ ์š”์†Œ๋กœ ์กด์žฌํ•ด ๊ฐ์ฒด ์ƒ์„ฑ ๊ณผ์ •์ด ๋งค์šฐ ๋ณต์žกํ–ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด,

  1. ์ด์Šˆ ๋ชฉ๋ก๋“ค์„ ๋ณด์—ฌ์ฃผ๋Š” IssueViewController์—์„œ ํ•„์š”ํ•œ ๋ฉ”์„œ๋“œ๊ฐ€ IssueService์˜ ๋ฉ”์„œ๋“œ ์ค‘ 1๊ฐœ๋ฟ์ธ๋ฐ, ๊ทธ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด IssueModel์—๊ฒŒ IssueService๊ฐ์ฒด ์ „์ฒด๋ฅผ ๋„˜๊ธฐ๋Š” ์ƒํ™ฉ ๋ฐœ์ƒ
  2. ๊ฐ์ฒด ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” Container๊ฐ€ AppDelegate์—๋งŒ ์กด์žฌํ•ด, Container์‚ฌ์šฉ์„ ์œ„ํ•ด AppDelegate๋ฅผ ์˜ค๋ธŒ์ ํŠธํ™”ํ•˜์—ฌ ์ ‘๊ทผํ•˜๋Š” ์ƒํ™ฉ ๋ฐœ์ƒ

...์™€ ๊ฐ™์€ ํ˜ผ๋ž€์Šค๋Ÿฌ์šด ์ƒํ™ฉ์ด ์ƒ๊ฒผ๋‹ค.

1์ฐจ์ ์œผ๋กœ ์ƒ๊ฐํ•œ ํ•ด๊ฒฐ๋ฐฉ์•ˆ์€ "IssueService์— ๋„ˆ๋ฌด ๋งŽ์€ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์–ด์„œ ๊ทธ๋Ÿฌ๋‹ˆ, ๊ฐ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ์— ํ•„์š”ํ•œ ๋ฉ”์„œ๋“œ๋“ค๋กœ ์„œ๋น„์Šค ๊ฐ์ฒด๋ฅผ ๋˜ ๋‚˜๋ˆ ์•ผ๊ฒ ๋‹ค" ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์„œ๋น„์Šค๋ฅผ ๋‚˜๋ˆ„๋Š” ๊ฒƒ ๋งŒ์œผ๋กœ๋Š” ๋‘ ๋ฒˆ์งธ ์ƒํ™ฉ์ด ํ•ด๊ฒฐ๋˜์ง€ ์•Š๋Š”๋‹ค.

๋˜, "๋ชจ๋ธ์ด๋ผ๋Š” ๊ฐ์ฒด๊ฐ€ ํ•„์š”๋กœ ํ•˜๋Š” ๊ฒƒ์€ IssueService๊ฐ€ ์•„๋‹ˆ๋ผ IssueService์— ์žˆ๋Š” ๋ฉ”์„œ๋“œ ์ผ๋ถ€์ด๋‹ค. ๊ทธ๋Ÿฌ๋‹ˆ ๊ฐ์ฒด ์ „์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ ๊ฐ์ฒด์— ํ•„์š”ํ•œ ํ–‰๋™๋งŒ์„ ์ „๋‹ฌํ•ด ์ฃผ๋ฉด ๋˜์ง€ ์•Š๊ฒ ๋‚˜?" ๋ผ๋Š” ๋ฆฌ๋ทฐ์–ด๋‹˜์˜ ์กฐ์–ธ์ด ์žˆ์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ Environment๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์— ๋„์ „ํ•ด ๋ณด์•˜๋‹ค.

์ ์šฉ ๊ณผ์ •

Environment๋Š” ํŠน์ • ๊ฐ์ฒด์— ํ•„์š”ํ•œ ์˜์กด์„ฑ์„ ๊ด€๋ฆฌํ•œ๋‹ค.

๋‚˜๋Š” ๊ธฐ์กด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ๋ถ€๋ถ„์ธ Container์™€ Model์— Environment๋ฅผ ์ ์šฉํ–ˆ๋‹ค.

  • ContainerEnvironment : Container์— ํ•„์š”ํ•œ ํ™˜๊ฒฝ ๊ด€๋ฆฌ
    • Container๋Š” ๊ฐ์ฒด๋“ค์„ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ๊ฐ์ฒด์ด๋ฏ€๋กœ, ๊ฐ ๊ฐ์ฒด๋“ค์˜ ์ƒ์„ฑ์ž๊ฐ€ ํ•„์š”๋กœ ํ•˜๋Š” ์š”์†Œ๋“ค์„ ๋ชจ๋‘ ๋„ฃ์–ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.
    • ๋”ฐ๋ผ์„œ UserDefaults, OAuthService, IssueService๋ผ๋Š” ๊ฐ€์žฅ ๋งŽ์€ ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ–๊ฒŒ ๋˜์—ˆ๋‹ค.
  • Model๋“ค์˜ Environment : ํ•ด๋‹น Model์— ํ•„์š”ํ•œ Service์˜ ๋ฉ”์„œ๋“œ ์ผ๋ถ€ ๊ด€๋ฆฌ
    • ์˜ˆ๋ฅผ ๋“ค์–ด, ReposModelEnvironment๋Š” ReposModel์— ํ•„์š”ํ•œ ํ™˜๊ฒฝ(IssueService์˜ ๋ฉ”์„œ๋“œ ์ผ๋ถ€)์„ ๊ด€๋ฆฌํ•œ๋‹ค.
    • ์‚ฌ์šฉํ•˜๋Š” IssueService ๋‚ด์˜ ํ•จ์ˆ˜๋งŒ ํด๋กœ์ €๋กœ ๋ฐ›๊ณ , ์ด Environment๋ฅผ ReposModel์˜ ์ƒ์„ฑ์ž์— ์ฃผ์ž…ํ•ด ์ค€๋‹ค.

์ ์šฉ ๋ฐฉ๋ฒ•

struct ReposModelEnvironment {
    let requestRepos: (@escaping (Result<[Repository], IssueError>) -> Void) -> Void
}
  1. ์ด ๊ฐ์ฒด์— ํ•„์š”ํ•œ ํ™˜๊ฒฝ์„ ํ™•์ธํ•œ๋‹ค.
    1. ReposModel์ด ๊ธฐ์กด์— ์ƒ์„ฑ์ž๋กœ ๋ฐ›์•„์˜ค๋˜ ์š”์†Œ๋Š” IssueService์˜€๊ณ , IssueService์—์„œ ์‚ฌ์šฉํ•˜๋˜ ๋ฉ”์„œ๋“œ๋Š” requestRepos() ๋ฟ์ด๋‹ค.
  2. ๊ฐ์ฒด์ด๋ฆ„Environment ๊ตฌ์กฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ , ๊ทธ ๊ฐ์ฒด๊ฐ€ ํ•„์š”๋กœ ํ•˜๋Š” ํ™˜๊ฒฝ์„ ํ”„๋กœํผํ‹ฐ๋กœ ์ •์˜ํ•œ๋‹ค.
    1. ๊ฐ์ฒด๊ฐ€ ํ•„์š”๋กœ ํ•˜๋Š” ๊ฒƒ์ด ๋ฉ”์„œ๋“œ์ธ ๊ฒฝ์šฐ์—๋Š” ํด๋กœ์ €๋กœ ์ •์˜ํ•œ๋‹ค.
    2. ์˜ˆ๋ฅผ ๋“ค์–ด ReposModel์—์„œ ์‚ฌ์šฉํ•˜๋˜ IssueService์˜ requestRepos()์˜ ๊ฒฝ์šฐ, ๊ธฐ์กด service.requestRepos()ํ•˜๋Š” ๋ฉ”์„œ๋“œ์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
    func requestRepos(completion: @escaping (Result<[Repository], IssueError>) -> Void) { 
            // ...
    }
    c. ReposModel์˜ ํ™˜๊ฒฝ์€ ์ด ๋ฉ”์„œ๋“œ๋ฅผ ๋‹ด์•„์•ผ ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์ด ๋ฉ”์„œ๋“œ๋ฅผ ๋ฐ›๊ณ  ๋ฆฌํ„ดํƒ€์ž…์€ ์—†๋Š” (@escaping ((Result<[Repository], IssueError>)) -> Void) -> Void ํƒ€์ž…์ด ๋œ๋‹ค.
  3. ๊ฐ์ฒด๊ฐ€ ๊ธฐ์กด์— ๋ฐ›๋˜ ์˜์กด์„ฑ ๋Œ€์‹ , Environment๋ฅผ ๋„ฃ๋Š”๋‹ค.
    1. ์ด์ œ ReposModel์„ ์ƒ์„ฑํ•  ๋•Œ service ๋Œ€์‹  Environment๋ฅผ ๋„ฃ์–ด์ค€๋‹ค.
    2. let environment = ReposModelEnvironment(requestRepos: { [weak self] completion in // ํ•„์š”๋กœ ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ completionHandler๋กœ ์ „๋‹ฌ self?.container.environment.issueService.requestRepos(completion: { result in completion(result) }) }) let model = ReposModel(environment: environment) let viewController = ReposViewController(model: model)

์ „ํ›„ ๋น„๊ต

Before (Model)

import Foundation

class LoginModel {

    private let service: OAuthService

    init(service: OAuthService) {
        self.service = service
    }

    func requestCode(completion: @escaping (Result<URL, OAuthError>) -> Void) {
        service.requestCode { result in
            completion(result)
        }
    }
}

๋ชจ๋ธ์ด ํ•„์š”๋กœ ํ•˜๋Š” service์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด, ์ƒ์„ฑ์ž์—์„œ service๋ฅผ ์ง์ ‘ ๋ฐ›๊ณ  ์žˆ๋‹ค.

After (Model)

import Foundation

class LoginModel {

    private let environment: LoginModelEnvironment

    init(environment: LoginModelEnvironment) {
        self.environment = environment
    }

    func requestCode(completion: @escaping (Result<URL, OAuthError>) -> Void) {
        environment.requestCode { result in
            completion(result)
        }
    }
}

struct LoginModelEnvironment {
    let requestCode: (@escaping (Result<URL, OAuthError>) -> Void) -> Void
}

๋ชจ๋ธ์ด ํ•„์š”๋กœ ํ•˜๋Š” service์˜ ํŠน์ • ๋ฉ”์„œ๋“œ๋ฅผ LoginModelEnvironment๋ผ๋Š” ๊ตฌ์กฐ์ฒด์— ๋”ฐ๋กœ ์ •์˜ํ•œ๋‹ค.

๋ชจ๋ธ์€ ์ƒ์„ฑ์ž์—์„œ ์ด Environment๋ฅผ ๋ฐ›๋Š”๋‹ค.

์ด์ œ ๋ชจ๋ธ์„ ์ƒ์„ฑํ•  ๋•Œ OAuthService์ „์ฒด๊ฐ€ ์•„๋‹Œ LoginModelEnvironment๋ฅผ ์ƒ์„ฑํ•˜๊ณ ,

LoginModelEnvironment์— ๋ชจ๋ธ์ด ํ•„์š”๋กœ ํ•˜๋Š” OAuthService์˜ ๋ฉ”์„œ๋“œ๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด ๋œ๋‹ค.

Before (Container)

class Container {

    private var accessToken: String?

    init(token: String?) {
        self.accessToken = token
    }

        func buildViewController(_ screen: Screen) -> UIViewController {
        let service = IssueService(token: self.accessToken ?? "")
        switch screen {
        case .login:
            return LoginViewController(service: OAuthService())
        case .issue(let selectedRepo):
            let model = IssueModel(service: service, repo: selectedRepo)
            let viewController = IssueViewController(model: model, repo: selectedRepo)
            viewController.title = "Issues"
            return viewController
        case .repos:
            let model = ReposModel(service: service)
            let viewController = ReposViewController(model: model)
            viewController.title = "Repos"
            return UINavigationController(rootViewController: viewController)
        case .newIssue(let repo):
            let model = NewIssueModel(service: service)
            return NewIssueViewController(repo: repo, model: model)
        case .optionSelect(let option, let repo):
            let model = OptionSelectModel(service: service)
            return OptionSelectViewController(model: model, option: option, repo: repo)
        }
    }

}

์ปจํ…Œ์ด๋„ˆ๊ฐ€ ๊ฐ์ฒด๋“ค์„ ๋งŒ๋“ค ๋•Œ ํ•„์š”๋กœ ํ•˜๋Š” ์—ฌ๋Ÿฌ ์š”์†Œ๋“ค์ด ํ”„๋กœํผํ‹ฐ๋กœ ์—†์–ด, ๊ทธ๋•Œ๊ทธ๋•Œ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค. ๋˜, ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ๋งˆ๋‹ค ํ•„์š”๋กœ ํ•˜๋Š” ์„œ๋น„์Šค๋ฅผ ํ†ต์งธ๋กœ ์ „๋‹ฌํ•˜๊ณ  ์žˆ๋‹ค.

After (Container)

class Container {

    let environment: ContainerEnvironment
    private var registeredObjects: [String: Any] = [:]
    private var registeredViewControllerCoordinator: [UIViewController : Coordinator] = [:]

    init(environment: ContainerEnvironment) {
        self.environment = environment
    }

    // .......

}

struct ContainerEnvironment {
    var githubUserDefaults: GithubUserDefaults
    var oAuthService: OAuthService
    var issueService: IssueService

    static let live: ContainerEnvironment = {
        let githubUserDefaults = GithubUserDefaults()
        guard let token = githubUserDefaults.getToken() else {
            return ContainerEnvironment(githubUserDefaults: githubUserDefaults, oAuthService: OAuthService(), issueService: IssueService())
        }

        return ContainerEnvironment(githubUserDefaults: githubUserDefaults, oAuthService: OAuthService(), issueService: IssueService(token: token))
    }()
}

์ปจํ…Œ์ด๋„ˆ๊ฐ€ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๋•Œ ํ•„์š”๋กœ ํ•˜๋Š” ์š”์†Œ๋“ค์„ Environment๋กœ ์ œ๊ณตํ•ด ์ฃผ๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋™์ผํ•œ ๊ฐ์ฒด๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ๋งŒ๋“ค ํ•„์š”๊ฐ€ ์—†์–ด์กŒ๋‹ค. ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ํ•„์š”๋กœ ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋„ ContainerEnvironment.issueService.๋ฉ”์„œ๋“œ ์™€ ๊ฐ™์ด ์ ‘๊ทผํ•ด ๋ฐ”๋กœ ๋„˜๊ฒจ์ค„ ์ˆ˜ ์žˆ๋‹ค.

๋Š๋‚€ ์ 

  • ์šฐ์„  ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ๋ถˆํ•„์š”ํ•˜๊ฒŒ ํฐ ๊ฐ์ฒด๊ฐ€ ์˜ค๊ณ ๊ฐ€์ง€ ์•Š๋Š” ์ ์ด ๊ฐ€์žฅ ํฐ ์žฅ์ ์ธ ๊ฒƒ ๊ฐ™๋‹ค.
    • “๊ฐ์ฒด๊ฐ€ ์ •๋ง ํ•„์š”ํ•œ ๊ฒƒ์ด ๋ฌด์—‡์ธ์ง€ ์ƒ๊ฐํ•ด ๋ณด๊ณ , ๊ผญ ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ๋„ฃ์–ด ์ฃผ๊ธฐ” ๋ฅผ ๋ช…์‹ฌํ•ด์•ผ๊ฒ ๋‹ค.
  • ํ•จ์ˆ˜๋ฅผ ๋ณ€์ˆ˜์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•˜๋Š” ์—ฐ์Šต์„ ์ง์ ‘์ ์œผ๋กœ ํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ข‹์•˜๋‹ค.