Jiseob Kim

iOS Developer

(RxSwift) Single ์•Œ์•„๋ณด๊ธฐ(2)

06 Feb 2022 » RxSwift

์ด์ „ ๊ธ€์—์„œ just๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋ฐฉ์ถœ์„ ๋ณด์•˜๋‹ค.

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” create์— ๋Œ€ํ•ด ์ข€๋” ์‹ฌ๋„ ์žˆ๊ฒŒ ๋ณด๋ฉด์„œ ์›๋ฆฌ๋ฅผ ๋ณด๊ณ ์ž ํ•œ๋‹ค.


Single์˜ Create

์ด์ „ ํŽธ just๋กœ ํ•œ ๊ฒƒ์„ create๋กœ ๋งŒ๋“ค์–ด๋ณด์ž.

// 1. just๋กœ ๊ตฌํ˜„
let singleString = Single.just("hi")

// 2. create๋กœ ๊ตฌํ˜„
let singleString = Single<String>.create { singleCloser -> Disposable in
    singleCloser(.success("hi"))
    return Disposables.create()
}

๊ทธ๋Ÿฐ๋ฐ, ์—ฌ๊ธฐ์„œ ํฅ๋ฏธ๋กœ์šด ์ ์€ create์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜์ด๋‹ค.


Create ์‹œ๊ทธ๋‹ˆ์ฒ˜์˜ ์ž…๋ ฅ ํŒŒ๋ผ๋ฏธํ„ฐ

์‹œ๊ทธ๋‹ˆ์ฒ˜: ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งํ•ด์„œ (๋ฉ”์†Œ๋“œ ์ด๋ฆ„, ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ์ด๋ฆ„&์ž๋ฃŒํ˜•, ๋ฆฌํ„ด ๊ฐ’์˜ ์ž๋ฃŒํ˜•)์„ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฒƒ

public static func create(subscribe: @escaping (@escaping SingleObserver) -> Disposable) -> Single<Element>


escaping์€ ์ผ๋‹จ ๋ฌด์‹œํ•˜๊ณ  ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’๋งŒ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

subscribe: (SingleObserver) -> Disposable)


์—ฌ๊ธฐ์„œ SingleObserver์€ ๋ญ˜๊นŒ? (Disposable์€ ์•ˆ๋‹ค๋Š” ๊ฐ€์ •ํ•˜์—)

public typealias SingleObserver = (SingleEvent<Element>) -> Void


typealias์ด๊ณ  ๋˜ ๋‹ค์‹œ ์•ˆ์— ์žˆ๋Š”SingleEvent๋Š” ๋˜ typealias๋‹ค.

public typealias SingleEvent<Element> = Result<Element, Swift.Error>


๋“œ๋””์–ด ์•Œ์•„ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ ๊นŒ์ง€ ๋‚˜์™”๋‹ค.

์ฆ‰, subscribe๋Š” ํด๋กœ์ €์˜€๊ณ , ๊ทธ ํด๋กœ์ €์˜ ์ž…๋ ฅ ํŒŒ๋ผ๋ฏธํ„ฐ ๋˜ํ•œ ํด๋กœ์ €์˜€๋‹ค.


์ € Result๋ฅผ ์ด์šฉํ•ด์„œ ์„ฑ๊ณต๊ณผ ์‹คํŒจ๋ฅผ ๋‚˜ํƒ€๋‚ธ ๊ฒƒ์ด์—ˆ๋‹ค.

๋ณ„๊ฒƒ ์—†๋‹ค! ๊ทธ์ € Observable์— Result๋ฅผ ์ด์šฉํ•œ ๊ฒƒ์ด Single์ด๋‹ค.


์ด์ œ ์–ด๋–ป๊ฒŒ ์ด์šฉ์„ ํ–ˆ๋Š”์ง€ create ๋ฉ”์†Œ๋“œ์˜ ๋‚ด์šฉ์„ ๋ณด์ž.



create ๋‚ด์šฉ

// 1. Observable ์ƒ์„ฑ
let source = Observable<Element>.create { observer in

    // 2. ์ž…๋ ฅ ํŒŒ๋ผ๋ฏธํ„ฐ์˜€๋˜ subscribe๋Š” Disposable์„ ๋ฆฌํ„ด ํ•˜๊ธฐ์— return์— ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.
    return subscribe { event in
    
        // 3. event๋Š” Result์ด๋ฏ€๋กœ switch๋ฅผ ์ด์šฉํ•œ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ
        switch event {
        case .success(let element):
            // 4. ์ž…๋ ฅ ๋ฐ›์€ element๋ฅผ ๊ณง๋ฐ”๋กœ ๋ฐฉ์ถœ.
            observer.on(.next(element))
            // 5. completed ๋ฐฉ์ถœ
            observer.on(.completed)
            
        case .failure(let error):
            // 6. ์‹คํŒจ์‹œ error ๋ฐฉ์ถœ
            observer.on(.error(error))
        }
    }
}

// 7. Single<Element> ๋˜ํ•œ PrimitiveSequence์˜ typealias์ด๋‹ค.
return PrimitiveSequence(raw: source)

๋ฉ”์†Œ๋“œ ๋‚ด์šฉ์—์„œ Observable์„ ์ƒ์„ฑํ•˜์˜€๊ณ , Result์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ด์šฉํ–ˆ๋‹ค.

  • ์„ฑ๊ณต์‹œ: nextํ›„ completed ๋ฐฉ์ถœ
  • ์‹คํŒจ์‹œ: error ๋ฐฉ์ถœ


