Search

[라이브러리 정리] ReSwift

ReSwift 발표 자료 Figma

contents

ReSwift 발표 Script

Start, Contents

안녕하세요 여러분들. 벌써 마지막 세션이네요. 늦은 시간이니만큼 얼른 발표해보도록 하겠습니다. 오늘 저는 Redux Swift, ReSwift!라는 주제로 발표를 하게되었구요. 저도 실제로 사용해본 적은 없고 항상 공부하고 싶었던 주제였는데, 이번 기회를 빌어서 공부할 수 있어서 너무 좋았던 서드 파티 라이브러리였습니다. 일단 목차를 보면서 어떤 순서로 진행이 되는지 볼게요. 첫 번째Redux에 대해서 알아보려고 합니다. ReSwift의 Base는 Redux이기 때문에 ReSwift를 공부하기 전에 Redux를 공부하는 게 좋을 것 같아서 가져와봤구요. 두 번째로는 앞에서 배운 Redux를 기반으로 ReSwift에 대해서 알아보려고 합니다. 그리고 마지막으로 Example를 같이 보면서 ReSwift를 실제 코드로 보는 시간을 가져보겠습니다.

Redux-Background

여러분들은 Redux라는 라이브러리를 들어보신 적 있나요? 아마 React, 프론트엔드를 내가 쬐깐 해봤다 하시는 분들은 다들 들어보셨을 거예요. Redux는 JavaScript, 프론트엔드와 아주 찰떡 궁합인 라이브러리인데요. 우리가 Redux를 알아보기 전에 이 Redux가 어떻게 생겨났는지, 등장을 어떻게 하게 되었는지 먼저 알아보는 게 좋을 거 같아서 등장 배경에 대해서 제가 알아왔습니다. 리덕스가 등장하기 이전 프론트엔드에서 데이터의 흐름을 관리하는 방식은 MVC패턴이었다고 해요. 여러분들도 아시다시피 MVC의 큰 특징 중 하나가 '양방향 데이터 흐름'입니다. 모델이 변경된다면 뷰 또한 변경되고, 사용자에 의해 뷰에서 변경이 일어난다면 모델 또한 변경이 됩니다. 이러한 양방향 데이터 흐름은 설계하기 간단하고 코드 작성이 쉽다는 장점이 있어요. 하지만 어플리케이션 규모가 커진다면 문제가 생기게 됩니다. 한 개의 모델이 여러 개의 뷰를 조작하고 한 개의 뷰가 여러 개의 모델을 조작한다면 데이터 흐름을 이해하기 힘들어질거예요. 우리는 아마 버그가 나타나도 버그를 찾기 어려워질테고 데이터 흐름을 추적하는 데 많은 시간을 투자해야할겁니다. 이런 MVC패턴에 대한 대안으로 2014년 페이스북에서는 Flux라는 새로운 아키텍쳐 패턴을 개발하게 됩니다. 기존의 어플리케이션과 달리 페이스북은 굉장히 많은 뷰와 모델이 필요하게 됐는데 거기에 사용자와의 인터렉션 또한 기존 서비스보다 훨씬 많아졌던거죠. 그로 인해서 많은 버그들이 발생하게 되었고 수 많은 양방향의 데이터 흐름 때문에 버그를 수정하기도 힘들었다고 합니다. 대표적으로 페이스북의 알람 정보가 제대로 업데이트되지 않아서 알람 표시가 계속 유지되는 문제가 있었다고 하는데요. 그렇기 때문에 Flux는 MVC패턴에서 겪은 복잡한 상황을 개선하는 것이 목적이었고 그 방법으로 단방향 데이터 흐름을 적용한 것입니다. View는 MVC패턴과 달리 데이터를 변경시키지 않고 Action를 넘겨줍니다. Action 은 반드시 Dispatcher를 지나게 되고 Dispatcher를 통해서 데이터 변경이 일어납니다. View는 변경된 데이터를 Store를 통해서 전달 받습니다. 이러한 단방향 데이터 흐름은 기존의 MVC패턴에 있던 상태의 전이, 즉 뷰와 모델 사이의 데이터 변경이 연결된 수많은 곳으로 따라 변경되는 현상을 없애주고 어떻게 상태가 변할지 예측이 가능하다는 특징을 가지게 됩니다. 2015년에는 Dan Abramov가 React + Flux 구조에 Reducer를 결합한 Redux를 개발하게 됩니다.
아까 우리가 Flux를 본 바로는 Flux가 전에 있던 문제들을 충분히 해결했다고 봤어요. 근데 그 전에 겪었던 문제들을 충분히 해결했는데 왜 굳이 리덕스를 사용해야 할까? 라는 의문이 들겁니다. 이 질문에 제작자 Dan Abramov가 바로 이렇게 직접 답을 해주었는데, 첫째 리듀서의 구성때문에. 둘째 서버 랜더링때문에. 셋째 좋은 개발자 경험을 할 수 있어서. 그리고 마지막으로 간결성이 있기 때문에 Flux보다 Redux를 사용하는 것이 좋다고 답변을 했습니다. 각각이 어떠한 이유를 가졌는지 설명해드리고 싶지만 시간관계상 여러분들이 원문 링크를 통해서 직접 보는게 의미가 있다고 생각이 들어서 링크를 보내드리겠습니다. 나는 이유를 좀 알고 싶다! 하시면 직접 읽어보는게 좋을 것 같습니다. 저도 여러분들께 직접 읽고서 이유를 답변드리고 싶었는데 Flux에 대한 얘기도 나오고 그러다보니 제가 아직 사용을 안해봤기때문에 약간 이해하기가 어려워서 네.. https://stackoverflow.com/questions/32461229/why-use-redux-over-facebook-flux
그럼 우리는 완벽하게 Redux가 어떻게 나타났는지 알게 되었으니깐 Redux가 뭔지 알아봅시다.

