[RxSwift๐Ÿฆˆ] #8 Transforming Operators

๋˜์ƒยท2022๋…„ 2์›” 7์ผ
0

iOS

๋ชฉ๋ก ๋ณด๊ธฐ
8/42
post-thumbnail

raywenderlich RxSwift Ch7~8

๊ธฐ์กด ์ฝ”๋“œ๋ฅผ ์ฝ์œผ๋ฉด์„œ ๊ฐ€์žฅ ์ดํ•ด๊ฐ€ ์•ˆ๊ฐ€๋Š”!!!!!! ํŒŒํŠธ๊ฐ€ ์™”๋‹ค. ์—ฌ๊ธฐ๋ฅผ ์ œ๋Œ€๋กœ ๊ณต๋ถ€ํ•˜๋Š”๊ฒŒ ์ค‘์š”ํ•œ ๊ฒƒ ๊ฐ™๋‹ค.
๐Ÿ“• ์•ผ๊ณฐ์˜ Swift Programming ์˜ Monad ๋ฅผ ์ฝ๊ณ ๋‚˜๋‹ˆ flatMap ์ด ์กฐ๊ธˆ ๋” ์ดํ•ด๋˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค!!

1. Transforming Elements

Observable ์—์„œ ๋ฐฉ์ถœ๋˜๋Š” ์š”์†Œ๋“ค์€ ๋…๋ฆฝ์ ์ด์ง€๋งŒ, ์ด๊ฑธ ๊ทธ๋ฃน์œผ๋กœ ๋ฌถ์–ด์„œ ๋ณด๊ณ ์‹ถ์„ ๋•Œ๊ฐ€ ์žˆ๋‹ค.

1. toArray

  • Observable Sequence ์˜ ์š”์†Œ๋“ค์„ array์˜ ์š”์†Œ๋กœ ๋„ฃ๊ณ , ๋งŒ๋“ค์–ด์ง„ ๋ฐฐ์—ด์„ .next ๋กœ ๋ฐฉ์ถœํ•œ๋‹ค.

2. map

์ผ๋ฐ˜์ ์ธ map ๊ณผ ๊ฐ™๋‹ค. iterable ์š”์†Œ๋ฅผ ํ•˜๋‚˜์”ฉ ์ˆœํšŒํ•˜๋ฉด์„œ ํ•จ์ˆ˜๋ฅผ ์ ์šฉํ•ด์ฃผ๋Š” ๊ฒƒ์„ Observable ์—์„œ emit ๋˜๋Š” ์š”์†Œ๋ฅผ ํ•˜๋‚˜์”ฉ ํ•จ์ˆ˜ ์ ์šฉ์‹œ์ผœ์คŒ.


3. enumerated

ํ•ด๋‹น Observable ๊ฐ’์„ value ๋กœ ๊ฐ€์ง€๋Š” tuple ์„ ๋งŒ๋“ค์–ด์„œ ๋‚ด๋ ค์ค€๋‹ค.



2. ๋‚ด๋ถ€์˜ Observable ๋ณ€ํ™˜

Q. ๋งŒ์•ฝ... Observable ์†์„ฑ์ด Observable ์˜ next ๋กœ ๋‚ด๋ ค์˜จ๋‹ค๋ฉด..? ์–ด๋–ป๊ฒŒ ํ• ๊นŒ??

struct Student {
    var score: BehaviorSubject<Int>
}

// Student ๊ฐ€ ๋‚ด๋ ค์˜จ๋‹ค๋ฉด?

A. flatMap ์€ ์—ฐ์‚ฐ์ž ๋‚ด์— ๋ช‡ ๊ฐ€์ง€ ์—ฐ์‚ฐ์ž๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์„œ, Observable ๋‚ด๋ถ€์—์„œ Observable ์˜ ์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์ž‘์—…ํ•  ์ˆ˜ ์žˆ๋‹ค.


1. flatMap

transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable ๋ญ”๋ง์ผ๊นŒ

