[iOS] Combine 활용하기(2)

Han's·2023년 10월 9일
0
post-thumbnail

Combine을 활용한 다양한 API 호출

  • ApiService
enum API {
    case fetchTodos // 할 일 가져오기
    case fetchPosts // 포스트 가져오기
    case fetchUsers // 유저 가져오기
    
    var url: URL {
        switch self {
        case .fetchTodos:
            return URL(string: "https://jsonplaceholder.typicode.com/todos")!
        case .fetchPosts:
            return URL(string: "https://jsonplaceholder.typicode.com/posts")!
        case .fetchUsers:
            return URL(string: "https://jsonplaceholder.typicode.com/users")!
        }
    }
}

enum ApiService {
    static func fetchTodos() -> AnyPublisher<[Todo], Error> {
//        return URLSession.shared.dataTaskPublisher(for: API.fetchTodos.url)
//            .map { $0.data }
//            .decode(type: [Todo].self, decoder: JSONDecoder())
//            .eraseToAnyPublisher()
        
        // Alamofire 사용
        return AF.request(API.fetchTodos.url)
            .publishDecodable(type: [Todo].self)
            .value()
            .mapError { afError in
                return afError as Error
            }
            .eraseToAnyPublisher()
    }
    
    static func fetchPosts(todosCount: Int = 0) -> AnyPublisher<[Post], Error> {
//        return URLSession.shared.dataTaskPublisher(for: API.fetchPosts.url)
//            .map { $0.data }
//            .decode(type: [Post].self, decoder: JSONDecoder())
//            .eraseToAnyPublisher()
        
        // Alamofire 사용
        return AF.request(API.fetchPosts.url)
            .publishDecodable(type: [Post].self)
            .value()
            .mapError { afError in
                return afError as Error
            }
            .eraseToAnyPublisher()
    }
    
    static func fetchUsers() -> AnyPublisher<[User], Error> {
//        return URLSession.shared.dataTaskPublisher(for: API.fetchUsers.url)
//            .map { $0.data }
//            .decode(type: [User].self, decoder: JSONDecoder())
//            .eraseToAnyPublisher()
        
        // Alamofire 사용
        return AF.request(API.fetchUsers.url)
            .publishDecodable(type: [User].self)
            .value()
            .mapError { afError in
                return afError as Error
            }
            .eraseToAnyPublisher()
    }
    
    /// Todos + Posts 동시 호출
    static func fetchTodosAndPostsAtSameTime() -> AnyPublisher<([Todo], [Post]), Error> {
        let fetchedTodos = fetchTodos()
        let fetchedPosts = fetchPosts()
        
        return Publishers.CombineLatest(fetchedTodos, fetchedPosts)
            .eraseToAnyPublisher()
    }
    
    /// Todos 호출 뒤 그 결과로 Posts 호출하기 (연쇄 호출)
    static func fetchTodosAndThenPosts() -> AnyPublisher<[Post], Error> {
        fetchTodos().flatMap { todos in
            return fetchPosts(todosCount: todos.count).eraseToAnyPublisher()
        }.eraseToAnyPublisher()
    }
    
    /// Todos 호출 뒤 그 결과로 특정 조건이 성립되면 Posts 호출하기 (조건 + 연쇄 호출)
    static func fetchTodosAndPostsConditionally() -> AnyPublisher<[Post], Error> {
        return fetchTodos()
            .map { $0.count }
            .filter { $0 >= 200 }
            .flatMap { todosCount in
                return fetchPosts(todosCount: todosCount).eraseToAnyPublisher()
            }
            .eraseToAnyPublisher()
    }
}

  • ApiViewModel
final class ApiViewModel {
    private var subscriptions = Set<AnyCancellable>()
    
    func fetchTodos() {
        ApiService.fetchTodos()
            .sink { completion in
                switch completion {
                case .failure(let error):
                    print("ApiViewModel_error: \(error.localizedDescription)")
                case .finished:
                    print("finish")
                }
            } receiveValue: { todos in
                print("todos.count: \(todos.count)")
            }
            .store(in: &subscriptions)
    }
    
    func fetchPosts() {
        ApiService.fetchPosts()
            .sink { completion in
                switch completion {
                case .failure(let error):
                    print("ApiViewModel_error: \(error.localizedDescription)")
                case .finished:
                    print("finish")
                }
            } receiveValue: { posts in
                print("posts.count: \(posts.count)")
            }
            .store(in: &subscriptions)
    }
    
    func fetchTodosAndPostsAtSameTime() {
        ApiService.fetchTodosAndPostsAtSameTime()
            .sink { completion in
                switch completion {
                case .failure(let error):
                    print("ApiViewModel_error: \(error.localizedDescription)")
                case .finished:
                    print("finish")
                }
            } receiveValue: { todos, posts in
                print("todos.count: \(todos.count), posts.count: \(posts.count)")
            }
            .store(in: &subscriptions)
    }
    
    // todos 호출 후 응답으로 posts 호출
    func fetchTodosAndThenPost() {
        ApiService.fetchTodosAndThenPosts()
            .sink { completion in
                switch completion {
                case .failure(let error):
                    print("ApiViewModel_error: \(error.localizedDescription)")
                case .finished:
                    print("finish")
                }
            } receiveValue: { posts in
                print("posts.count: \(posts.count)")
            }
            .store(in: &subscriptions)
    }
    
    // todos 호출 후 응답에 따른 조건으로 posts 호출
    func fetchTodosAndPostsConditionally() {
        ApiService.fetchTodosAndPostsConditionally()
            .sink { completion in
                switch completion {
                case .failure(let error):
                    print("ApiViewModel_error: \(error.localizedDescription)")
                case .finished:
                    print("finish")
                }
            } receiveValue: { posts in
                print("posts.count: \(posts.count)")
            }
            .store(in: &subscriptions)
    }
    
    // todos 호출 후 응답에 따른 조건으로 다음 api 호출 결정
    func fetchTodosAndApiCallConditionally() {
        let shouldFetchPosts: AnyPublisher<Bool, Error> =
        ApiService.fetchTodos().map { $0.count < 200 }
            .eraseToAnyPublisher()
        
        shouldFetchPosts
            .filter { $0 == true }
            .flatMap { _ in
                return ApiService.fetchPosts()
            }.sink { completion in
                switch completion {
                case .failure(let error):
                    print("ApiViewModel_error: \(error.localizedDescription)")
                case .finished:
                    print("finish")
                }
            } receiveValue: { posts in
                print("posts.count: \(posts.count)")
            }
            .store(in: &subscriptions)
        
        shouldFetchPosts
            .filter { $0 == false }
            .flatMap { _ in
                return ApiService.fetchUsers()
            }.sink { completion in
                switch completion {
                case .failure(let error):
                    print("ApiViewModel_error: \(error.localizedDescription)")
                case .finished:
                    print("finish")
                }
            } receiveValue: { users in
                print("users.count: \(users.count)")
            }
            .store(in: &subscriptions)
    }
}
profile
🍎 iOS Developer

0개의 댓글