새소식

인기 검색어

iOS/Swift

[Swift] RxSwift, Combine 원리 이해하기

  • -

 

 

왜 RxSwift가 나오게 되었는지 또 RxSwift가 어떤 원리인지 알아봅시다.

비동기 함수의 콜백지옥

아래는 swift에서 json을 다운로드 하는 함수입니다.

func downloadJson(_ url: String, _ completion: ((String?) -> Void)?) {
    DispatchQueue.global().async {
        let url = URL(string: url)!
        let data = try! Data(contentsOf: url)
        let json = String(data: data, encoding: .utf8)
        DispatchQueue.main.async {
            completion?(json)
        }
    }
}

 

주소를 입력받고 데이터를 받아와 Json으로 파싱해주는 간단한 함수입니다.

네트워크 처리를 하기 위해서 비동기로 처리해주어야 하기 때문에

Json 데이터가 파싱이 끝났을 때 completion의 파라미터로 값을 넘겨주는 모습입니다.

 

그런데 여기서 json을 여러번 다운받게 된다면 콜백지옥이 되어 점점 알아보기 힘들게 됩니다.

func bind() {
        downloadJson(ID_LIST_URL) { json in
            self.idListView = json
            
            downloadJson(ADDRESS_LIST_URL) { json in
                self.addressListView = json
                
                downloadJson(NAME_LIST_URL) { json in
                    self.nameListView = json
                }
            }
        }
    }

 

어떻게 하면 콜백지옥을 해결할 수 있을까요 ??

아니 왜 콜백지옥이 생기게 되었을까요 ??

 

어떻게 하면 비동기로 생기는 데이터를 어떻게 하면 일반 함수처럼 리턴값으로 줄 수 있을까 ??

RxSwift가 나오게 된 근본적인 이유입니다.

비동기 함수를 어떻게 해야 일반함수처럼 사용할 수 있을까요 ??

다양한 방법을 시도해봅시다.

먼저 completion을 없애본다면 아래와 같을 겁니다.

func downloadJson(_ url: String) -> String? {
        var json: String?
        
        DispatchQueue.global().async {
            let url = URL(string: url)!
            let data = try! Data(contentsOf: url)
            let jsonString = String(data: data, encoding: .utf8)
            json = jsonString
        }
        
        return json
    }

 

하지만 async이기 때문에 donwloadJson함수를 호출하게 되면 json은 nil을 반환하게 됩니다.

저희가 생각했던 방법이 아니네요.

 

async를 걷어낸다면 ??

func downloadJson(_ url: String) -> String? {
        let url = URL(string: url)!
        let data = try! Data(contentsOf: url)
        let json = String(data: data, encoding: .utf8)
        return json
}

func bind() {
        DispatchQueue.global().async {
            self.idListView =  downloadJson(ID_LIST_URL)
            self.addressListView = downloadJson(ADDRESS_LIST_URL)
            self.nameListView = downloadJson(NAME_LIST_URL)
        }
    }

저희가 생각했던 방법과 비슷하지만 downloadJson을 호출하려면 반드시 비동기 처리를 해주어야 합니다.

실수로 비동기 처리를 해주지 않고 호출한다면 앱이 죽어버리게 되니까 리스크는 증가하게 됩니다.

 

비슷하긴 하지만 마찬가지로 저희가 원하는 방법은 아니네요

RxSwift, Combine의 원리

그렇다면 어떻게 하면 될까요 ?

바로 값을 한번 감싸서 새로운 데이터 타입을 만들어 주는 것 입니다.

class 나중에생기는데이터<데이터타입> {
    
    private let 데이터가생기면할일: (@escaping (데이터타입) -> Void) -> Void
    
    init(데이터가생기면할일: @escaping (@escaping (데이터타입) -> Void) -> Void) {
        self.데이터가생기면할일 = 데이터가생기면할일
    }
    
    func 나중에데이터가생기면(_ 콜백함수: @escaping (데이터타입) -> Void) {
        데이터가생기면할일(콜백함수)
    }
    
}

 

위 클래스를 새로운 데이터 타입으로 만들어주고 함수를 수정하면 아래와 같습니다.

