๐ŸŽ Apple/iOS

[iOS] ReactorKit ๋“ค์—ฌ๋‹ค๋ณด๊ธฐ

JINiOS 2024. 5. 17. 00:35
728x90

1. Reactorkit ์†Œ๊ฐœ

Reactorkit์€ Swift๋กœ ์ž‘์„ฑ๋œ ๋ฆฌ์•กํ‹ฐ๋ธŒ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, ์ฃผ๋กœ ์ƒํƒœ ๊ด€๋ฆฌ์™€ ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์ œ์–ด๋ฅผ ์‰ฝ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” iOS ์•ฑ ๊ฐœ๋ฐœ์—์„œ ๋ฆฌ์•กํ‹ฐ๋ธŒ ํŒจํ„ด์„ ์ฑ„ํƒํ•˜์—ฌ ์ผ๊ด€๋œ ์ƒํƒœ ๊ด€๋ฆฌ์™€ ๋ฐ˜์‘ํ˜• UI ์—…๋ฐ์ดํŠธ๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.


์ด๋ฒˆ ๊ธ€์—์„œ๋Š” Reactorkit์˜ ๊ธฐ๋ณธ ๊ฐœ๋…๊ณผ ์ž‘๋™ ๋ฐฉ์‹, ์–ด๋–ค ์žฅ์ ์ด ์žˆ๋Š”์ง€ ๋ฆฌ๋“œ๋ฏธ๋ฅผ ๋ฒˆ์—ญํ•ด๋ณด๋ฉฐ ์†Œ๊ฐœํ•ด๋“œ๋ฆฌ๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

 

2. Reactorkit์˜ ๊ธฐ๋ณธ ๊ฐœ๋…

Reactorkit์˜ ์ •์˜

 

ReactorKit์€ Flux์™€ Reactive ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ์กฐํ•ฉ์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์•ก์…˜๊ณผ ๋ทฐ์˜ ์ƒํƒœ๋Š” ๊ด€์ฐฐ ๊ฐ€๋Šฅํ•œ ์ŠคํŠธ๋ฆผ์„ ํ†ตํ•ด ๊ฐ ๋ ˆ์ด์–ด์— ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ์ด ์ŠคํŠธ๋ฆผ์€ ๋‹จ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ทฐ๋Š” ์•ก์…˜๋งŒ ๋‚ด๋ณด๋‚ผ ์ˆ˜ ์žˆ๊ณ , ๋ฆฌ์•กํ„ฐ๋Š” ์ƒํƒœ๋งŒ ๋‚ด๋ณด๋‚ผ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ ์ฒ˜๋Ÿผ ๋ง์ž…๋‹ˆ๋‹ค!

 

 

Reactorkit์˜ ์ž‘๋™ ๋ฐฉ์‹

์ž‘๋™ ๋ฐฉ์‹์„ ๊ฐ€๋ณ๊ฒŒ! ์•„์ฃผ ๊ฐ€๋ณ๊ฒŒ ์ดํ•ดํ•ด๋ณผ๊นŒ์š”?!


์ด์ „์— ์ž‘์„ฑํ•œ ๋ฐ”์™€ ๊ฐ™์ด Reactorkit์€ ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

1) ์‚ฌ์šฉ์ž์˜ Action์ด ๋ฐœ์ƒํ•˜๋ฉด, ์ด ์•ก์…˜์€ Reactor๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.

2) Reactor๋Š” action์— ๋Œ€ํ•œ State๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ ,

3) ์ด State๊ฐ€ View์— ๋ฐ˜์˜๋˜์–ด UI๊ฐ€ ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค.

 

Reactorkit์˜ ๋””์ž์ธ ๋ชฉํ‘œ

