๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐ŸŽ iOS/Architecture

[iOS/Architecture] UseCase ํ™œ์šฉ๊ธฐ

์•ˆ๋…•ํ•˜์„ธ์š” ์ œ์ธ์ž…๋‹ˆ๋‹ค :)

์ตœ๊ทผ์— ์ง„ํ–‰ํ•œ ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ์˜ ์•„ํ‚คํ…์ฒ˜๋ฅผ MVVM-Clean Architecture ๋กœ ์„ค๊ณ„ํ•˜๊ณ , 

๊ฐ ๋ ˆ์ด์–ด์˜ ์—ญํ• ์„ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•ด ์ „์ฒด ์ฝ”๋“œ์˜ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋†’์ด๋Š” ๋ฐ ์ดˆ์ ์„ ๋งž์ถ”์–ด ๊ฐœ๋ฐœ์„ ํ–ˆ์—ˆ๋Š”๋ฐ์š”,

์ด ๊ณผ์ •์—์„œ ํŠนํžˆ UseCase์˜ ์—ญํ• ์— ๋Œ€ํ•ด ๋งŽ์ด ๊ณ ๋ฏผ์„ ํ•œ ๊ฒƒ ๊ฐ™์•„์š”!

๊ทธ๋ž˜์„œ ์ œ๊ฐ€ ์ €์˜ ๋ฐฉ์‹๋Œ€๋กœ UseCase๋ฅผ ๊ตฌ์„ฑํ•œ ๋‚ด์šฉ์„ ํ•œ ๋ฒˆ ๊ณต์œ ํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

UseCase๋ž€?

UseCase ๋ ˆ์ด์–ด์˜ ์—ญํ• ์„ ํ•œ ์ค„๋กœ ์ •์˜ํ•œ๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์ •์˜ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

UseCase๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์œ„์น˜ํ•˜๋Š” ๊ณณ์œผ๋กœ, ์—”ํ‹ฐํ‹ฐ๋กœ ๋“ค์–ด์˜ค๊ณ  ๋‚˜๊ฐ€๋Š” ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

 

์ด ์ •์˜๋ฅผ ์ข€ ํ’€์–ด์„œ ์ƒ๊ฐํ•ด๋ณผ๊ฒŒ์š”!

ํ™”๋ฉด์— ๋ณด์—ฌ์ค„ ๋ฐ์ดํ„ฐ๋ฅผ ์›ํ•˜๋Š” ํ˜•ํƒœ๋กœ ์–ป์œผ๋ ค๋ฉด?

1. ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์„ ํ†ตํ•ด DB์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
2. ์š”๋ฆฌ์กฐ๋ฆฌ ๊ณ„์‚ฐ์„ ํ•ด์„œ(= ์–ด๋–ค ๋กœ์ง์„ ๊ฑฐ์ณ์„œ) ์ตœ์ข…์ ์œผ๋กœ Entity ํš๋“

 

๋ณดํ†ต ์ด๋Ÿฐ ๊ณผ์ •์„ ๊ฑฐ์ณ ๋ฐ์ดํ„ฐ๋ฅผ ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

์—ฌ๊ธฐ์„œ UseCase ๋ ˆ์ด์–ด์˜ ์—ญํ• ์€

2. ์š”๋ฆฌ์กฐ๋ฆฌ ๊ณ„์‚ฐ์„ ํ•ด์„œ(= ์–ด๋–ค ๋กœ์ง์„ ๊ฑฐ์ณ์„œ) ์ตœ์ข…์ ์œผ๋กœ Entity ํš๋“

์—ฌ๊ธฐ์„œ์˜ '๋กœ์ง'์„ ์˜๋ฏธํ•˜๊ฒŒ ๋˜๋Š” ๊ฒƒ ๊ฐ™์•„์š”. (์ œ๊ฐ€ ์›๋ฌธ์„ ํ†ตํ•ด ๊ณต๋ถ€ํ•˜๊ณ  ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ณธ ๊ฒฐ๊ณผ ๋Š๋‚€ ๊ฒฐ๋ก ์€ ์ด๋ ‡์Šต๋‹ˆ๋‹ค)

 