func downloadJson(_ url: String) -> 나중에생기는데이터<String?> {
        return 나중에생기는데이터 { 콜백함수 in
            
            DispatchQueue.global().async {
                let url = URL(string: url)!
                let data = try! Data(contentsOf: url)
                let json = String(data: data, encoding: .utf8)
                
                DispatchQueue.main.async {
                    콜백함수(json)
                }
            }
            
        }
    }

func bind() {
        let idList: 나중에생기는데이터<String?> = downloadJson(ID_LIST_URL)
        idList.나중에데이터가생기면 { json in
            self.idListView = json
        }
        
        let addressList: 나중에생기는데이터<String?> = downloadJson(ADDRESS_LIST_URL)
        addressList.나중에데이터가생기면 { json in
            self.idListView = json
        }
        
        let nameList: 나중에생기는데이터<String?> = downloadJson(NAME_LIST_URL)
        nameList.나중에데이터가생기면 { json in
            self.nameListView = json
        }
    }

 

차근차근 풀어서 봅시다.

bind()함수의 첫번째 줄에서 downloadJson함수를 사용하여 나중에생기는데이터를 생성해줍니다.

 

이 나중에생기는데이터의 데이터가생기면할일은 아래와 같습니다.

DispatchQueue.global().async {
    let url = URL(string: url)!
    let data = try! Data(contentsOf: url)
    let json = String(data: data, encoding: .utf8)
    
    DispatchQueue.main.async {
        콜백함수(json)
    }
}

 

 

그 다음줄에서 나중에데이터가생기면할일을 호출하고 콜백함수를 지정해줍니다.

이제 연쇄호출로 아래와 같은 순서로 함수가 실행됩니다.

 

나중에데이터가생기면 → 데이터가생기면할일 → 콜백함수

 

idList.나중에데이터가생기면(콜백함수) {
	데이터가생기면할일(콜백함수)
}

// 데이터가생기면할일
DispatchQueue.global().async {
    let url = URL(string: url)!
    let data = try! Data(contentsOf: url)
    let json = String(data: data, encoding: .utf8)
    
    DispatchQueue.main.async {
        콜백함수(json)
    }
}

// 콜백함수
{json in
		self.idListView = json
}

 

이렇게 하게되면 함수를 호출하는 곳에서 비동기처리를 하지 않고도 일반 함수처럼 사용할 수 있게 됩니다.

 

RxCocoa 원리 맛보기

우리는 이제 RxSwift, Combine의 원리를 알게되었습니다. 이를 조금 응용하면 값이 변화할 때마다 뷰를 업데이트 해줄 수 도 있습니다.

class 나중에생기는데이터<데이터타입> {
    
    var 데이터값: 데이터타입 {
        didSet {
            데이터가생긴뒤호출될수있는함수?(데이터값)
        }
    }
    
    var 데이터가생긴뒤호출될수있는함수: ((데이터타입) -> Void)?
    
    init(_ 데이터값: 데이터타입) {
        self.데이터값 = 데이터값
    }
    
    func 바인딩하기(_ 콜백함수: @escaping (데이터타입) -> Void) {
        self.데이터가생긴뒤호출될수있는함수 = 콜백함수
    }
    
}

class ViewController {
    
    private var profile: 나중에생기는데이터<Profile> = 나중에생기는데이터(Profile(id: nil, address: nil, name: nil))
        
    
    func fetch(url: String) {
        // 네트워크 처리
        profile.데이터값 = newProfile
    }
    
    private func bind() {
        profile.바인딩하기 { profile in
            self.idLabel.text = profile.id
            self.addressLabel.text = profile.address
            self.nameLabel.text = profile.name
        }
    }
    
}

 

이처럼 다양하게 응용할 수 있으니 RxCocoa와 같은 라이브러리 코드를 한번씩 보는것을 추천드립니다.

참고자료

[SUB] 시즌2 모임 종합편 입니다.

앨런 Swift문법 마스터 스쿨 (15개의 앱을 만들면서 근본원리부터 배우는 UIKit) 강의 - 인프런

 

앨런 Swift문법 마스터 스쿨 (15개의 앱을 만들면서 근본원리부터 배우는 UIKit) 강의 - 인프런

본 강의는 비공개 강의로 기존의 Swift문법 마스터 스쿨 수강자에게 제공되는 무료 강의입니다., 본 강의는 기존 부트캠프 강의 수강자에게 제공되는 무료 비공개 강의입니다.(기존 부트캠프 강

www.inflearn.com

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.