Redux

Redux는 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너입니다. 리액트 뿐만 아니라 앵귤러, 제이쿼리, 바닐라 자바스크립트 등 다양한 프레임워크에서 작동되도록 설계되었습니다. 즉 리액트만을 위한 라이브러리는 아닌거죠. 이러한 Redux를 사용하면 컴포넌트의 상태 업데이트 관련 로직을 다른 파일로 분리시켜서 효율적으로 관리할 수 있어집니다.
Redux는 상태 관리 도구입니다. 요 상태라는 게 무엇이나면
Component안에서 관리가 되는 것인데 무슨말이냐하면, 리액트로 프로젝트를 진행하면, 컴포넌트는 local state를 가지게 되고, 어플리케이션은 global state를 가지게 됩니다. local state는 global상태를 공유하게 됩니다. 즉 local state라는 게 각각의 컴포넌트가 가지는 상태인데, 이 상태는 어플리케이션을 기반으로 만들어진다는 말이죠. global state라는 건 유저의 로그인 유무에 따라서 어플리케이션의 state가 달리 보이는 걸 예를 들 수가 있겠는데. 근데 이러한 상태들이 관리하기가 어렵습니다.
일단 local state의 전달이 어렵습니다. 자식 컴포넌트 간의 다이렉트 데이터 전달이 불가능하고 자식 컴포넌트 간의 데이터 전달은 상태를 관리하는 부모 컴포넌트를 통해서 주고 받기 때문에 그런 건데요. 물론 하나의 부모 컴포넌트에 하나의 자식 컴포넌트를 가진 구조라면 최상단에서 프로퍼티로 바로 가져다주는 단순한 구조기 때문에 한번의 데이터 이동으로 값이 전달되게 될 겁니다. 하지만 프로젝트 규모가 커지고 Component의 수가 늘어나게 된다면 어떻게 될까요?
상태를 관리하기 매우 복잡해질겁니다.
상태를 관리하는 상위 컴포넌트에서 계속 내려 받아야하기 때문에 Props driling 이슈(프롭스 뜨라일링), 즉 프로퍼티 내려꽂기 문제가 발생하는 거죠. 예시를 들어서 설명을 하자면 이런겁니다.
맨 위의 최상위 컴포넌트 에이가 상태를 가지고 있어요, 그리고 컴포넌트 디에서 상태를 사용하고 싶어합니다. 하지만 디로 바로 보내주지 못하겠죠.
해당 상태를 쓰지도 않는데 B랑 C가 해당 상태를 받아서 디에다가 보내줘야 해요. 즉 필요없는 데이터 흐름이 생기게 되는 거죠. 그리고 만약에 이런 상황에서 오류가 생기게 된다면 우리는 중간에 낀 모든 컴포넌트들에서 일일이 문제점을 찾아봐야 할겁니다. 그리고 맨 위에 있는 컴포넌트도 global state를 유지하는 게 쉽지가 않을거예요. 왜냐면 모든 컴포넌트들이 해당 컴포넌트로 계속 접근해서 상태를 바꾸기 때문에 상태를 안전하게 유지하는게 어려울거예요.
이런 예시를 하나 가져왔는데. 방금 말한 복잡한 상태 관리를 움직이는 Gif로 나타낸거예요. 보시다시피 제일 하단에서 데이터가 바뀌면 가장 윗단에 알리고 그 중간에 있는 애들까지 해당 데이터를 받아서 바뀐 상태를 전달해요. 이렇게 트리형태로 보니깐 더 비효율적으로 보이지 않나요. 그래서 저희는 Redux를 사용해서 이러한 상태를
요렇게 만들어줄겁니다. 상태가 바뀌어서 Reducer가 있는 쪽으로 전달이 되면 Store에서 받아서 바뀐 상태를 해당 상태를 가진 곳에다가 뿌려주는 형식인거죠. 리덕스를 사용하면 하나의 Store를 통해서 global state를 포함한 모든 스테이트를 저장, 유지할 수 있게 되겠죠. 또한 원하는 컴포넌트로만 데이터를 전달하기 때문에 굳이 중간에 끼인 컴포넌트가 값을 받는다는 비효율적인 문제가 해결되겠죠?
근데 우리가 Redux를 더 자세히 알기 위해선 그 내부가 어떻게 생겼는지 알 필요가 있겠죠?