์ €๋Š” MVVM-Clean Architecture ๊ตฌ์กฐ๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ์„ค๊ณ„ํ•˜์˜€๋Š”๋ฐ์š”, (๋‹ค๋ฅธ ํŒจํ„ด๊ณผ๋„ ์ ์šฉ ๊ฐ€๋Šฅ)

๊ธฐ์กด MVVM ํŒจํ„ด์—์„œ๋Š” ViewModel์ด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ–ˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด MVVM-Clean Architecture ๊ตฌ์กฐ์—์„œ๋Š” MVVM์ด ๋‹ด๋‹นํ•˜๋˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ UseCase๋กœ ๋œ์–ด๋‚ด๊ฒŒ ๋˜๊ฒ ์ฃ ?

- ViewModel: ๋ทฐ๋กœ๋ถ€ํ„ฐ Input์ด ๋“ค์–ด์˜ค๋ฉด ์ ์ ˆํ•œ Output์„ ๋งŒ๋“ค์–ด ์ตœ์ข…์ ์œผ๋กœ ๋ทฐ์— entity๋ฅผ ํ‘œ์ถœํ•ด์ค„ ์ˆ˜ ์žˆ๋„๋ก
- UseCase: Repository์— ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•ด์„œ DB๋กœ๋ถ€ํ„ฐ ๋ทฐ์— ํ‘œ์ถœํ•  ๋ฐ์ดํ„ฐ๋ฅผ ์–ป๊ณ , ์ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณต or ๋ฐ์ดํ„ฐ ๊ด€๋ จ ํ›„์ฒ˜๋ฆฌ

 

๊ตฌ์ฒด์ ์œผ๋กœ ๊ฐ๊ฐ ์œ„์™€ ๊ฐ™์ด 

ViewModel์€ Input-Output์˜ ํ๋ฆ„์„ ์กฐ์ •ํ•˜๋Š” ์—ญํ• , UseCase๋Š” ๋ฐ์ดํ„ฐ์˜ ํ๋ฆ„์„ ์กฐ์ •ํ•˜๋Š” ์—ญํ• ์„ ๋‹ด๋‹นํ•˜๊ฒŒ ๋˜์–ด

๊ด€์‹ฌ์‚ฌ๊ฐ€ ํ•œ ๋‹จ๊ณ„ ๋” ๋ถ„๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

 

์ฒ˜์Œ ํด๋ฆฐ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉฐ UseCase๊ฐ€ ๋ฌด์Šจ ์—ญํ• ์„ ํ•˜๋Š”์ง€ ๋ช…ํ™•ํ•˜๊ฒŒ ์ดํ•ดํ•˜๊ธฐ๊ฐ€ ํž˜๋“ค์—ˆ๋˜ ์ด์œ ๋Š”

ViewModel๊ณผ Repository ์‚ฌ์ด์—์„œ ๋Œ€์ฒด ์–˜์˜ ์—ญํ• ์ด ๋ฌด์—‡์ธ์ง€?!?!?! ์ดํ•ดํ•˜๋ ค๊ณ  ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณด๋ฉด,

๊ฐ„๋‹จํ•œ ์ž‘์—…์˜ ๊ฒฝ์šฐ ๋”ฑํžˆ UseCase์—์„œ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋˜๋Š” ์ผ์ด ์—†์–ด์ง€๊ธฐ ๋•Œ๋ฌธ.. ์ด์—ˆ๋˜ ๊ฒƒ ๊ฐ™์•„์š”!

 

protocol UseCase {
    func execute(request: Request) async throws -> [Item]
}

class DefaultUseCase: UseCase {
    private let repository = DefaultRepository()
    
    func execute(request: Request) async throws -> [Item] {
        let result = try await repository.fetchList(query: request.query, page: request.page)
        return result
    }
}

 

์œ„์™€ ๊ฐ™์ด UseCase๋Š” Repository๋ฅผ execute() ๋ผ๋Š” ํ•จ์ˆ˜๋กœ ํ•œ๋ฒˆ ๋” ๊ฐ์‹ธ๊ธฐ๋งŒ ํ•˜๊ณ .. ๊ฑฐ์˜ ์ฝ”๋“œ๊ฐ€ ๋™์ผํ•ด์ง€๊ธฐ ๋•Œ๋ฌธ์—..