์œ„์˜ ๊ทธ๋ฆผ์ฒ˜๋Ÿผ, flatMap์€ ๊ฐ Observable ์„ ์ง€์ผœ๋ณด๋ฉด์„œ ํ•˜๋‚˜์˜ Observable Sequence ์—์„œ ๋‚ด๋ ค์˜จ Observable Element ๋“ค์„ ๊ฐ๊ฐ ํŽผ์ณ์„œ Observable Sequence ๋กœ ๋งŒ๋“ค๊ณ , ๊ทธ๋ ‡๊ฒŒ ์ƒ๊ธด ์—ฌ๋Ÿฌ๊ฐœ์˜ Sequence ๋ฅผ ํ•˜๋‚˜์˜ Sequence๋กœ ํ•ฉ์ณ์ค€๋‹ค. ๋ฌผ๋ก  Map ์ด๋‹ˆ๊นŒ ํ•จ์ˆ˜๋„ ์ ์šฉ์‹œ์ผœ์ค€๋‹ค.

// ์ด๋Ÿฐ ์‹์œผ๋กœ nil ๊ฐ’์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•˜๊ธฐ๋„ ํ•œ๋‹ค.
.flatMap { $0 == nil ? Observable.empty() : Observable.just($0!) }

-> ์ด ๊ฒฝ์šฐ์—๋Š” filter ๋กœ๋„ ์ถฉ๋ถ„ํžˆ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์€๋ฐ ์™œ flatMap์„ ์‚ฌ์šฉํ•œ๊ฑด๊ฐ€ ํ–ˆ๋Š”๋ฐ, ๋ณดํ†ต filter + map ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์— flatMap ์„ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ๋ฅผ ๊น”๋”ํ•˜๊ณ  ๊ฐ€๋…์„ฑ ์žˆ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.


2. flatMapLatest

flatMap + switchLatest

SwitchLatest : ๊ฐ€์žฅ ์ตœ๊ทผ์˜ Observable ์—์„œ ๊ฐ’์„ ์ƒ์„ฑํ•˜๊ณ  ์ด์ „ Observable ์˜ ๊ตฌ๋…์€ ํ•ด์ œํ•œ๋‹ค.

flatMap ๊ณผ ๊ฐ™์œผ๋‚˜ ๋“ค์–ด์˜ค๋Š” Sequence ์—์„œ ํ•ด๋‹น ์‹œ์  ๊ฐ€์žฅ ์ตœ๊ทผ์˜ Observable Sequence ์—์„œ๋งŒ ๊ฐ’์„ ํŽผ์ณ์„œ ๋‚ด๋ ค์ค€๋‹ค.

๊ฒ€์ƒ‰์ฒ˜๋Ÿผ v, e, l, o, g ๋ฅผ ์ž…๋ ฅํ–ˆ์„ ๋•Œ ์ƒˆ ๊ฒ€์ƒ‰์„ ์‹คํ–‰ํ•˜๊ณ  ์•ž์„  ๊ฒ€์ƒ‰๊ฐ’ v, ve, vel, velo ๋Š” ๋ฌด์‹œํ•ด์•ผํ•˜๋Š” ์ƒํ™ฉ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ ๋Š” ํ•˜์ง€๋งŒ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ฐ์€ ์•ˆ์˜จ๋‹ค.



3. ์ด๋ฒคํŠธ ๊ด€์ฐฐ

1. materialize

  • Observable ์„ Observable Event ๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
  • Observable ์†์„ฑ์„ ๊ฐ€์ง„ Observable ํ•ญ๋ชฉ์„ ์ œ์–ดํ•  ์ˆ˜ ์—†๊ณ , ์™ธ๋ถ€์ ์œผ๋กœ Observable ์ด ์ข…๋ฃŒ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด error ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. ๋ญ”์†Œ๋ฆฌ์•ผ2
    • ์–ด๋–ค Observable ์— ๋‚ด๋ ค์˜ค๋Š” ์š”์†Œ๊ฐ€ Observable ์ผ ๋•Œ,
    • ์–ด๋–ค Observable ์˜ ์š”์†Œ์ธ inner Observable ์—์„œ Error ๊ฐ€ ๋ฐœ์ƒํ•ด์„œ ์›๋ž˜ Observable ๊นŒ์ง€ ์ข…๋ฃŒ๋˜์–ด๋ฒ„๋ฆฌ๋ฉด ๊ณค๋ž€.
    • ๊ทธ๋ž˜์„œ materialize ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์‹ค์ œ ๊ฐ’ ๋Œ€์‹  .next(๊ฐ’) .error(์—๋Ÿฌ) ์ด๋ ‡๊ฒŒ ๋‚ด๋ ค์˜ค๋„๋ก ์ œ์–ด.