Redux-flow

리덕스는 이런 흐름을 가져요. UI에서 Action를 보내고 Reducer에서 해당 액션을 받아서 상태를 변화시켜주고 스토어에서 해당 상태값을 가지고 있어서 UI에게 바뀐 값을 보내주는 형식인겁니다. 찬찬히 하나씩 그 역할을 살펴보자면
Store는 상태가 관리되는 오직 하나의 공간입니다. 컴포넌트와는 별개로 리덕스는 스토어라는 공간을 생성할거예요. 그 스토어 안에는 앱에서 필요한 상태를 담을겁니다. 전체 앱 상태를 단일 데이터 구조 형태로 저장을 하는거죠. 컴포넌트들을 상태 정보가 필요하다면 스토어에 접근합니다. 그리고 상태 정보가 바뀌게 된다면 저장소는 모든 관찰자, 해당 상태를 가진 Observer들에게 상태가 바뀌었다는 걸 알리게 됩니다.
Action은 앱에서 스토어에 운반할 데이터에요. 만약 UI가 상태 변화가 필요하다면 액션을 일으켜야 합니다. 상태를 변경을 설명하는 선언적은 방법이며 액션은 코드를 포함하지 않는다고 합니다. 액션은 스토어에서 사용이 되고 Reducer로 전달이 돼요.
Action를 받는 Reducer는 어플리케이션의 상태가 어떻게 바뀌는지 특정하는 함수입니다. 현재 스토어에 저장되어 있는 상태와 들어온 액션을 받아서 필요하다면 새로운 상태를 리턴하는 함수입니다. 현 작업과 현재 앱 상태, 즉 액션과 스테이트를 기반으로 새로운 앱 상태를 생성하는거죠. 액션은 스토어에 바로 전달되는 것이 아닌 Reducer에 전달된다는 걸 꼭꼭 기억해주세요. 스토어가 알아서 상태를 바꾸는 것이 아니라 리듀서가 액션이 넘겨준 주문을 보고서 스토어의 상태를 업데이트해주는겁니다.
각각의 역할을 정리하자면 액션은 상태를 변경하는 함수를 선언해줄거고, 리듀서에서는 액션에서 선언한 함수를 구현하겠죠? 그리고 스토어에서는 상태를 보관하게 됩니다. 스토어는 Swift에서 사용하기 위해서 AppDelegate.swift파일에 선언하거나 전역변수로 선언해주면 되겠죠?
Action이 들어오고나서 Reducer에서 액션과 상태값을 기준으로 상태를 변경하면 스토어는 해당 값 상태가 변경되는지 보고 있다가
변화를 감지하고
해당 상태를 Observe하고 있던 UI Componet들에게 상태 변경을 알리게 됩니다.
제가 전체적인 흐름을 나타내는 Gif를 하나 가지고 왔어요. 처음에 UI에서 deposit를 누르는 이벤트가 발생을 합니다. Event Handler에서 이벤트를 감지해서 액션을 일으킵니다. 액션에서는 어떤 이벤트가 일어났는지 써져 있죠. 선언적인 방법이기 때문에 코드를 포함하지 않아요. 스토어는 이전 상태와 현재 액션으로 리듀서를 실행하겠죠? 그리고 그 리턴값을 새로운 상태로 저장을 할겁니다. 전에 UI의 상태에 있던 값이 0달러였기 때문에 액션을 통해서 온 10달러를 받아서 아마 상태를 10달러로 변경했을 겁니다. 그리고 스토어에서 스토어를 구독하고 있던 UI들에게 값이 업데이트 해줄 겁니다. 데이터가 변경된 컴포넌트들은 새 데이터로 강제로 다시 렌더링하기때문에 화면에 업데이트된 상태가 표시되겠죠?
근데 왜 리덕스는 이런 공식을 따라서 진행을 할까요?
바로 데이터가 한 방향으로만 흘러야하는 단방향 데이터 흐름을 가지고 있기 때문입니다. 물론 다른 분들도 전에 흐름을 보면서 아셨겠지만 리덕스는 Flux가 가지고 있는 단방향 데이터 흐름의 장점을 쇽샥 가져왔기 때문에 동일하게 단방향으로 흘러가는걸 볼 수 있어요.

