๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
iOS ๐ŸŽ/iOS

ReactorKit 1

by yongmin.Lee 2022. 3. 15.

ReactorKit?

  • ReactorKit is a framework for a reactive and unidirectional Swift application architecture. 

 

ReactorKit ์žฅ์ 

  • ๋ทฐ์™€ ๋ฆฌ์•กํ„ฐ๋กœ UI์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋ถ„๋ฆฌ
  • ๋ชจ๋“ˆ๊ฐ„ ๊ฒฐํ•ฉ๋„๊ฐ€ ๋‚ฎ์•„์ง€๊ณ  ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฝ๋‹ค.
  • ์ƒํƒœ๊ด€๋ฆฌ ์šฉ์ด : API๋ฅผ ํ†ตํ•ด ์•ฑ์—์„œ ์—ฐ์†์ ์ธ ํŽ˜์ด์ง€ ๋กœ๋“œํ•  ๋•Œ ์ด์ „ ํŽ˜์ด์ง€๋ฅผ ๊ธฐ๋กํ•ด๋†“์•„์•ผ ํ•˜๋“ฏ์ด, ์ด์ „ ํŽ˜์ด์ง€๋ฅผ ๊ธฐ๋กํ•˜๋Š” ์ƒํƒœ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋”ฐ๋กœ ๊ด€๋ฆฌ๋˜๊ฒŒ๋” ์„ค๊ณ„๋œ ๊ตฌ์กฐ

 

 

 

Unidirectional Data Flow

1. View๋Š” Action(์‚ฌ์šฉ์ž ์ž…๋ ฅ ๋“ฑ)์„ Reactor์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค

2. Reactor๋Š” ์ „๋‹ฌ๋ฐ›์€ Action์— ๋”ฐ๋ผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

2-1. mutate()

  • Action ์ŠคํŠธ๋ฆผ์„ Mutation ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์—ญํ• 
  • action์ด state๋ฅผ ๋ฐ”๋กœ ๋ณ€๊ฒฝํ•˜์ง€๋Š” ์•Š์Œ (ex : ์‚ฌ์šฉ์ž๋ฅผ ํŒ”๋กœ์šฐํ•˜๋Š” api ์š”์ฒญ ์ดํ›„ state ๋ณ€๊ฒฝ) -> action๊ณผ state ์‚ฌ์ด์— mutation์„ ๋‘ฌ์„œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ
  • ์ด๊ณณ์—์„œ ๋„คํŠธ์›Œํ‚น์ด๋‚˜ ๋น„๋™๊ธฐ ๋กœ์ง ๋“ฑ์˜ ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค.
  • ๊ทธ ๊ฒฐ๊ณผ๋กœ Mutation์„ ๋ฐฉ์ถœํ•˜๋Š”๋ฐ, ๊ทธ ๊ฐ’์ด reduce() ํ•จ์ˆ˜๋กœ ์ „๋‹ฌ๋œ๋‹ค.

2-2. reduce()

  • ์ด์ „ ์ƒํƒœ์™€ Mutation์„ ๋ฐ›์•„์„œ ๋‹ค์Œ ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

3. ๊ทธ ํ›„ Reactor๋Š” ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ View์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค 

 

View

  • ๋ทฐ๋Š” ์ƒํƒœ๋ฅผ ํ‘œํ˜„, ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ๋‚˜ ์…€๋„ ๋ชจ๋‘ ๋ทฐ์— ํ•ด๋‹น.
  • View ํ”„๋กœํ† ์ฝœ์„ ์ค€์ˆ˜ํ•˜๋ฉด DisposeBag ์†์„ฑ๊ณผ bind(reactor:) ๋ฉ”์„œ๋“œ๋ฅผ ํ•„์ˆ˜๋กœ ์ •์˜ํ•จ์œผ๋กœ์จ ๋ทฐ๋ฅผ ์ •์˜
  • bind()๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋ทฐ์™€ ๋ฆฌ์—‘ํ„ฐ ์‚ฌ์ด์˜ ์•ก์…˜ ์ŠคํŠธ๋ฆผ๊ณผ ์ƒํƒœ ์ŠคํŠธ๋ฆผ์„ ๋ฐ”์ธ๋”ฉ

 

Reactor

  • ๋ฆฌ์•กํ„ฐ๋Š” ๋ทฐ์˜ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌ.
  • ๋ทฐ์—์„œ ์ „๋‹ฌ๋ฐ›์€ action์— ๋”ฐ๋ผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•œ ๋’ค ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ ๋‹ค์‹œ ๋ทฐ์— ์ „๋‹ฌ.
  • Reactor ํ”„๋กœํ† ์ฝœ์„ ์ค€์ˆ˜ํ•˜๋ฉด,  Action, Mutation, State, initialState ํ”„๋กœํผํ‹ฐ ์ •์˜
  • Action : represent user actions
  • Mutation : represent state changes
  • State : represents the current view state
  • mutate() : mutate() receives an Action and generates an Observable<Mutation>
  • reduce() :  reduce() generates a new State from a previous State and a Mutation.

 

View Testing