Reactorkit์˜ ๋””์ž์ธ ๋ชฉํ‘œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค๊ณ  ๊ธฐ์ˆ ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ์„ฑ : ReactorKit์˜ ์ฒซ ๋ฒˆ์งธ ๋ชฉ์ ์€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋ทฐ์—์„œ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฝ”๋“œ๋ฅผ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฆฌ์•กํ„ฐ๋Š” ๋ทฐ์— ๋Œ€ํ•œ ์ข…์†์„ฑ์ด ์—†์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฆฌ์•กํ„ฐ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ณ  ๋ทฐ ๋ฐ”์ธ๋”ฉ์„ ํ…Œ์ŠคํŠธํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
  • ์†Œ๊ทœ๋ชจ๋กœ ์‹œ์ž‘ ๊ฐ€๋Šฅ : ReactorKit์€ ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋‹จ์ผ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋”ฐ๋ฅผ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ReactorKit์€ ํŠน์ • ๋ทฐ์— ๋Œ€ํ•ด ๋ถ€๋ถ„์ ์œผ๋กœ ์ฑ„ํƒ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด ํ”„๋กœ์ ํŠธ์—์„œ ReactorKit์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋ชจ๋“  ๊ฒƒ์„ ๋‹ค์‹œ ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
  • ๊ฐ„๊ฒฐํ•œ ์ฝ”๋“œ : ReactorKit์€ ๊ฐ„๋‹จํ•œ ์ž‘์—…์„ ์œ„ํ•ด ๋ณต์žกํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์„ ํ”ผํ•˜๋Š” ๋ฐ ์ค‘์ ์„ ๋‘ก๋‹ˆ๋‹ค. ReactorKit์€ ๋‹ค๋ฅธ ์•„ํ‚คํ…์ฒ˜์— ๋น„ํ•ด ๋” ์ ์€ ์ฝ”๋“œ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ์‹œ์ž‘ํ•˜๊ณ  ํ™•์žฅํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

3. Reactorkit์˜ ๊ตฌ์„ฑ

1) View

View๋Š” data๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์—ญํ• ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ์™€ ์…€์€ ๋ทฐ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ๋ทฐ๋Š” ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ์ž‘์—… ์ŠคํŠธ๋ฆผ์— ๋ฐ”์ธ๋”ฉํ•˜๊ณ  ๋ทฐ ์ƒํƒœ๋ฅผ ๊ฐ UI ์ปดํฌ๋„ŒํŠธ์— ๋ฐ”์ธ๋”ฉํ•ฉ๋‹ˆ๋‹ค. ๋ทฐ ๋ ˆ์ด์–ด์—๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์—†์Šต๋‹ˆ๋‹ค. ๋ทฐ๋Š” ๋‹จ์ง€ ์•ก์…˜ ์ŠคํŠธ๋ฆผ๊ณผ ์ƒํƒœ ์ŠคํŠธ๋ฆผ์„ ๋งคํ•‘ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

 

๋ทฐ๋ฅผ ์ •์˜ํ•˜๋ ค๋ฉด ๊ธฐ์กด ํด๋ž˜์Šค๊ฐ€ View๋ผ๋Š” ํ”„๋กœํ† ์ฝœ์„ ๋”ฐ๋ฅด๋„๋ก ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ํด๋ž˜์Šค๋Š” ์ž๋™์œผ๋กœ reactor๋ผ๊ณ  ์ด๋ฆ„์ด ์ง€์ •๋œ ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด ํ”„๋กœํผํ‹ฐ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋ทฐ ์™ธ๋ถ€์—์„œ ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค.

class ProfileViewController: UIViewController, View {
  var disposeBag = DisposeBag()
}

profileViewController.reactor = UserViewReactor() // reactor ์ฃผ์ž…

 

 

reactor ํ”„๋กœํผํ‹ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด bind(reactor:)๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. ์ž‘์—… ์ŠคํŠธ๋ฆผ๊ณผ ์ƒํƒœ ์ŠคํŠธ๋ฆผ์˜ ๋ฐ”์ธ๋”ฉ์„ ์ •์˜ํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜์˜ ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜์„ธ์š”.

func bind(reactor: ProfileViewReactor) {
  // action (View -> Reactor)
  refreshButton.rx.tap.map { Reactor.Action.refresh }
    .bind(to: reactor.action)
    .disposed(by: self.disposeBag)

  // state (Reactor -> View)
  reactor.state.map { $0.isFollowing }
    .bind(to: followButton.rx.isSelected)
    .disposed(by: self.disposeBag)
}

 

2) Reactor