let studentScore = student
    .flatMapLatest {
        $0.score.materialize()
    }


์ด๋Ÿฐ ํ˜•ํƒœ๋กœ ๊ฐ’์ด ๋‚ด๋ ค์˜ค๊ฒŒ ๋œ๋‹ค.


2. demeterialize

๊ทธ๋Ÿฌ๋ฉด materialize ๋œ ๊ฐ’์€ ์–ด๋–ป๊ฒŒ ๋ฐ›์•„์„œ ์“ฐ์ง€?

let studentScore = student
    .flatMapLatest {
        $0.score.materialize()
    }

studentScore
    .filter {
        guard $0.error == nil else {
            print($0.error!)
            return false
        }
        
        return true
    }
    // error ๋ฅผ ๊ฑฐ๋ฅด๊ณ  dematerialize ๋กœ ๋ฐ›์•„์„œ ์‚ฌ์šฉํ•œ๋‹ค.
    .dematerialize()
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

์ด๋Ÿฐ ์‹์œผ๋กœ dematerialize ํ•˜์—ฌ ์‚ฌ์šฉํ•œ๋‹ค.



3. In Practice

์‹ค์ œ ์ƒํ™ฉ์—์„œ filter, map, flatMap ์„ ์‚ฌ์šฉํ•ด ๋ณด์•˜๋‹ค. ๊นƒํ—™์—์„œ RxSwift ๋ ˆํฌ์ง€ํ† ๋ฆฌ์— ์ตœ๊ทผ์— ๊ธฐ์—ฌํ•œ ์‚ฌ๋žŒ๋“ค์˜ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์™€์„œ ๋ณด์—ฌ์ฃผ๋Š” ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์—ˆ๋‹ค.

1. fetchEvent ์ฝ”๋“œ ์ž‘์„ฑ

// ActivityController > fetchEvent

let response = Observable.from([repo])
    // urlString ์„ URL ๋กœ ๋ณ€ํ™˜ํ•˜๊ณ 
    .map { urlString -> URL in
        return URL(string: "https://api.github.com/repos/\(urlString)/events")!
    }
    // URL ์„ URLRequest ๋กœ ๋ณ€ํ™˜ํ•˜๊ณ 
    .map { url -> URLRequest in
        return URLRequest(url: url)
    }
    // Request ๋ฅผ ๋ณด๋‚ด์„œ response ๋ฅผ ๋ฐ›์•„์˜จ๋‹ค.
    .flatMap { request -> Observable<(response: HTTPURLResponse, data: Data)> in
        return URLSession.shared.rx.response(request: request)
    }
    .share(replay: 1)

1. ๊ทผ๋ฐ ์™œ ์—ฌ๊ธฐ์„œ flatMap ์„ ์“ธ๊นŒ?

  • ๋ฌธ์ž์—ด, ์ˆซ์ž ๊ฐ™์€ ๋ฐฐ์—ด๋“ค์˜ Observable ๊ฐ™์ด ์ผ์‹œ์ ์œผ๋กœ ๋ฐฉ์ถœํ•˜๊ณ  ๋๋‚˜๋ฒ„๋ฆฐ Observable ์„ flatten ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š” Observable ์„ ํ†ตํ•ด ํšจ๊ณผ์ ์œผ๋กœ Observable ๋“ค์ด ๋Œ€๊ธฐํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๊ณ , ๊ทธ ๋™์•ˆ ๋‹ค๋ฅธ ์—ฐ๊ฒฐ์€ ์ž‘๋™ํ•œ๋‹ค.

-> ๋ญ”์†Œ๋ฆฌ์•ผ? : flatMap ์€ ์•ž์˜ Observable ์—์„œ ๋„˜์–ด์˜จ ๊ฒƒ๋“ค์„ flat + map ํ•ด์„œ ์ƒˆ๋กœ์šด Observable ์„ emit ํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด, ํ•˜๋‚˜์˜ item ์„ ๋ณ€ํ˜•ํ•œ ์—ฌ๋Ÿฌ item ์„ ๋ฐฉ์ถœํ•  ์ˆ˜ ์žˆ๊ณ , Observable ์„ emit ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— flatMap ๋’ค์—์„œ ๋ณ‘๋ ฌ์ ์ธ ์‹คํ–‰์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