Redux-3rule

우리가 더 깊게 Redux를 알아보기 위해선 Redux 3원칙을 알고 갈 필요가 있어요.
첫번째 진실은 하나의 소스로부터, Single Source of Truth입니다.
애플리케이션의 모든 상태는 하나의 스토어 안에 하나의 객체 트리 구조로 저장이 됩니다. 데이터 변경은 아까 설명했듯이 Reducer를 통해서 일어나며 그 데이터는 스토어에 저장됩니다.
부모 컴포넌트가 자식 컴포넌트에게 데이터를 넘겨줄 수 있지만 데이터의 원천은 항상 스토어여야 합니다. 모든 변경 사항이 스토어에 기록되기 때문에 데이터 흐름의 원천은 항상 스토어여야 하는거죠.
즉 데이터는 하나의 스토어로부터 와야한다는겁니다. 상황과 필요에 따라 스토어를 여러개 만들 수도 있겠지만 너무 많은 스토어를 가진 Redux라면 MVC패턴과 큰 차이가 있는지 생각해봐야합니다. 그리고 하나의 상태 트리만을 가지고 있기 때문에 디버깅에도 용이하다고 합니다.
두번째 상태는 읽기 전용이다. State is read-only.
상태를 변화시키는 유일한 방법은 무슨 일이 벌어지는 지 묘사하는 액션 객체를 전달하는 방법뿐입니다. View에서 일어나는 이벤트는 직접 데이터(상태)를 변경해서는 안됩니다.
만약 UI에서 클릭이벤트가 일어난다고 해서 스스로 상태를 true로 변경하면 안된다는 겁니다.
우리는 이 흐름을 따라서 상태값을 변경해줘야 해요. 이벤트가 일어나면 액션이 해당 이벤트를 Reducer로 전달하고 Reducer가 데이터를 변경해줍니다. 즉 데이터 변경은 Reducer만 할 수 있어요. 그 이외의 공간에서는 데이터는 읽기, Read-only 모드인거죠. 그 바뀐 데이터는 해당 상태를 Observe하고 있는 모든 UI들에게 돌아가겠죠. 이를 통해서 뷰나 네트워크 콜백에서 결코 상태를 직접 바꾸지 못 한다는 것을 보장할 수 있습니다. 모든 상태 변화는 중앙에서 관리되며 모든 액션은 엄격한 순서에 의해서 하나하나 실행되기 때문에, 신경써서 관리해야할 미묘한 경쟁 상태는 없습니다.
마지막으로 변화는 순수 함수로 작성되어야 합니다. Changes are made with pure functions.
액션에 의해 상태 트리가 어떻게 변화하는지를 지정하기 위해 프로그래머는 순수 리듀서를 작성해야 합니다.
일단 순수 함수가 뭣이냐? 함수가 실행되는 곳이 어디서든, 언제든 외부의 상태를 변경하지 않으면서 동일한 입력값에는 동일한 결과값을 반환해주는 함수입니다. 지금 보는 getName이라는 함수는 네임이 들어온다고 해서 해당 네임을 변경하지 않으면서 항상 같은 이름에 같은 String를 return해주기 때문에 순수함수라고 할 수 있습니다. 순수 리듀서는 이런 순수 함수의 특징에 2가지 특징을 더 가지고 있습니다.
첫번째 리듀서는 반드시 이전의 데이터와 액션을 매개변수로 받는다. 우리가 아까 각각의 기능을 설명할 때 리듀서는 이전 상태값과 현재 들어온 액션을 받는다고 했습니다. 바로 그 말입니다. 두번째 리듀서는 결과값으로 이전의 데이터를 변경시키지 않고 새로운 데이터를 만들어 반환한다. 리듀서는 이전의 데이터를 변경시키는 것이 아닌 바뀐 데이터를 만들어 반환하는겁니다. 이전 상태를 변경하는 대신 새로운 상태 객체를 생성해서 반환해야한다는 걸 기억해야해요. 처음에는 하나의 리듀서만으로 충분하지만 애플리케이션이 커지면서 상태 트리의 특정한 부분들을 조작하는 더 작은 리듀서로 나누는 것도 가능해집니다. 리듀서는 그저 함수이기 때문에 호출되는 순서를 정하거나 추가적인 데이터를 넘길 수도 있어요.
그래서 이 리덕스의 장점이 뭐냐? 데이터가 집중화되어 있어서 예측이 가능합니다. 아까 마지막으로 봤던 순수함수가 작성되어야 한다는 특징 때문에 어떤 데이터가 나올지 예측을 할 수 있어요. 그리고 데이터 흐름이 단뱡향이기 때문에 디버깅이 쉽습니다. 다음 순수 함수의 형태를 가지고 있어서 테스트를 하기 쉽겠죠? 마지막으로 유지보수를 하기에 좋습니다. 여기까지 우리가 리덕스를 알아봤으니깐 이젠 ReSwift를 한 번 알아볼게요.

