Skip to content

iOS 설계 및 구현

TTOzzi edited this page Dec 20, 2020 · 2 revisions

디자인 패턴 - MVVM

역할

NetworkService

  • 네트워크 통신 담당
  • URLSession의 dataTaskPublisher를 통해 data task를 publisher로 감싸서 받아온 <(Data, URLResponse), Error>를 <Data, NetworkError> 형태로 반환
  • 네트워킹 도중 발생하는 error에 대한 예외 처리를 위해 Custom Error 타입인 NetworkError 구현
enum NetworkError: Error {
   case invalidURL
   case unsuccessfulResponse
   case unknownError(message: String)
}

UseCase

  • NetworkService를 통해 요청을 보내고 응답을 받아 ViewModel이 필요로 하는 형태로 가공
  • 네트워크 요청을 보낼 구체적인 EndPoint 지정과 요청을 보내기 위한 encoding 및 응답 decoding을 통해 가공이 일어남
  • UseCase의 요청, 응답에 대한 error 또는 데이터를 가공하는 과정에서 error 발생 시 예외처리의 구분을 위해 Custom Error 타입인 UseCaseError 구현
enum UseCaseError: Error {
   case networkingError // NetworkService에서 네트워크 통신 중 에러가 발생했을 경우
   case decodingError   // 받아 온 응답 decoding 실패 시
   case encodingError   // Post 요청 등 body에 실어보낼 데이터 encoding 실패 시
   case unknownError    // 그 밖의 error 발생 시
}

ViewModel

  • View에서 발생한 이벤트를 받아 그에 맞게 UseCase를 활용해 요청을 보내고, 응답을 받아 View가 필요로 하는 형태로 가공
  • ViewModel은 SwiftUI에서 제공하는 ObservableObject 프로토콜을 채택해 View가 필요로 하는 데이터의 형태를 State로 업데이트
  • View에서는 ViewModel을 @StateObject / @ObservedObject로 가지면서 ViewModel의 State를 관찰
  • View는 ViewModel의 State의 변화에 따라 스스로를 다시 그리도록 하여 View-ViewModel 간 Binding이 이루어짐
final class TrackViewModel: ObservableObject {
   enum Input {
      case like                 // view에서 발생하는 이벤트
   }

   struct State {
      var track: TrackInfo      // view를 그리는데 필요한 값
   }

    @Published var state: State // State의 값에 변화가 생겼을 경우 ObservableObject의 objectWillChange를 호출

    func send(_ input: Input) { // view에서 이벤트가 발생하면 send 메서드를 통해 이벤트를 뷰모델로 전달
        switch input {          // view에서 발생한 이벤트에 맞게 동작
        case .like:
            state.track.liked == 0 ? like() : cancelLike()
        }
    }
}

View

  • 이벤트가 발생하면 ViewModel에 이벤트를 전달
  • ViewModel과 Binding 되어 ViewModel의 State에 맞게 화면을 그림
  • 예시 : 특정 곡의 '좋아요' 버튼이 눌렸을 때
좋아요 버튼 누르기 전 좋아요 버튼 누른 후
struct PlayerControls: View {
   @ObservedObject private var viewModel: TrackViewModel

   ...

   let track: TrackInfo = viewModel.state.track
   Button {
      viewModel.send(.like)                         // 좋아요 이벤트가 발생했음을 viewModel에게 전달
   } label: {                                       // state.track의 liked 속성에 따라 하트 상태 변경
      Image(systemName: track.liked ? "heart.fill" : "heart"
   }
}

이벤트 로그 수집기의 프레임워크화

  • 추상화된 이벤트 로그 타입(EventLogType), 이벤트를 전송/보관하는 객체의 구조(Local/ServerStorageType)를 프로토콜로 제공
  • 전체 이벤트를 관리하는 EventLogger를 구체 타입으로 제공

ReachabilityObserver

  • Reachability를 활용해 기기의 네트워크 연결 상태를 확인
  • 테스트 가능한 구조를 만들기 위해 ReachabilityObserver를 ReachabilityObserving 프로토콜로 추상화

EventLogger

  • 생성 시 전체 이벤트를 관리하게 될 객체 (구체 타입으로 제공 됨)
  • 생성 시 로컬 저장 객체, 이벤트 서버 전송 객체를 주입 받음
  • ReachabilityObserver를 활용해 기기의 네트워크 연결 상태를 확인
  • 네트워크 연결상태에 따라 로그를 기기에 저장, 서버에 전송
open class EventLogger: EventLoggerType {
    
    public let local: LocalStorageType?
    public let server: ServerStorageType?
    private let reachability: ReachabilityObserving
    private var networkState: Connection = .unavailable
    
    public init(local: LocalStorageType?,
         server: ServerStorageType?,
         reachability: ReachabilityObserving) {  // 테스트 가능한 구조를 만들기 위해 의존성을 가지는 객체들을 추상화하여 주입
        self.local = local
        self.server = server
        self.reachability = reachability
        reachability.setUpNotify { [weak self] in
            self?.networkState = $0
            if $0 == .wifi || $0 == .cellular {
                local?.sendToServer()            // 기기의 네트워크 연결이 활성화되면 기기에 저장했던 로그를 서버로 전송
            }
        }
        server?.setFailureHandler { event in
            local?.save(event)                   // 서버 전송 중 실패했을 경우 기기에 로그를 저장
        }
    }
    
    public func send<T: EventLogType>(_ event: T) {
        switch networkState {                    // 네트워크 연결 상태에 따라 로그를 기기에 저장, 서버에 전송
        case .cellular, .wifi:
            server?.send(event)
        default:
            local?.save(event)
        }
    }
}

LocalStorage & ServerStorage

  • LocalStorage는 네트워크 연결 상태가 끊어졌을 때 CoreData를 사용해 로컬에 저장
  • ServerStorage는 네트워크 연결 상태가 .wifi 또는 .cellular 일 때 서버에 전송
  • LocalStorage와 ServerStorage 모두 추상화된 프로토콜 LocalStorageType, ServerStorageType으로 제공
Clone this wiki locally