๋งŒ์•ฝ์— ์ด ์˜ˆ์ œ์—์„œ repo ๊ฐ€ ํ•˜๋‚˜๊ฐ€ ์•„๋‹ˆ๋ผ ์—ฌ๋Ÿฌ๊ฐœ์˜€๋‹ค๋ฉด, Request ๋ฅผ ๋ณด๋‚ด๊ณ  ํ•ด๋‹น request ๋ฅผ ๋‹ค ๋ฐ›์„๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆด ํ•„์š” ์—†์ด flatMap ์ด ๋น„๋™๊ธฐ์ ์œผ๋กœ response ๋ฅผ ๋ฐ›์•„์˜ค๊ณ , ๋จผ์ € return ๋œ Observable(response) ๋ฅผ ๊ฐ€์ง€๊ณ  ์ง€์ง€๊ณ  ๋ณถ๊ณ  ์žˆ์œผ๋ฉด ๋œ๋‹ค. ๊ทธ๋ž˜์„œ repo ๋ฐฐ์—ด๊ณผ response ์˜ ์ˆœ์„œ๋Š” ๋ณด์žฅ์ด ์•ˆ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ˆœ์„œ๊ฐ€ ๋ณด์žฅ๋˜์•ผ ํ•˜๋ฉด concatMap ์„ ์‚ฌ์šฉํ•œ๋‹ค.


2. share(replay:)

return URLSession.shared.rx.response(request: request)
์ด ์ฝ”๋“œ๋Š” ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์‘๋‹ต์„ ๋ฐ›์œผ๋ฉด, ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ .next ์ด๋ฒคํŠธ๋กœ ๋”ฑ ํ•œ๋ฒˆ๋งŒ ๋ฐฉ์ถœํ•˜๊ณ  complete ๋œ๋‹ค. completed ์ดํ›„์— ๋‹ค์‹œ ๊ตฌ๋…์„ ํ•˜๊ฒŒ ๋˜๋ฉด ๋˜ request ๋ฅผ ๋ณด๋‚ด๊ณ .. ๋ฐ›๊ณ ... ํ•˜๋Š” ์‹œ๊ฐ„์ด ๊ฑธ๋ ค์„œ ๋น„ํšจ์œจ์ .

share(replay: 1) ๋กœ ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฐฉ์ถœ๋œ ์š”์†Œ๋ฅผ ๋ฒ„ํผ์— ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ฐ€ ์ƒˆ๋กœ์šด ๊ตฌ๋…์ž๊ฐ€ ์ƒ๊ธฐ๋ฉด ๋ณด๋‚ด์ค€๋‹ค.

share(replay: 1, scope: .whileConnected) - ๊ตฌ๋…์ž๊ฐ€ ์—†์–ด์ง€๋ฉด ๋ฒ„ํผ๊ฐ€ ์‚ฌ๋ผ์ง.
share(replay: 1, scope: .forever) - ๊ณ„์† ๋ฒ„ํผ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Œ.


2. response ๋ณ€ํ˜•

response ๋ฅผ ๊ตฌ๋…ํ•ด์„œ, ๋‹ค ๋ฐ›์•„์˜ค๋ฉด success ์ธ ๊ฒƒ ์ค‘ empty ์ธ ๊ฒƒ์„ ์ œ์™ธํ•˜๊ณ  Event ๋ฐฐ์—ด๋กœ decode ํ•ด์„œ processEvent ํ•จ์ˆ˜๋กœ ๋„˜๊ธด๋‹ค (events relay ์— ๋ณด๋ƒ„)

response
    .filter { response, _ in
        return 200..<300 ~= response.statusCode
    }
    // filter + map ์„ ์“ธ ๊ฒƒ์„ compactMap ์œผ๋กœ ํ•œ๋ฐฉ์— ์ฒ˜๋ฆฌํ–ˆ๋‹ค.
    .compactMap { _, data -> [Event]? in
        return try? JSONDecoder().decode([Event].self, from: data)
    }
    .subscribe(onNext: { [weak self] newEvents in
        self?.processEvents(newEvents)
    })
    .disposed(by: bag)