Reactor๋Š” ๋ทฐ์˜ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” UI ๋…๋ฆฝ์ ์ธ ๋ ˆ์ด์–ด์ž…๋‹ˆ๋‹ค. Reactor์˜ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์—ญํ• ์€ ๋ทฐ๋กœ๋ถ€ํ„ฐ ์ œ์–ด ํ๋ฆ„์„ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ๋ทฐ๋Š” ํ•ด๋‹น ๋ทฐ์— ๋Œ€์‘ํ•˜๋Š” Reactor๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, ๋ชจ๋“  ๋กœ์ง์„ ํ•ด๋‹น Reactor์— ์œ„์ž„ํ•ฉ๋‹ˆ๋‹ค.

Reactor๋Š” ๋ทฐ์— ๋Œ€ํ•œ ์˜์กด์„ฑ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ์‰ฝ๊ฒŒ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

Reactor๋ฅผ ์ •์˜ํ•˜๋ ค๋ฉด Reactor ํ”„๋กœํ† ์ฝœ์„ ์ค€์ˆ˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ํ”„๋กœํ† ์ฝœ์€ ์„ธ ๊ฐ€์ง€ ํƒ€์ž…(Action, Mutation, State)์„ ์ •์˜ํ•  ๊ฒƒ์„ ์š”๊ตฌํ•˜๋ฉฐ, initialState๋ผ๋Š” ์†์„ฑ์„ ํ•„์š”๋กœ ํ•ฉ๋‹ˆ๋‹ค.

Action์€ ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ์„ ๋‚˜ํƒ€๋‚ด๊ณ , State๋Š” ๋ทฐ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. Mutation์€ Action๊ณผ State ์‚ฌ์ด์˜ ๋‹ค๋ฆฌ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. Reactor์˜ ๋‚ด๋ถ€์—์„œ๋Š” ์•ก์…˜ ์ŠคํŠธ๋ฆผ์„ ์ƒํƒœ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋‘ ๋‹จ๊ณ„, ์ฆ‰ mutate()์™€ reduce()๋ฅผ ํ†ตํ•ด ์ด๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

class ProfileViewReactor: Reactor {
  // Action: ์œ ์ €์˜ ์•ก์…˜์„ ํ‘œํ˜„
  enum Action {
    case refreshFollowingStatus(Int)
    case follow(Int)
  }

  // Mutation: ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ํ‘œํ˜„
  enum Mutation {
    case setFollowing(Bool)
  }

  // State: ํ˜„์žฌ ๋ทฐ ์ƒํƒœ๋ฅผ ํ‘œํ˜„
  struct State {
    var isFollowing: Bool = false
  }

  // ์ดˆ๊ธฐ ์ƒํƒœ ์ง€์ •
  let initialState: State = State()
}
 

 

 

 

3) Reactor - mutate()

mutate()๋Š” Action์„ ๋ฐ›์•„์„œ Observable<Mutation>์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

func mutate(action: Action) -> Observable<Mutation>

 

 

 

๋น„๋™๊ธฐ ์ž‘์—…์ด๋‚˜ API ํ˜ธ์ถœ๊ฐ™์€ side effect๋“ค์€ ์•„๋ž˜์˜ ๋ฉ”์„œ๋“œ์—์„œ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค.

func mutate(action: Action) -> Observable<Mutation> {
  switch action {
  case let .refreshFollowingStatus(userID): // receive an action
    return UserAPI.isFollowing(userID) // create an API stream
      .map { (isFollowing: Bool) -> Mutation in
        return Mutation.setFollowing(isFollowing) // convert to Mutation stream
      }

  case let .follow(userID):
    return UserAPI.follow()
      .map { _ -> Mutation in
        return Mutation.setFollowing(true)
      }
  }
}

 

 

4) Reactor - reduce()

reduce()๋Š” State์™€ Mutation์œผ๋กœ๋ถ€ํ„ฐ ์ƒˆ๋กœ์šด State๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

func reduce(state: State, mutation: Mutation) -> State
 

 

