MVC -> MVVM으로 구조 변경해보기

MVVM은 무엇이고, 왜 사용해야할까?

많은 글들을 보고, 코드를 봤지만 나는 아직도 왜 MVVM이 필요한지 모르겠다..

그래서 MVC를 MVVM으로 리팩토링해보며 왜 필요한 지 공부해보고자 한다.

우선 내가 만들고자 하는 앱은 iTunes API를 이용해 음악 정보를 받아오는 아주 단순한 앱이다.

MVVM, MVC에서 공통적으로 사용하는 코드는 다음과 같다.

공통 코드

모델

import Foundation

struct MusicData: Decodable {
    let resultCount: Int
    let results: [Music]
}

struct Music: Decodable {
    
    let albumImage: String
    let musicTitle: String
    let artist: String
    let albumName: String
    let releaseDate: String
    
    enum CodingKeys: String, CodingKey {
        case albumImage = "artworkUrl100"
        case musicTitle = "trackName"
        case artist = "artistName"
        case albumName = "collectionName"
        case releaseDate = "releaseDate"
    }
}

네트워크 통신

import Foundation

enum NetworkError: Error {
    case urlError
    case networkError
    case dataError
    case parseError
}

final class NetworkManager {
    
    static let shared = NetworkManager()
    private init() {}
    
    func requestData(term: String, completion: @escaping (Result<[Music], NetworkError>) -> Void) {
        
        let baseURLString = "https://itunes.apple.com"
        
        guard var urlComponents = URLComponents(string: baseURLString) else {
            completion(.failure(.urlError))
            return
        }
        
        let mediaQueryItem = URLQueryItem(name: "media", value: "music")
        let termQueryItme = URLQueryItem(name: "term", value: term)
        
        urlComponents.path = "/search"
        urlComponents.queryItems = [mediaQueryItem, termQueryItme]
        
        guard let url = urlComponents.url else {
            completion(.failure(.urlError))
            return
        }
        
        fetchData(url: url) { result in
            completion(result)
        }
    }
    
    private func fetchData(url: URL, completion: @escaping (Result<[Music], NetworkError>) -> Void) {
        
        let session = URLSession(configuration: .default)
        
        let task = session.dataTask(with: url) { data, response, error in
            
            if let error {
                print(error.localizedDescription)
                completion(.failure(.networkError))
                return
            }
            
            guard let data else {
                completion(.failure(.dataError))
                return
            }
            
            if let musics = self.parseJSON(data) {
                completion(.success(musics))
            } else {
                completion(.failure(.parseError))
            }
        }
        
        task.resume()
    }
    
    private func parseJSON(_ data: Data) -> [Music]? {
        do {
            let musicData = try JSONDecoder().decode(MusicData.self, from: data)
            return musicData.results
        } catch {
            return nil
        }
    }
}

Last updated