func testAction_refresh() {
  // 1. Stub ๋ฆฌ์•กํ„ฐ๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค.
  let reactor = MyReactor()
  reactor.stub.isEnabled = true

  // 2. Stub๋œ ๋ฆฌ์•กํ„ฐ๋ฅผ ์ฃผ์ž…ํ•œ ๋ทฐ๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค.
  let view = MyView()
  view.reactor = reactor

  // 3. ์‚ฌ์šฉ์ž ์ธํ„ฐ๋ž™์…˜์„ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
  view.refreshControl.sendActions(for: .valueChanged)

  // 4. Reactor์— ์•ก์…˜์ด ์ž˜ ์ „๋‹ฌ๋˜์—ˆ๋Š”์ง€๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  XCTAssertEqual(reactor.stub.actions.last, .refresh)
}

func testState_isLoading() {
  // 1. Stub ๋ฆฌ์•กํ„ฐ๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค.
  let reactor = MyReactor()
  reactor.stub.isEnabled = true

  // 2. Stub๋œ ๋ฆฌ์•กํ„ฐ๋ฅผ ์ฃผ์ž…ํ•œ ๋ทฐ๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค.
  let view = MyView()
  view.reactor = reactor

  // 3. ๋ฆฌ์•กํ„ฐ์˜ ์ƒํƒœ๋ฅผ ์ž„์˜๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  reactor.stub.state.value = MyReactor.State(isLoading: true)

  // 4. ๊ทธ ๋•Œ ๋ทฐ ์ปดํฌ๋„ŒํŠธ์˜ ์†์„ฑ์ด ์ž˜ ๋ณ€ํ•˜๋Š”์ง€๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  XCTAssertEqual(view.activityIndicator.isAnimating, true)
}
  • ์‚ฌ์šฉ์ž ์ธํ„ฐ๋ž™์…˜์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ Action์ด ๋ฆฌ์•กํ„ฐ๋กœ ์ž˜ ์ „๋‹ฌ๋˜๋Š”์ง€
  • ๋ฆฌ์•กํ„ฐ์˜ ์ƒํƒœ๊ฐ€ ๋ฐ”๋€Œ์—ˆ์„ ๋•Œ ๋ทฐ์˜ ์ปดํฌ๋„ŒํŠธ ์†์„ฑ์ด ์ž˜ ๋ณ€๊ฒฝ๋˜๋Š”์ง€
  • ๋ฆฌ์•กํ„ฐ์˜ stub ๊ธฐ๋Šฅ์„ ์ด์šฉํ•˜๋ฉด ๋ฆฌ์•กํ„ฐ๊ฐ€ ๋ฐ›์€ Action์„ ๋ชจ๋‘ ๊ธฐ๋กํ•˜๊ณ , mutate()์™€ reduce()๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋Œ€์‹  ์™ธ๋ถ€์—์„œ ์ƒํƒœ๋ฅผ ์„ค์ •

 

Reactor Testing

func testBookmark() {
  // 1. ๋ฆฌ์•กํ„ฐ๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค.
  let reactor = MyReactor()

  // 2. ๋ฆฌ์•กํ„ฐ์— ์•ก์…˜์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
  reactor.action.onNext(.toggleBookmarked)

  // 3. ๋ฆฌ์•กํ„ฐ์˜ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š”์ง€๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  XCTAssertEqual(reactor.currentState.isBookmarked, true)
}

func testUnbookmark() {
  // 1. ๋ฆฌ์•กํ„ฐ๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค. ์•ก์…˜์„ ๋ฏธ๋ฆฌ ํ•œ ๋ฒˆ ์ „๋‹ฌํ•ด์„œ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ๋งŒ๋“ค์–ด๋‘ก๋‹ˆ๋‹ค.
  let reactor = MyReactor()
  reactor.action.onNext(.toggleBookmarked)

  // 2. ๋ฆฌ์•กํ„ฐ์— ์•ก์…˜์„ ํ•œ ๋ฒˆ ๋” ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
  reactor.action.onNext(.toggleBookmarked)

  // 3. ๋ฆฌ์•กํ„ฐ์˜ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š”์ง€๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  XCTAssertEqual(reactor.currentState.isBookmarked, false)
}
  • Action์„ ๋ฐ›์•˜์„ ๋•Œ ์›ํ•˜๋Š” State๋กœ ์ž˜ ๋ณ€๊ฒฝ๋˜๋Š”์ง€

 

 

 

 

 

์ฐธ๊ณ ์ž๋ฃŒ

https://velog.io/@sso0022/iOS-ReactorKit-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0

https://www.slideshare.net/devxoul/reactorkit/1

https://eunjin3786.tistory.com/100

https://github.com/ReactorKit/ReactorKit

https://ios-development.tistory.com/783

https://medium.com/styleshare/reactorkit-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-c7b52fbb131a

https://you9010.tistory.com/289

'iOS ๐ŸŽ > iOS' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

Pixel, PT, PPI  (0) 2022.03.16
Deep Link : URI Scheme vs Universal Link  (0) 2022.03.16
iOS ๋ฉ”๋ชจ๋ฆฌ ๊ตฌ์กฐ  (0) 2022.03.04
iOS File System = SandBox ๊ตฌ์กฐ  (0) 2022.03.04
Frame-base layout vs Auto layout  (0) 2022.03.02