๊ทธ๋Ÿฌ๋ฉด ํ†ต๋กœ ์—ญํ• ๋งŒ ํ•˜๋Š” ๊ฐ์ฒด์ธ UseCase๋ฅผ ๋‘˜ ํ•„์š”๊ฐ€ ์—†์ง€ ์•Š๋‚˜? ํ•˜๋Š” ์ƒ๊ฐ์— ๋ญ์ง€...ํ•˜๋Š” ์˜๋ฌธ์ด ๋งŽ์ด ๋“ค์—ˆ์—ˆ๋Š”๋ฐ

๋” ๋งŽ์€ ์˜ˆ์ œ๋ฅผ ์ฐพ์•„๋ณด๊ณ , ์‹ค์ œ๋กœ ํ”„๋กœ์ ํŠธ์— ์ ์šฉ๊นŒ์ง€ ํ•ด๋ณด๋‹ˆ ์–ด๋–ค ์—ญํ• ์„ ํ•˜๋Š”์ง€ ๊ฐ์ด ์žกํžˆ๋”๋ผ๊ตฌ์š”..!

์ œ๊ฐ€ ํ”„๋กœ์ ํŠธ์— UseCase๋ฅผ ์ด์šฉํ•ด์„œ ViewModel์˜ ์—ญํ• ์„ ๋œ์–ด๋‚ธ ๊ณผ์ •์„ ์†Œ๊ฐœํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

UseCase๋ฅผ ์ด์šฉํ•ด ViewModel์˜ ๋กœ์ง์„ ๋œ์–ด๋‚ด๊ธฐ

์ผ๋‹จ, ์ œ๊ฐ€ ๊ตฌํ˜„ํ•œ ๋กœ์ง์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

1. ์‚ฌ์šฉ์ž๊ฐ€ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฆ…๋‹ˆ๋‹ค.

2. ViewModel๋กœ ์‚ฌ์šฉ์ž ์ด๋ฒคํŠธ๊ฐ€ ๋“ค์–ด์˜ค๋ฉด OpenAI API๋ฅผ ์ด์šฉํ•ด AI์˜ ๋‹ต๋ณ€์„ ์š”์ฒญํ•˜๋Š” ๋„คํŠธ์›Œํ‚น ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

3. ๋„คํŠธ์›Œํ‚น์— ์„ฑ๊ณตํ•˜์—ฌ ๋‹ต๋ณ€์„ ์–ป์œผ๋ฉด DB์˜ ์‚ฌ์šฉ์ž์˜ ์ด์šฉ ํšŸ์ˆ˜๋ฅผ ์—…๋ฐ์ดํŠธ ์‹œํ‚ต๋‹ˆ๋‹ค.

 

๊ธฐ์กด์— UseCase๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์„ ๋•Œ๋Š”, (๊ตณ์ด ๋ชจ๋“  layer๋ฅผ ๊ผญ ๊ฐ–์ถ”์–ด์•ผ ํ•  ํ•„์š”๋Š” ์—†๋‹ค๊ณ  ์ƒ๊ฐํ•ด์„œ ๊ธฐ์กด์—” UseCase๋ฅผ ์‚ฌ์šฉX)

ViewModel์ด Repository๋กœ ๋„คํŠธ์›Œํ‚น ์š”์ฒญ์„ ํ•˜๊ณ  Repository๋Š” ์‹ค์ œ ๋„คํŠธ์›Œํ‚น์„ ์ˆ˜ํ–‰ํ•˜๋Š” Service ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•ด ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ(ai์˜ ๋‹ต๋ณ€ ๊ฒฐ๊ณผ)๋ฅผ fetch ํ•ด์™€์„œ ํ™”๋ฉด์— ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋„๋ก View์— ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”์ธ๋”ฉํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ , ViewModel์—์„œ ๋‹ค์‹œ Repository๋กœ DB ๋ฐ์ดํ„ฐ์˜ ์—…๋ฐ์ดํŠธ๋ฅผ ์š”์ฒญํ•˜๋Š” ๊ตฌ์กฐ์˜€์Šต๋‹ˆ๋‹ค.

๊ธฐ์กด ์ฝ”๋“œ

final class InputTroubleViewModel {
    // ...
    
