Swift/RxSwift

ReactorKit 에서 API 활용하기

iosswift 2022. 10. 15. 10:13

 

// 출처: https://nsios.tistory.com/141

(출처 코드를 일부 수정했습니다. ) 

 

 

중요한 Code 

1. Controller

class APIPracticeController: UIViewController, View {
    var disposeBag = DisposeBag()
    
    func bind(reactor: MainReactor) {
        
        loadButton.rx.tap
            .map({ Reactor.Action.touchButton(index: 0) })
            .bind(to: reactor.action)
            .disposed(by: disposeBag)
        
        reactor.state
            .map({$0.image})
            .subscribe(onNext: {[weak self] img in
                guard let img = img, let self = self else { return }
                    DispatchQueue.main.async {
                        self.centerImageView.image = img
                    }
            })
            .disposed(by: disposeBag)
    }
}

 

2. Reactor, Network 

class MainReactor: Reactor {
    let network = Network()
    
    private let URLs = ["https://avatars.githubusercontent.com/u/62657991?v=4"]
    
    enum Action {
        case touchButton(index: Int)
    }
    
    enum Mutation {
        case setImage(image: UIImage?)
    }
    
    struct State {
        var image: UIImage?
    }
    
    let initialState: State
    
    init() {
        self.initialState = State()
    }
    
    func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        case .touchButton(let index):
            return Observable.just(URLs[index])
                .flatMap({ self.network.load(url: $0) })
                .map({ Mutation.setImage(image: $0) })
        }
    }
    
    func reduce(state: State, mutation: Mutation) -> State {
        var state = state
        
        switch mutation {
        case .setImage(let image):
            state.image = image
        }
      
        return state
    }
}


class Network {
    func load(url: String) -> Single<UIImage?> {
        let request = URLRequest(url: URL(string: url)!)
        return URLSession.shared.rx.response(request: request)
            .map({ UIImage(data: $0.data) })
            .asSingle()
            
    }
}

 

API Call 이용할 때 작동 순서! 

1. 버튼을 누른다 -> Reactor 의 Action 에 전달.

2. func mutate(action: Action) 을 통해 return Mutation.setImage 

case .touchButton(let index):
            return Observable.just(URLs[index])
                .flatMap({ self.network.load(url: $0) })
                .map({ Mutation.setImage(image: $0) })
        }

3. 2 과정에서, networkCall 을 통해 URL -> Image 로 변환.

이때, flatMap 안에서 url -> Observable<UIImage> 로 변환해준다. 

 

 switch mutation {
        case .setImage(let image):
            state.image = image
        }

4. 그 후에는 func mutate 에서 위와 같이 state 의 image property 를 설정, 이어 ViewController 에 전달. 

 

 

 

 

 

 

 

전체 코드 

import Foundation
import RxSwift
import ReactorKit
import UIKit
import RxCocoa

class APIPracticeController: UIViewController, View {
    var disposeBag = DisposeBag()
    
    let loadButton: UIButton = {
        let button = UIButton()
        button.setTitle("load Image", for: .normal)
        button.setTitleColor(.systemBlue, for: .normal)
        return button
    }()
    
    init(reactor: MainReactor) {
        super.init(nibName: nil, bundle: nil)
        self.reactor = reactor
    }
    
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    lazy var centerImageView: UIImageView = {
        let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 350, height: 350))
        imageView.image = UIImage()
        imageView.center = view.center
        imageView.backgroundColor = .magenta
        return imageView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        makeUI()
        
    }
    
    func makeUI() {
        view.addSubview(centerImageView)
        view.addSubview(loadButton)
        
        loadButton.translatesAutoresizingMaskIntoConstraints = false
        loadButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        loadButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
    }
    
    func bind(reactor: MainReactor) {
        
        loadButton.rx.tap
            .map({ Reactor.Action.touchButton(index: 0) })
            .bind(to: reactor.action)
            .disposed(by: disposeBag)
        
//        reactor.state
//            .map({$0.image})
//            .bind(to: centerImageView.rx.image)
////            .bind(to: centerImageView.imag)
//            .disposed(by: disposeBag)
        
        
        reactor.state
            .map({$0.image})
            .subscribe(onNext: {[weak self] img in
                if let img = img {
                    DispatchQueue.main.async {
                        self?.centerImageView.image = img
                    }
                    
                } else {
                    print("img is nil")
                }
                
            })
            .disposed(by: disposeBag)
    }
}

 

import Foundation
import RxSwift
import ReactorKit

class MainReactor: Reactor {
    let network = Network()
    
    private let URLs = ["https://avatars.githubusercontent.com/u/62657991?v=4"]
    
    enum Action {
        case touchButton(index: Int)
    }
    
    enum Mutation {
        case setImage(image: UIImage?)
    }
    
    struct State {
        var image: UIImage?
    }
    
    let initialState: State
    
    init() {
        self.initialState = State()
    }
    
    func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        case .touchButton(let index):
            return Observable.just(URLs[index])
                .flatMap({ self.network.load(url: $0) })
                .map { img -> UIImage? in
                    return img
                }
                .map({ Mutation.setImage(image: $0) })
        }
    }
    
    func reduce(state: State, mutation: Mutation) -> State {
        var state = state
        
        switch mutation {
        case .setImage(let image):
            state.image = image
        }
        
        return state
    }
}


class Network {
    func load(url: String) -> Single<UIImage?> {
        let request = URLRequest(url: URL(string: url)!)
        print("request: \(request)")
        return URLSession.shared.rx.response(request: request)
            .map({ UIImage(data: $0.data) })
            .asSingle()
            
    }
}