ReSwift

ReSwift 홈페이지에 들어가게 되면 이렇게 설명이 되어있어요. Swift의 단방향 데이터 흐름 아키텍처를 Redux와 유사하게 구현해놓은 라이브러리. 하지만 Getting Started Guide부분에 이렇게 써둔 것을 보니 그냥 Redux를 Swift로 가져온거구나. 라고 생각하면 되겠습니다.
ReSwift의 흐름은 이렇게 생겼어요. 그냥 아까 봤던 Redux 플로우와 플로우가 동일합니다. 스토어 있고 리듀서있고 액션 있고.. 그냥 똑같습니다. 거기에 각각에 대한 설명이 있는데,
제가 번역을 해오면 그 고유의 의미를 잃는 기분이라서 그대로 가져왔습니다. 대충 해석을 해보자면 스토어는 전체앱에 있는 단일 데이터 구조고 오직 Action이 dispatch되면서 상태 변경 가능하고 바뀌면 Observer들한테 바뀐 상태 알려줄거라는 거.. 어디서 봤지 않나요? 네 아까 리덕스에 써 있던 설명과 동일하죠. 그 다음 액션을 보면 선언적으로 상태 변경을 describing해둔거고, 코드가 안넣어져있고 리듀서로 가서 각 액션에 따른 상태 변화를 실행할거라는 걸 보면 네 아까 리덕스에 써 있던 설명과 동일하죠. 마지막으로 리듀서를 보면 순수함수를 제공하고 새로운 앱 상태 생성하고 현재 액션과 앱 상태를 기반으로 한다. 그냥 리덕스입니다.
즉 리덕스를 안다면 우리가 ReSwift를 자세히 공부하지 않아도 단번에 이해할 수 있다는 겁니다.

ReSwift Example

그렇기 때문에 바로 실제 예시로 넘어가볼게요.
함께 구현해봅시다! 저희가 해볼 예시는 버튼을 통해서 숫자를 counting하는 간단한 예시입니다. 물론 함께 구현하지 않으실거지만, 그리고 pod로 다운을 받고 하면 너무 귀찮기 때문에 저혼자 해볼거구요. 나중에 영상 혹은 ReSwift자체에서 제공해주는 예시들을 해보시면 됩니다. 제가 지금 하는 counter도 ReSwift에 있는 예시중에 하나이기 때문이죠. 그럼 한 번 해볼게요.
"https://github.com/ReSwift/ReSwift.git"
제일 먼저 Storyboard 구성 → ViewController에 IBAction, IBOutlet 연결 → State 생성(counter) → Action 생성 두가지 액션 생성 → Reducer 생성(increase, decrease) → AppDelegate 저장소 생성 → ViewController에서 StoreSubscriber 상속, 그 안에 newState, typealias 적용 → ViewController Click이벤트 연결 → mainStore 구독시키기 → 실행
네 너무 재밌는 시간이었네요^^..

Goodbye

장점이 많은 ReSwift지만 꼭 써야하는 건 아닙니다. MVC패턴을 유지하기 힘들정도로 상태에 관리함에 있어서 복잡성이 높다면 ReSwift를 쓰는 것이 좋겠지만 그렇지 않다면 쓰지 않아도 충분히 괜찮습니다. 굳이 불필요한 라이브러리만 하나 더 임포트해서 애플리케이션 번들 사이즈를 증가시킬 뿐입니다.. 만약 내가 만든 앱이 양방향 데이터 흐름을 구현할 경우 과도한 prop driling이 생긴다거나 디버깅에 어려움이 생긴다면 그 때 사용해도 충분히 괜찮을 거 같네요.
여러분 지금까지 너무 수고 많으셨고
마다가스카 시즌2에서 뵙는거로 하겠습니다^^..