    private func fetchData(
        systemContent: String,
        userContent: String
    ) {
        // ๋ฐ์ดํ„ฐ fetch ์š”์ฒญ
        repository.fetchResultData(systemContent: systemContent,
                                   userContent: userContent)
        .sink(receiveCompletion: { [weak self] completion in
            switch completion {
            case .finished:
                // ๋ฐ์ดํ„ฐ fetch ์„ฑ๊ณตํ•˜๋ฉด DB์˜ ์œ ์ € ์ •๋ณด ์—…๋ฐ์ดํŠธ
                self?.saveData()
            case .failure(_):
                self?.state.hasErrorOccurred = true
            }
            self?.state.isLoading = false
        }, receiveValue: { [weak self] result in
            self?.state.result = result.reply
            self?.state.onCompleted = true
        }).store(in: &cancellables)
    }
    
    // DB ์œ ์ € ์ •๋ณด ์—…๋ฐ์ดํŠธ
    private func saveData() {
        repository.updateUserData(userId: UserDefaults.userId,
                               lastUsedDate: Date().today,
                               usedCount: UserDefaults.usedCount + 1)
        .sink(receiveCompletion: { [weak self] completion in
            if case let .failure(error) = completion {
                self?.state.hasErrorOccurred = true
            }
        }, receiveValue: { }
        ).store(in: &cancellables)
    }
}

 

์œ„์—์„œ ์ •๋ฆฌํ•œ ๋‚ด์šฉ์„ ๋ฐ˜์˜ํ•ด ์ฝ”๋“œ๋ฅผ ๋ฆฌํŒฉํ† ๋ง ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

UseCase๋ฅผ ๋„์ž…ํ•ด ๋ฐ์ดํ„ฐ์˜ ํ๋ฆ„์„ ์กฐ์ •ํ•˜๋Š” ์—ญํ• ์€ UseCase๊ฐ€, Input-Output์˜ ํ๋ฆ„์„ ์กฐ์ •ํ•˜๋Š” ์—ญํ• ์€ ViewModel์ด ๋‹ด๋‹นํ•˜๋„๋ก ํ•˜์—ฌ ๋ทฐ๋ชจ๋ธ์˜ ์—ญํ• ์„ ๋œ์–ด๋‚ด๋ณผ๊ฒŒ์š”.

๊ฐœ์„  ์ฝ”๋“œ - UseCase

protocol ConvertTroubleUseCase {
    func execute(systemContent: String, userContent: String) -> AnyPublisher<ReplyEntity, NetworkError>
}

final class ConvertTroubleUseCaseImpl: ConvertTroubleUseCase {
    private let gptRepository: GptRepository
    private let userRepository: UserRepository
    
    init(gptRepository: GptRepository,
         userRepository: UserRepository
    ) {
        self.gptRepository = gptRepository
        self.userRepository = userRepository
    }
    
    func execute(systemContent: String, userContent: String) -> AnyPublisher<ReplyEntity, NetworkError> {
        return gptRepository
            .fetchResultData(systemContent: systemContent, userContent: userContent)
            .flatMap { [weak self] replyEntity -> AnyPublisher<ReplyEntity, NetworkError> in
                guard let self = self
                else { 
                    return Fail(error: NetworkError.serverError).eraseToAnyPublisher()
                }
                return self.userRepository
                    .updateUserData(userId: UserDefaults.userId,
                                    lastUsedDate: Date().today,
                                    usedCount: UserDefaults.usedCount + 1)
                    .map { _ in replyEntity }
                    .eraseToAnyPublisher()
            }
            .eraseToAnyPublisher()
    }
}

 

์œ„์˜ UseCase์—์„œ๋Š” ๋‘ ๊ฐœ์˜ repository๋ฅผ ์—ฎ์–ด์„œ ๋ฐ์ดํ„ฐ์™€ ๊ด€๋ จ๋œ ๋กœ์ง์„ ๋ชจ๋‘ ๋‹ด๋‹นํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋” ์ž์„ธํžˆ ๋ณด๋ฉด, gptRepository๋ฅผ ํ†ตํ•ด ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , flatMap์„ ์‚ฌ์šฉํ•˜์—ฌ gptRepository๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ๊ฒฐ๊ณผ(= replyEntity)๋ฅผ ๊ฐ€์ง€๊ณ  ์ถ”๊ฐ€์ ์ธ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ ์ž‘์—…์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๋งŒ์•ฝ ์ค‘๊ฐ„ ๊ณผ์ •์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ฆ‰์‹œ ์ข…๋ฃŒํ•˜๊ณ  ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ , ๋ชจ๋“  ์ž‘์—…์ด ์™„๋ฃŒ๋˜๋ฉด ReplyEntity๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. 