3. plist ์— ์ •๋ณด ์ €์žฅํ•˜๊ธฐ

// ActivityController.swift
private let eventsFileURL = self.cachedFileURL("events.plist")

// class ๋ฐ”๊นฅ์—!
func cachedFileURL(_ fileName: String) -> URL {
    return FileManager.default
        .urls(for: .cachesDirectory, in: .allDomainsMask)
        .first!
        .appendingPathComponent(fileName)
}

// processEvent ํ•˜๋‹จ
// json ๋ฐ›์•„์™€์„œ decode ํ–ˆ์œผ๋ฉด ๊ทธ๊ฑธ ๋‹ค์‹œ encode ํ•ด์„œ ์ €์žฅํ•ด๋‘”๋‹ค.
let encoder = JSONEncoder()
if let eventsData = try? encoder.encode(updatedEvents) {
    try? eventsData.write(to: eventsFileURL, options: .atomicWrite)
}

// viewDidLoad : refresh() ์ „์—
// ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ๋ถˆ๋Ÿฌ์™€์„œ events ์— ๋ณด๋‚ธ๋‹ค.
let decoder = JSONDecoder()
if let eventsData = try? Data(contentsOf: eventsFileURL) {
    let persistedEvents = try? decoder.decode([Event].self, from: eventsData)
    events.accept(persistedEvents)
}

4. request header ์ถ”๊ฐ€ํ•˜๊ธฐ

header ์— Last-Modified (์‹œ๊ฐ„ String) ๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ Last-Modified ๋ณด๋‹ค ๋” ๋’ค์— ์ˆ˜์ •๋œ (์ „์— ๋ฐ›์ง€ ์•Š์€) ์ƒˆ๋กœ์šด ์ด๋ฒคํŠธ๋งŒ ์š”์ฒญํ•˜๋„๋ก ์ตœ์ ํ™”ํ•œ๋‹ค. ํ•ด๋‹น repository ์— ์•„๋ฌด ์ด๋ฒคํŠธ๋„ ์ผ์–ด๋‚˜์ง€ ์•Š์•˜์œผ๋ฉด ๊ทธ๋ƒฅ ๋นˆ ์‘๋‹ต์„ ๋ฐ›๊ฒŒ ๋˜์–ด์„œ ์ž์›์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ๋‹ค.

// ActivityController.swift
private let modifiedFileURL = cachedFileURL("modified.txt")

private let lastModified = BehaviorRelay<String?>(value: nil)

// ActivityContrller > viewDidLoad
if let lastModifiedString = try? String(contentsOf: modifiedFileURL, encoding: .utf8) {
    lastModified.accept(lastModifiedString)
}
// fetchEvents
// url -> URLRequest ์— header ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ์ฝ”๋“œ ์ž‘์„ฑ
  .map { [weak self] url -> URLRequest in
      var request = URLRequest(url: url)
      if let modifiedHeader = self?.lastModified.value {
          request.addValue(modifiedHeader, forHTTPHeaderField: "Last-Modified")
      }
      return request
  }

// ์ƒˆ๋กœ์šด ๊ตฌ๋… ์ƒ์„ฑ. header ๊ฐ€ ์—†๋Š” ๊ฒƒ์„ ๊ฑฐ๋ฅธ๋‹ค.
response
    .filter { response, _ in
        return 200..<400 ~= response.statusCode
    }
    .flatMap { response, _ -> Observable<String> in
        guard let value = response.allHeaderFields["Last-Modified"] as? String else {
            return Observable.empty()
        }
        return Observable.just(value)
    }
    .subscribe(onNext: { [weak self] modifiedHeader in
        guard let self = self else { return }
        
        self.lastModified.accept(modifiedHeader)
        try? modifiedHeader.write(to: self.modifiedFileURL, atomically: true, encoding: .utf8)
    })
    .disposed(by: bag)

5. Challenge