์„ฑ๊ณต๊ณผ ์‹คํŒจ๋กœ๋งŒ ๋‚˜๋ˆ„๊ธฐ ์œ„ํ•ด์„œ Observable๊ณผ Result๋ฅผ ์–ด๋–ป๊ฒŒ ์‘์šฉํ–ˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์ด๋‹ค.



create ์‹œ๊ทธ๋‹ˆ์ฒ˜์˜ escaping

์ฃผ์„ 1์„ ๋ณด๋ฉด Observable<Element>.create๋ผ๋Š” ๋ถ€๋ถ„ ๋˜ํ•œ ํด๋กœ์ €์ด๋ฉฐ,

์ด๊ฒƒ ๋˜ํ•œ escaping์œผ๋กœ ์ž‘์„ฑ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ ์•ˆ์—์„œ ์ฃผ์„2์™€ ๊ฐ™์ด subscribe๋ฅผ ์“ฐ๊ธฐ ์œ„ํ•ด์„  escaping์ด ์žˆ์–ด์•ผํ•œ๋‹ค.


์ด๊ฒŒ ์‹œ๊ทธ๋‹ˆ์ฒ˜์— ์žˆ๋˜ ์ฒซ๋ฒˆ์งธ escaping์˜ ์ด์œ ์ด๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด, ์‹œ๊ทธ๋‹ˆ์ฒ˜์˜ ๋‘๋ฒˆ์งธ escaping์€ ์™œ ํ•„์š”ํ•œ ๊ฒƒ์ผ๊นŒ?


์ด๊ฒƒ์€ ๊ธ€์˜ ์ฒซ๋ฒˆ์งธ ์ฝ”๋“œ ๋ธ”๋ก์œผ๋กœ ๊ฐ€๋ณด์ž.

let singleString = Single<String>.create { singleCloser -> Disposable in
    singleCloser(.success("hi"))
    return Disposables.create()
}

singleCloser๋ผ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค.


์—ฌ๊ธฐ์„œ ๋งŒ์•ฝ ์ง€๊ธˆ ๋‹น์žฅ success๋‚˜ failure๋ฅผ ๋„ฃ์„ ์ˆ˜ ์—†๋‹ค๋ฉด?

ํ†ต์‹ ๊ณผ ๊ฐ™์ด ๊ฐ’์„ ์ง€๊ธˆ ๋‹น์žฅ ๋ณด๋‚ผ ์ˆ˜ ์—†๊ณ , ์บก์ณํ•˜๊ณ  ํ›„์— ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผํ•œ๋‹ค๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ์ž‘์„ฑ๋  ๊ฒƒ์ด๋‹ค.

let singleString = Single<String>.create { singleCloser -> Disposable in

    let url = URL(string: "www.naver.com")!
    
    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        // 1. ํ†ต์‹  ํ›„ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ
        guard data != nil else {
            // 2. ์‹คํŒจ ์—๋Ÿฌ ๋ฐฉ์ถœ
            let error = NSError(domain: "", code: 0, userInfo: nil)
            singleCloser(.failure(error))
            return
        }
    
        // 3. ์„ฑ๊ณต ๋ฐฉ์ถœ
        singleCloser(.success("Hi"))
    }
        
    task.resume()

    return Disposables.create()
}


์—ฌ๊ธฐ์„œ singleObserver ๋˜ํ•œ ์บก์ณ๊ฐ€ ๋˜๊ธฐ ์œ„ํ•ด์„  ๋‘๋ฒˆ์งธ escaping์„ ๋„ฃ์–ด์ฃผ์ง€ ์•Š๋Š”๋‹ค๋ฉด ์ด๋Ÿฐ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜์˜จ๋‹ค.

Escaping closure captures non-escaping parameter 'singleCloser'


๋”ฐ๋ผ์„œ singleCloser๋„ escaping์ด ๋ถ™์–ด์•ผํ•œ๋‹ค.

์ด๊ฒŒ ๋‘๋ฒˆ์งธ ์ด์œ ์ด๋‹ค.



๋งˆ๋ฌด๋ฆฌ

์ด๋ ‡๊ฒŒ ์“ฐ๋‹ค๋ณด๋‹ˆ ์‹œ๊ฐ„์ด ์ƒ๊ฐ๋ณด๋‹ค ์˜ค๋ž˜๊ฑธ๋ ธ๋‹ค. ์•Œ์•˜๋‹ค ์‹ถ์—ˆ๋Š”๋ฐ, ์ •๋ฆฌ์™€๋Š” ์—ญ์‹œ ๋ณ„๊ฐœ์˜€๋‹ค.

Single์€ ํ™•์‹คํžˆ ํ†ต์‹ ๊ณผ ์ž˜ ์–ด์šธ๋ฆฐ๋‹ค ์ƒ๊ฐ์ด ๋œ๋‹ค. ์•ž์œผ๋กœ๋Š” ํ†ต์‹  ๋ชจ๋“ˆ ๋ถ™์ผ๋•Œ ์ ๊ทน์ ์œผ๋กœ ๋„์ž…์„ ํ•ด๋ด์•ผ๊ฒ ๋‹ค.


๋!