๊ฐœ์„  ์ฝ”๋“œ - ViewModel

final class InputTroubleViewModel {
    // ...
    
    private func fetchData(
        systemContent: String,
        userContent: String
    ) {
        convertTroubleUseCase
            .execute(systemContent: systemContent,userContent: userContent)
        .sink(receiveCompletion: { [weak self] completion in
            if case .failure(_) = completion {
                self?.state.hasErrorOccurred = true
            }
            self?.state.isLoading = false
        }, receiveValue: { [weak self] result in
            self?.state.result = result.reply
            self?.state.onCompleted = true
        }).store(in: &cancellables)
    }
}

 

๊ธฐ์กด์— ViewModel์ด ๋‹ด๋‹นํ•˜๋˜ ์—ญํ• ์„ UseCase์—์„œ ์ผ๋ถ€ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ›จ์”ฌ ๊ฐ„๋‹จํ•ด์กŒ์Šต๋‹ˆ๋‹ค.

๋ทฐ๋ชจ๋ธ์—์„œ๋Š” UseCase์˜ ๊ฒฐ๊ณผ ๊ฐ’(= ReplyEntity)์„ ๋ฐ›์•„์„œ Output์œผ๋กœ ๋‚ด๋ณด๋‚ด ๋ทฐ์— ๋ฟŒ๋ ค์ฃผ๊ฑฐ๋‚˜(์—ฌ๊ธฐ์„  state๋ฅผ ์—…๋ฐ์ดํŠธํ•ด์ฃผ๋Š” ๋ฐฉ์‹) ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์—๋Ÿฌ์— ๋”ฐ๋ฅธ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ฆ‰, Presentation์— ํ•„์š”ํ•œ ์ •๋ณด๋งŒ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

์—๋Ÿฌ ์ฒ˜๋ฆฌ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋„ ๋ทฐ๋ชจ๋ธ์—์„œ๋Š” ์ตœ์ข…์ ์œผ๋กœ ํ•œ ๋ฒˆ๋งŒ ๋ฐ›์•„ ํ›จ์”ฌ ๊ฐ„๊ฒฐํ•ด์ง„ ๊ฒƒ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

UseCase ๋„์ž…์œผ๋กœ ์–ป์„ ์ˆ˜ ์žˆ๋Š” ์ด์ 

1. ์ฝ”๋“œ ์ค‘๋ณต์„ ๋ฐฉ์ง€

๊ธฐ์กด ์ฝ”๋“œ vs ๊ฐœ์„  ์ฝ”๋“œ๋ฅผ ๋น„๊ตํ•˜๋ฉด์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์ฃ ? (์—๋Ÿฌ ์ฒ˜๋ฆฌ์˜ ๊ฒฝ์šฐ)

ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์บก์Šํ™”ํ•˜๊ณ , ์žฌ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•จ์œผ๋กœ์จ ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ์—ฌ๋Ÿฌ ๊ณณ์— ์‚ฐ์žฌ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

2. ์ฑ…์ž„์„ ๋‚˜๋ˆ„์–ด ์ปค๋‹ค๋ž€ ๊ฐ์ฒด๋ฅผ ํ”ผํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๊ฒƒ๋„ ๋ทฐ๋ชจ๋ธ์ด ํ›จ์”ฌ ๊ฐ„๊ฒฐํ•ด์ง„ ๊ฒƒ์œผ๋กœ ์œ„์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ViewModel์ด ๋น„๋Œ€ํ•ด์ง€๋Š” ๋ฌธ์ œ๋ฅผ ๊ฐ์ž ํ•œ๊ฐ€์ง€ ์ฑ…์ž„๋งŒ ๊ฐ€์ง€๋Š” UseCase๋“ค์„ ๋‘์–ด์„œ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

3. ํ…Œ์ŠคํŠธ์— ์šฉ์ด

2๋ฒˆ๊ณผ๋„ ์ด์–ด์ง€๋Š” ์ด์ ์ž…๋‹ˆ๋‹ค!