๊ฐ€์žฅ ์ธ๊ธฐ๊ฐ€ ๋งŽ์€ 5๊ฐœ์˜ repository ๋งŒ ๊ฐ€์ ธ์™€์„œ ๊ฐ repository ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ด๋ฒคํŠธ๋“ค์„ ๊ฐ๊ฐ ๊ฐ€์ ธ์˜ด.


 func fetchEvents(repo: String) {
 //        let response = Observable.from(["repo"])
     let response = Observable.from(["https://api.github.com/search/repositories?q=language:swift&per_page=5"])
     
[map to convert to to URLRequest]

[flatMap to fetch JSON back]

[flatMap to convert JSON to list of repo names, and create Observable from that list]

[existing code follows below]
         .map { urlString -> URL in
             return URL(string: "https://api.github.com/repos/\(urlString)/events")!
         }
// ์ƒ์œ„ 5 ๋ ˆํŒŒ์ง€ํ† ๋ฆฌ๋ฅผ ์š”์ฒญ
.map { urlString -> URL in
    return URL(string: urlString)!
}
.flatMap { request -> Observable<Any> in
    let request = URLRequest(url: url)
    return URLSession.rx.json(request: request)
}
.flatMap { response -> Observable<String> in
    guard let response = response as? [String: Any],
          let items = response["items"] as? [[String: Any]] else {
              return Observable.empty()
          }
    return Observable.from(items.map { $0["full_name"] as! String })
}
// ๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ๊ฐ๊ฐ ์ด๋ฒคํŠธ (5ํŽ˜์ด์ง€๋งŒ) ์š”์ฒญ
.map { urlString -> URL in
    return URL(string: "https://api.github.com/repos/\(urlString)/events/per_page=5")!
}



์ถœ์ฒ˜ - https://github.com/fimuxd/RxSwift/tree/master/Lectures/07_Transforming%20Operators
https://github.com/fimuxd/RxSwift/blob/master/Lectures/08_Transforming%20Operators%20in%20Practice/Ch.8%20Transforming%20Operators%20in%20Practice.md

๋Š๋‚€์ 

๊ธฐ์กด ์ฝ”๋“œ์—์„œ flatMap ๋งŒ ๋‚˜์˜ค๋ฉด ์ด๊ฒŒ ๋ญ”๋ฐ... ํ•˜๊ณ  ๋ˆˆ์„ ๊ฐ์•˜๋Š”๋ฐ

์ด์ œ ์กฐ๊ธˆ์€ ๋ญ”์ง€ ์•Œ ๊ฒƒ ๊ฐ™๋‹ค! ๋ชจ๋“  ๊ธ€์—์„œ ํ•˜๊ณ  ์žˆ๋Š” ๋ง์ด์ง€๋งŒ ์ ์šฉ์€.. ใ…Žใ…Ž ํ•˜๋ ค๋ฉด ๋งŽ์€ ์—ฐ์Šต์ด ํ•„์š”ํ•  ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ž˜๋„ Observable type ์ด Obsevable ์„ ํ†ตํ•ด์„œ ๋‚ด๋ ค์˜ฌ ๋•Œ ๋‚ด๋ ค์˜จ Observable ์˜ Element ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ flatMap ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋ผ๋Š” ์ ์€ ํ™•์‹คํžˆ ์žก์€ ๊ฒƒ ๊ฐ™๋‹ค.

Challenge ๊ฐ€ ์—ญ๋Œ€๊ธ‰์œผ๋กœ.. ์ดํ•ด๋Š” ๊ฐ€๋Š”๋ฐ... ๋Œ€๋žต ๋ฉํ•ด์ง€๋Š” ๋Š๋‚Œ์ด์—ˆ๋‹ค. ์ƒ๊ฐ๋ณด๋‹ค 7~8 ์„ ๊ณต๋ถ€ํ•˜๋Š” ์‚ฌ์ด์— ์ž์ž˜ํ•œ ๋ฒ„๊ทธ ์žก๋Š” ์—…๋ฌด๋ฅผ ๊ฝค ๋ฐ›์•„์„œ small chapter ๋‹จ์œ„๋กœ ๊ฑฐ์˜ ์ชผ๊ฐœ ๊ณต๋ถ€๋ฅผ ํ•ด์„œ ๋‹ค์‹œ ๋”ฐ๋ผ๊ฐ€๊ธฐ๊ฐ€ ํž˜๋“ค์—ˆ๋‹ค.

profile
0๋…„์ฐจ iOS ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€