reduce() ๋ฉ”์„œ๋“œ๋Š” ์ˆœ์ˆ˜ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ๋Š” ์˜ค์ง ์ƒˆ๋กœ์šด State๋ฅผ ๋™๊ธฐ์ ์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ž‘์—…๋งŒ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜ ๋‚ด์—์„œ๋Š” side effects๋ฅผ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

func reduce(state: State, mutation: Mutation) -> State {
  var state = state // create a copy of the old state
  switch mutation {
  case let .setFollowing(isFollowing):
    state.isFollowing = isFollowing // manipulate the state, creating a new state
    return state // return the new state
  }
}

 

5) Reactor - transform()

transform()๋Š” ๊ฐ ์ŠคํŠธ๋ฆผ์„ ๋ณ€ํ™˜ํ•˜๋ฉฐ, ์„ธ๊ฐ€์ง€์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

func transform(action: Observable<Action>) -> Observable<Action>
func transform(mutation: Observable<Mutation>) -> Observable<Mutation>
func transform(state: Observable<State>) -> Observable<State>

 

 

์ด ๋ฉ”์„œ๋“œ๋“ค์„ ๊ตฌํ˜„ํ•˜์—ฌ ๋‹ค๋ฅธ Observable ์ŠคํŠธ๋ฆผ๊ณผ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜ ๊ฒฐํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋”์šฑ ์ž์„ธํ•œ ๋‚ด์šฉ์€ Global States์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”!

 

 

4. Reactorkit์˜ ์žฅ์ 

Reactorkit์˜ ์žฅ์ ์€ ์•„๋ž˜์™€ ๊ฐ™์ด Reactorkit์ด ๋””์ž์ธ๋œ ๋ชฉ์ ์—์„œ๋ถ€ํ„ฐ ํ™•์žฅ๋œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.


- ์ผ๊ด€๋œ ์ƒํƒœ ๊ด€๋ฆฌ: ๋ชจ๋“  ์ƒํƒœ ๋ณ€ํ™”๊ฐ€ ์ •์˜๋˜์–ด์žˆ์–ด ์˜ˆ์ธก ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.
- ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ: ๋ทฐ์™€ ๋กœ์ง์ด ๋ถ„๋ฆฌ๋˜์–ด์žˆ์–ด ์ƒํƒœ ๋ณ€ํ™”์™€ ์•ก์…˜์„ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.
- ํ™•์žฅ์„ฑ ๋ฐ ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด์„ฑ: ๋ชจ๋“ˆํ™”๋œ ๊ตฌ์กฐ ๋•๋ถ„์— ํ™•์žฅ๊ณผ ์œ ์ง€๋ณด์ˆ˜์— ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค.

 

5. ๋งˆ๋ฌด๋ฆฌ

์ง€๊ธˆ๊นŒ์ง€ Reactorkit์„ ๊ฐ€๋ณ๊ฒŒ ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ข…์ข… ํšŒ์‚ฌ์˜ ์šฐ๋Œ€์‚ฌํ•ญ์—์„œ ์ ‘ํ•ด๋ณผ ์ˆ˜ ์žˆ์—ˆ๋˜ ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ดํŽด๋ณด๋‹ˆ, ํ•œ๊ตญ ๊ฐœ๋ฐœ์ž๋ถ„๋“ค์ด ๋งŒ๋“  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ผ๋Š” ์ ์ด ์ฐธ ์ธ์ƒ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค ๐Ÿ™ˆ

 

ํ•ด๋‹น ๊ธ€์„ ํ†ตํ•ด Reactorkit์„ ์ดํ•ดํ•˜๋Š”๋ฐ ๋„์›€์ด ๋˜์…จ๊ธธ ๋ฐ”๋ผ๋ฉฐ, ๋‹ค์Œ์—๋Š” ๋ฆฌ๋“œ๋ฏธ์˜ ์˜ˆ์‹œ์— ์žˆ๋Š” ToDoList ์•ฑ ๊ฐœ๋ฐœ์—์„œ ํ™œ์šฉํ•ด๋ณด๊ณ  ๋‹ค์‹œ ๋Œ์•„์˜ค๊ฒ ์Šต๋‹ˆ๋‹ค. ๐Ÿฃ

 

 

https://github.com/ReactorKit/ReactorKit?tab=readme-ov-file

728x90