ํ•œ๊ฐ€์ง€ ์ฑ…์ž„๋งŒ ๊ฐ–๊ณ , ์ƒํƒœ๋ฅผ ๊ฐ–์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ ๋ฒ”์œ„๊ฐ€ ๋ช…ํ™•ํ•ด์ง‘๋‹ˆ๋‹ค.

 

4. ์œ ์ง€๋ณด์ˆ˜์„ฑ ์šฉ์ด

์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ์ด ํ•œ ๊ณณ์— ๋ชจ์—ฌ์žˆ์ง€ ์•Š๊ณ , ํ•œ ๊ฐœ์˜ UseCase๊ฐ€ ๋‹จ ํ•˜๋‚˜์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๋งŒ ๋‹ด๋‹นํ•˜๊ธฐ ๋•Œ๋ฌธ์—,

UseCase ๋‚ด๋ถ€์˜ ์ผ๋ถ€ ๋กœ์ง์ด ๋ณ€๊ฒฝ๋˜์–ด๋„ ๋‹ค๋ฅธ ๊ฐ์ฒด๋‚˜ ๋ ˆ์ด์–ด์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋ฉด ์ˆ˜์ •ํ•˜๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ๊ฐ€ ๋˜๊ฒ ์ฃ !

 

์ง€๊ธˆ๊นŒ์ง€ ์ œ๊ฐ€ ๋ฆฌํŒฉํ† ๋งํ•œ ๊ฒฝํ—˜์„ ์ •๋ฆฌํ•˜๋ฉด์„œ UseCase์— ๋Œ€ํ•ด ๋” ์ž์„ธํžˆ ๊ณต๋ถ€ํ•ด๋ณด์•˜๋Š”๋ฐ์š”!

์ €๋„ ์ด๋ฒˆ ๊ธฐํšŒ๋กœ UseCase์— ๋Œ€ํ•œ ๋ชจํ˜ธํ•œ ๊ฐœ๋…์„ ํ™•์‹คํžˆ ์žก์„ ์ˆ˜ ์žˆ์—ˆ๋˜ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๊ธด ๊ธ€ ์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค๐Ÿ˜Š๐Ÿ˜Š


[์ฐธ๊ณ  ์ž๋ฃŒ]

 

GitHub - kudoleh/iOS-Clean-Architecture-MVVM: Template iOS app using Clean Architecture and MVVM. Includes DIContainer, FlowCoor

Template iOS app using Clean Architecture and MVVM. Includes DIContainer, FlowCoordinator, DTO, Response Caching and one of the views in SwiftUI - GitHub - kudoleh/iOS-Clean-Architecture-MVVM: Tem...

github.com

 

[iOS] 3. Clean Architecture + MVVM ๊ฐœ๋… ํ™•์‹คํ•˜๊ฒŒ ์ดํ•ดํ•˜๊ธฐ (Actor๊ฐ€ Entity๋ฅผ ๋ฐ›๊ธฐ๊นŒ์ง€)

Actor๊ฐ€ Entity๋ฅผ ๋ฐ›๊ธฐ๊นŒ์ง€ View๋Š” ViewModel์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœ viewModel์€ useCase ์‹คํ–‰ > useCase๋Š” Repository(DB or Network)์— ๋ฐ์ดํ„ฐ ์š”์ฒญ Repository์—์„œ cache์— ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ๋ฐ”๋กœ ํš๋“, ์—†์œผ๋ฉด memory cache, disk cac

ios-development.tistory.com

 

๐Ÿง™๐Ÿป‍โ™€๏ธ PRND iOSํŒ€์˜ UseCase ํ™œ์šฉ๊ธฐ

PRND iOSํŒ€์—์„œ๋Š” Clean Architecture๋ฅผ ๋„์ž…ํ•ด ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ณ , ๊ณ„์ธต ๊ฐ„ ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ๋ฅผ ํ†ตํ•ด ๋งŽ์€ ์ด์ ์„ ์ฒด๊ฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

medium.com

 

'๐ŸŽ iOS > Architecture' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Architecture] Clean Architecture  (1) 2024.01.03
[Architecture] VIPER ํŒจํ„ด  (1) 2022.04.15
[Architecture] MVC, MVP, MVVM ์•„ํ‚คํ…์ฒ˜ ํŒจํ„ด  (2) 2022.03.25