Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Swift] MVVM + URLSession #69

Closed
seungchan2 opened this issue Jun 1, 2022 · 0 comments
Closed

[Swift] MVVM + URLSession #69

seungchan2 opened this issue Jun 1, 2022 · 0 comments
Assignees
Labels

Comments

@seungchan2
Copy link
Owner

seungchan2 commented Jun 1, 2022

Helper

import Foundation

class Observable<T> {
     // 3) 호출되면, 2번에서 받은 값을 전달한다.
    private var listener: ((T) -> Void)?
    
    // 2) 값이 set되면, listener에 해당 값을 전달한다,
    var value: T {
        didSet {
            listener?(value)
        }
    }
    
    // 1) 초기화함수를 통해서 값을 입력받고, 그 값을 "value"에 저장한다.
    init(_ value: T) {
        self.value = value
    }
    
    // 4) 다른 곳에서 bind라는 메소드를 호출하게 되면, 
    // value에 저장했떤 값을 전달해주고,
    // 전달받은 "closure" 표현식을 listener에 할당한다.
    func bind(_ closure: @escaping (T) -> Void) {
        closure(value)
        listener = closure
    }
}

View

import UIKit

import Kingfisher

final class SearchViewController: BaseViewController {

    let searchView = SearchView()
    let viewModel = SearchViewModel()

    override func loadView() {
        super.loadView()
        self.view = searchView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        assignDelegation()
        bind()
    }
}

extension SearchViewController {
    private func bind() {
        viewModel.downloadedMovies.bind {_ in
            DispatchQueue.main.async {
                self.searchView.tableView.reloadData()
            }
        }
    }
    
    private func assignDelegation() {
        searchView.searchBar.delegate = self
        searchView.tableView.delegate = self
        searchView.tableView.dataSource = self
    }
}

extension SearchViewController: UISearchBarDelegate {
    
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        guard let text = searchBar.text else { return }
        viewModel.movieQuery.value = text
        viewModel.fetchData(query: text)
    }
}

extension SearchViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return viewModel.numOfRowInSection
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "SearchTableViewCell", for: indexPath) as? SearchTableViewCell else { return UITableViewCell() }
       
        cell.updateCell(viewModel, indexPath: indexPath)
        
        return cell
    }
}

ViewModel

import Foundation

class SearchViewModel {
    
    let searchView = SearchView()
    var downloadedMovies: Observable<Movie> = Observable(Movie(movie: []))
    var movieQuery: Observable<String> = Observable("")
    var title: Observable<String> = Observable("")
    
    func fetchData(query: String) {
        MovieDataManager.shared.fetchMovie(query: movieQuery.value) {[weak self] (movies) in
            guard let self = self else { return }
            if let movies = movies {
                movies.forEach {
                    let actor = $0.actor.removeString()
                    let director = $0.director.removeString()
                    let title = $0.title.removeString()
                    let data = MovieInfo(title: title , image: $0.image, director: director, actor: actor)
                    self.downloadedMovies.value.movie += [data]
                }
            }
        }
    }
    
    var numOfRowInSection: Int {
        return downloadedMovies.value.movie.count
    }
    
    func practiceCell(at indexPath: IndexPath) {
        self.title.value = self.downloadedMovies.value.movie[indexPath.row].title
    }
    
    
    func movieInfo(at index: Int) -> MovieInfo {
        return downloadedMovies.value.movie[index]
    }    
}

Model

import Foundation

struct Movie {
    var movie: [MovieInfo]
}

struct MovieInfo {
    let title: String
    let image: String
    let director: String
    let actor: String
}

import Foundation

struct MovieModel: Codable {
    let items: [Movies]
}

struct Movies: Codable {
    let image: String
    let title: String
    let director: String
    let actor: String
}

APIManager

import Foundation

struct MovieDataManager {
    
    static let shared = MovieDataManager()
    
    let searchURL = "https://openapi.naver.com/v1/search/movie.json"
    
    func fetchMovie(query: String, completion: @escaping ([MovieInfo]?) -> Void) {
        
        let movieQuery = query.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
        
        let urlString = "\(searchURL)?query=\(movieQuery!)"
        performRequest(with: urlString) { movies in
            completion(movies)
        }
    }
    
    func performRequest(with urlString: String, completion: @escaping ([MovieInfo]?) -> Void) {
        
        guard let url = URL(string: urlString) else { return }
        
        let session = URLSession(configuration: .default)
        
        var requestURL = URLRequest(url: url)
        
        requestURL.addValue(clientID, forHTTPHeaderField: "X-Naver-Client-Id")
        requestURL.addValue(clientSecret, forHTTPHeaderField: "X-Naver-Client-Secret")
        
        let task = session.dataTask(with: requestURL) { (data, response, error) in
            if error != nil {
                completion(nil)
                return
            }
            
            guard let safeData = data else {
                completion(nil)
                return
            }
            
            if let movies = self.parseJSON(safeData) {
                completion(movies)
            } else {
                completion(nil)
            }
        }
        task.resume()
    }
    
    func parseJSON(_ movieData: Data) -> [MovieInfo]? {
        
        let decoder = JSONDecoder()
        
        do {
            let decodedData = try decoder.decode(MovieModel.self, from: movieData)
            
            let dailyLists = decodedData.items
            
            let myMovieList = dailyLists.map {
                MovieInfo(title: $0.title, image: $0.image, director: $0.director, actor: $0.actor)
            }
            
            return myMovieList
            
        } catch {
            return nil
        }
    }
}
@seungchan2 seungchan2 self-assigned this Jun 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant