'Binding'에 해당하는 글 2건

옮긴이 : 종종 브라우저로 원문 링크에 들어가지지 않는 현상이 생깁니다. 이럴때는 Medium 앱으로 Architecture of Node.js Internal Codebase를 검색하여 들어가시면 됩니다.


첫째로 JavaScript라는 단어에 대해...

StackOverflow 공동 창시자인 Jeff Atwood가 Coding Horror 프로그래밍 블로그에 글을 올렸다.
"JavaScript로 작성될 수 있는 모든 앱들은 결국 JavaScript로 작성될 것이다."

JavaScript는 지난 몇년간 가장 인기있는 프로그래밍 언어 중 하나로 크게 확장되었다. 실제로 2016년 SO 개발자 조사에 따르면, StackOverflow에서 가장 인기있고 가장 기술력있는 언어로 1등을 달성하고, 다른 결과들도 좋은 기록을 세웠다.

Node.js는 바이너리 데이터 조작, 파일 시스템 I/O 오퍼레이션, 데이터베이스 접근, 컴퓨터 네트워킹 등과 같은 중요한 서버사이드 기능을 담은 기초(foundation)로서 JavaScript 환경의 서버사이드이다. 이것은 Django(Python), Laravel(PHP), RoR(Ruby) 등과 같은 현존하는 시도되고 테스트된 프레임워크 사이에서 특출난 특징을 가진다. Node.js는 PayPal, Tinder, Medium, LinkedIn, Netflix와 같은 기술 리더 회사들이 사용한다는 것이다. 몇몇은 Node.js가 1.0버전이 되기도 전에 이미 사용하고 있었다.

나는 최근에 StackOverflow에서 Node.js 내부 코드베이스의 아키텍처에 관한 질문에 답변을 달았었다. 그것이 이 글을 쓰도록 영감을 불어넣어 주었다.


사실 공식적인 문서에서는 Node.js가 무엇인지 설명하기에 크게 도움이 되지 않는다.


"JavaScript 런타임은 크롬의 V8 JavaScript 엔진으로 만들어졌다. Node.js는 event-driven, non-blocking I/O 모델이고..."

이 말을 이해하고 실제 숨어있는 힘을 이해하기 위해 Node.js 요소들을 쪼개어보고, 몇몇 중요 용어들을 상세히  설명 할 것이다. 다음 어떻게 다른 조각들이 서로 소통하여 Node.js를 강력하게 만들 수 있는지 설명할 것이다.

Node.js 아키텍처 (High-Level to Low-Level)

요소들/종속물(COMPONENTS/DEPENDENCIES)
V8 : 구글에의해 오픈소스화된 높은 퍼포먼스를 자랑하는 JavaScript 엔진이며 C++로 구현되어있다. 크롬에서 사용하는 엔진과 동일한 엔진이다. V8은 당신이 JavaScript로 작성한 코드를 받아서, 기계코드로 컴파일한 뒤(이렇기에 빠르다), 실행시킨다. 어떻게 V8이 그냥 빠를 수 있을까? StackOverflow의 답변을 확인해보아라.

libuv : 비동기적 기능을 제공하는 C 라이브러리이다. 이것은 다른 중요한 기능들 사이에서 이벤트 루프, 스레드 풀, 파일 시스템 I/O, DNS 기능, 네트워크 I/O를 관리한다.

다른 C/C++ 요소들/종속물 : c-ares, crypto(OpenSSL), http-parser, zlib. 이 요소들은 서버에서 네트워킹, 압축, 암호화와같은 중요한 기능을 수행하기 위해 low-level로 소통하는 방식을 제공한다. 

앱/모듈 : 이것은 JavaScript 코드가 존재하는 모든 곳이다. 당신의 앱 코드, Node.js의 코어 모듈, npm으로 설치한 모든 모듈, 당신이 작성한 모든 모듈까지 당신은 대부분의 시간을 여기서 보낼 것이다.

바인딩 : 당신은 아마 이 시간을 통해 Node.js가 JavaScript와 C/C++로 작성되었다는 것을 알게 되었을 것이다. 여기엔 수많은 C/C++ 라이브러리가 있는데 그 이유는 간단하다: 빠르기 때문이다. 그러나 어떻게 당신이 JavaScript로 짠 코드가 C/C++로 짠 코드와 자연스럽게 소통할까? 그것은 세개의 서로 다른 언어이지 않는가? 그렇다. 다른 언어로 짜여진 코드는 보통 서로 서통할 수 없다. 바인딩 없이는 안된다. 이름에서 이야기하듯 바인딩은 한 언어를 다른 언어에 '묶어(bind)' 코드를 접합시킨다. 그러면 서로 의사소통을 할 수 있게 된다. 이 경우(Node.js) 바인딩은 단순히 C/C++로 작성된(c-ares, zlib, OpenSSL, http-parser 등) Node.js 내부 코어 라이브러리들을 JavaScript에 연결한다. 바인딩을 작성하는데 첫번째 이유는 재사용이다: 만약 필요한 기능들이 이미 구현되있다면, 그것을 가져다 사용하면 된다. 단지 언어가 다르다는 이유로 그것을 다시 짤 필요는 없다. 왜 그냥 연결시켜 사용하지 않겠는가? 두번째 이유는 퍼포먼스이다: C/C++ 같은 시스템 프로그래밍 언어들은 일반적으로 고수준언어(Python, JavaScript, Ruby등) 보다 빠르다. 그러므로 CPU-집약 오퍼레이션 코드는 C/C++로 작성하는 것이 현명할 것이다.

C/C++ Addon : 바인딩은 단지 zlib, OpenSSL, c-ares, http-parser와 같은 Node.js 내부 라이브러리를 접합하는 기능만 한다. 만일 당신이 써드파티의 C/C++ 라이브러리(외부 C/C++ 라이브러리)를 넣고 싶으면, 스스로 라이브러리를 접합시켜야한다. 당신의 코드를 접합해주는 코드가 addon이라 불리는 것이다. 바인딩과 addon은 당신의 JavaScript 코드와 Node.js의 C/C++ 코드를 연결하는 다리라고 생각하면 편할 것이다.

용어들(TERMINOLOGIES)
I/O : Input/Output의 약자이다. 이것은 기본적으로 시스템 I/O의 하위 시스템에의해 주로 다뤄지는 모든 컴퓨터 오퍼레이션을 나타낸다. I/O 바운드( https://en.wikipedia.org/wiki/I/O_bound)오퍼레이션은 디스크/드라이브와의 상호작용을 포함한다. 예를들어 데이터베이스 접근과 파일 시스템 오퍼레이션을 포함한다. 연관된 다른 개념에는 CPU 바운드, 메모리 바운드 등이 있다. 오퍼레이션이 I/O 바운드에 속하는지 CPU 바운드에 속하는지 아니면 다른데 속하는지 구별하는 좋은 방법은, 특정 오퍼레이션이 더 많은 퍼포먼스를 내고 리소스를 많이 잡아먹는지 체크하면 된다. 예를들어 한 오퍼레이션이 눈에 띄게 빨라지고 CPU 파워가 증가했다면 그것은 CPU 바운드 이다. 

Non-blocking/비동기적 : 보통 리퀘스트가 들어오면 앱은 그 리퀘스트를 다루고, 그 리퀘스트의 작업이 끝날때까지 다른 모든 오퍼레이션을 멈춘다. 다음은 실제 일어날 수 있는 문제이다: 만일 한꺼번에 수많은 리퀘스트가 들어오면, 각 리퀘스트는 이전 리퀘스트의 작업이 끝날때까지 기다려야한다. 다른 말로는, 이전 오퍼레이션이 다음 것을 블럭(block)시킬 것이다. 최악의 상황은,  이전 리퀘스트가 긴 응답시간(eg. 먼저 1000개의 계산 후, DB로부터 3GB 데이터를 읽는 경우) 이라면, 다른 리퀘스트들은 긴 시간동안 멈춤/블럭이 되 있을 것이다. 이 문제를 해결하기 위한 방법에는 각 리퀘스트에 대해 멀티 프로세싱 혹은/그리고 멀티 스레딩이 있을 수 있다. Node.js는 이것을 좀 다르게 다룬다. 모든 새 요청에 대해 새 스레드를 만드는 것 대신, 요청들은 한 메인스레드에서 다뤄지며, 그것이하는 일의 대부분이다.

리퀘스트들을 다루기 : 리퀘스트에 포함된 모든 오퍼레이션(eg. 파일 시스템 접근, 데이터베이스 읽기/쓰기)은 백그라운드에서 (위에서 언급한)libuv에의해 관리되는 일꾼 스레드로 보내진다. 즉, 리퀘스트 안에 있는 I/O 오퍼레이션들은 메이스레드가 아닌 곳에서 비동기적으로 다뤄진다. 이 방법으로 무거운 것을 다른곳으로 보냄으로써, 메인스레드가 블럭되지 않게 해준다. 당신의 앱 코드는 한 순간에 한가지 일만 하고 있고, 그것은 메인스레드 안에 있을 것이다.  libuv의 스레드풀에 있는 모든 일꾼 스레드는 당신이 접근할 수 없게 만들어 놓았다. 여러분은 그들에게 직접 접근할 수 없으므로 그것에대해 걱정할 필요도 없다. Node.js가 당신을 위해 관리해주기 때문이다. 이러한 아키텍처는 I/O오퍼레이션을 특히 효율적이게 만들어준다. 그러나 이것에 단점이 없는 것은 아니다. 오퍼레이션에는 I/O 바운드 하나만 있는게 아니라 CPU바운드, 메모리 바운드 등이 더 있다. 조금만 더 이야기해보자면 Node.js는 I/O 작업을 위한 비동기 기능만을 제공한다. CPU 집약 오퍼레이션을 수행하는 방법이 있지만, 이 글의 논지가 아니니 넘어가도록 하자.

이벤트기반(Event-Driven) : 일반적으로 현대의 대부분 시스템은 메인 앱이 꺼져야 들어오는 리퀘스트에의해 프로세스가 초기화된다. 그러나 그들이 다르게 흘러간다면? 전형적인 구현은 리퀘스트를 다음 순서처럼 다룬다: 리퀘스트를 위한 스레드를 하나 만들고, 다른 오퍼레이션이 끝난뒤 그 오퍼레이션을 수행하며, 오퍼레이션이 느려지면 그 스레드에 있는 모든 오퍼레이션이 멈추거나 느려진다. 모든 오퍼레이션이 완료되면 리스폰을 돌려준다. 그러나 Node.js에서는 메인 앱에의한 것이든 리퀘스트에의한 것이든 모든 오퍼레이션이 트리거(trigger)를 기다리는 Node.js 이벤트로 등록된다.

런타임(시스템) : Node.js 런타임은 고수준, 저수준 둘 다 Node.js 앱 실행을 같이 도와주는 전체 코드베이스(위에서 언급한 요소들)이다.

모든것을 섞어보자. (PUTTING EVERYTHING TOGETHER)
이제 Node.js 요소의 고수준 관점에서 보자. 우리는 그것의 아키텍처를 더 잘 알기위해 그것의 워크플로우(workflow)를 알아보고, 어떻게 다른 요소들이 서로 소통하는지도 알아볼 것이다.

Node.js 앱이 실행되면, V8엔진이 당신의 앱 코드를 실행시킨다. 당신 앱의 객체들은 옵저버(이벤트로 등록할 수 있는 기능)의 목록에 넣어둔다. 이 옵저버는 각 이벤트들이 발생할 때 알림을 받는다.

한 이벤트가 발생되면, 그것의 콜백 함수가 이벤트 큐(event-queue)에 들어간다. 큐에 이벤트가 한개라도 남아있으면 이벤트 루프(event loop)는 큐에서 이벤트를 빼어 콜스택(call stack)에 집어넣는다. 이전 이벤트가 처리가 되면 이벤트 루프는 다음 이벤트를 콜스택에 넣는다는 점만 유의하면 된다.

콜스텍에서 우연히 I/O 오퍼레이션을 만나면, 처리하기위해 libuv에게 넘어간다. 기본적으로, libuv는 4개의 일꾼 스레드를 유지하고 있다. 물론 더 많은 스레드를 만들 수도 있다. 만약 요청이 파일 시스템 I/O나 DNS 관련이면, 처리를 위해 스레드 풀에 할당될 것이다; 다른경우, 네트워킹과 같은 다른 리퀘스트들에서는 특정 플랫폼의 매커니즘이 그 리퀘스트들을 알아서 배치할 것이다.

스레드 풀을 사용하게 만드는 I/O 오퍼레이션들(파일 I/O, DNS 등)은 데이터베이스 트렌젝션이나 파일 시스템 접근과 같은 오퍼레이션을 수행하기 위해 Node.js의 저수준 라이브러리로 일꾼 스레드가 작용할 것이다. 처리가 끝나고 libuv는 이벤트가 다시 메인스레드에서 처리될 수 있게 이벤트 큐에 다시 집어넣는다. 이 동안에는 libuv가 비동기의 I/O 오퍼레이션을 다루고 있고, 메인스레드는 마냥 앉아서 처리 결과를 기다리는 것이 아니라 자신의 할 일을 하고 있는다. libuv에의해 돌려받는 이벤트는 이벤트 루프에의해 콜스택으로 돌아왔을때, 다시 메인스레드에서 처리될 수 있다. 이것이 Node.js에서 이벤트 생명주기이다.

mbg는 기가막히게 Node.js와 레스토랑을 비유해 놓았다. 나는 Node.js 사이클을 더 쉽게 설명하기 위해 그의 이야기를 빌려 설명하겠다.

Node.js 앱을 스타벅스 카페라 생각해보자. 높은 효율과 숙련이 잘 된 웨이터(한 명이고 메인스레드라 할 수 있다)가 주문을 받는다. 만약 같은 시간대에 많은 고객이 들이닥치면 웨이터의 서비스를 받기위해 줄을 서게 된다(이벤트 큐에 넣는다). 한 고객이 웨이터로부터 서비스를 받으면 웨이터는 메니저(libuv)에게 주문을 넘겨준다. 각 주문을 어떤 바리스타(일꾼 스레드 혹은 플랫폼-특정 매커니즘)에게 주문을 할당할지 정한다. 바리스타는 다른 다른 재료들과 기계를 써서(저수준 C/C++ 요소들) 고객이 요청한 것들의 음료를 만든다. 보통 4명의 바리스타가 특정 라떼(파일 I/O, DNS 등)를 돌아가면서 만든다. 그러나 주문이 피크치에 달할 때는 더 많은 바리스타를 일시적으로 고용할 수 있다(개업한 날의 이야기이지, 점심시간대의 이야기는 아니다). 한 번 웨이터가 메니저에게 주문을 넘기면, 웨이터는 다른 고객을 서브하기 위해 커피 만드는 것을 기다리지 않는다. 대신에 다음 고객을 부른다(이벤트 루프에의해 다음 이벤트를 큐에서 꺼네어 콜스택에 넣는다). 이벤트가 콜스택 안에 있는 것은, 고객이 카운터에서 서브를 받으며 있는 것과 비슷하다. 커피가 다 만들어지면, 커피는 고객 줄의 끝으로 보내진다. 커피가 카운터로 나오면 웨이터는 고객의 이름을 부를 것이고, 고객은 주문한 커피를 받게된다. (비유가 다소 현실세계에서는 이상할 수 있지만, 프로그램 관점에서 프로세스를 고려할 때는 딱 들어 맞을 것이다)


고수준 관점의 전반적인 Node.js 내부 코드 베이스와 그것의 일반적인 이벤트 생명주기에대해 설명이 끝났다. 그러나 이 총람은 굉장히 일반적인 상황의 이야기이고 많은 이슈와 세부적인 요소에는 들어맞지 않을 수 있다는 점을 유의하길 바란다. 예를들어 CPU 바운드 핸들링이나 Node.js 디자인 패턴 등등이 있다. 더 많은 다른 글들의 토픽들이 그 부분을 채워줄 것이다. 



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

MVC, MVP, MVVM, VIPER에대해 확실하게 잡기

원문https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52#.wtcp3gqzw

UPD: NSLondon에대해 내가 발표한 슬라이드 자료가 이 링크 있다.

iOS에서 MVC 사용한다는게 다소 이상하게 느껴질 있다. 당신은 MV모데VM으로 바꾸려고 생각해본 적이 있는가? VIPER 적용시켜볼 생각을 적은 있으나, 그게 의미있는 것인지 확신이 들지 않는가?

글을 읽어 내려가면 것들에 대한 답을 찾을 있을 것이다. 또한 자유롭게 댓글로 의견을 제기할 있다.

당신은 iOS 환경에서 아키텍처 패턴에 대한 지식을 정리하고 싶을 것이다. 우리는 유명한 것들을 골라 한번 보고, 이론과 비교한 , 작은 예제들과 함께 연습해 것이다. 아래 링크는 당신이 특별히 관심있는 것을 연습할 있다.

디자인 패턴을 마스터하는것은 중독될 있으므로 조심해야한다: 전보다 많은 질문들이 생겨날 것이기 때문에.

- 누가 네트워크 리퀘스트를 소유하여야하나: 모델이냐 컨트롤러냐?
- 새 뷰의
어떻게 모델을 넘겨주나?
- 누가 새로 생긴 VIPER 모듈을 생성해야하나: Router Presenter?

아키텍처를 고르는데 신중해야하는가?

당신이 만약 개발을 하다가 디버깅을 해야하는데 엄청난 양의 클래스와 엄청난 양의 다른 것을 비교해야 하며, 이게 아키텍처가 없는 상황이라면, 당신 클래스의 어떠한 버그를 찾지도 고치지도 못하는 상황을 맞이하게 것이다. 우리는 클래스의 모든 속성을 머릿속에 담아두고 있을 없다. 만약 그짓을 하다보면 중요한 세부적인 요소를 놓힐 수가 있다. 만약 개발하면서 이런 경험을 이미 해보았다면 아래와 같은 것을 겪어봤을 것이다.

  • 클래스가 UIViewController 자식클래스이다.
  • 당신의 데이터들이 UIViewController에서 바로 저장된다.
  • UIView들이 거의 아무 일도 하지않는다.
  • Model 데이터 구조이다.
  • 유닛 테스트로 아무것도 하지 않는다.

그래도 애플의 가이드라인이나 애플의 MVC(링크) 따랐다해도 이러한 상황은 생길 있으니 너무 낙담하지는 마라. 애플의 MVC 뭔가 잘못되었고, 우리는 그걸 바로잡을 것이다.

좋은 아키텍처의 특징 정의해보자:

  • 엄격한 룰에 따라 개체들간의 책임 분리(Distribution) 균형있게 해야한다.
  • 첫번째 말한 특징으로부터 나올 있는 테스트들이 가능(Testability)해야한다. (그리고 걱정마라: 적절한 아키텍처를 고른다면 어렵지 않을것이다.)
  • 사용하기 편해야(Ease of use)하고 유지보수하기 쉬워야한다.

분리해야하나?
분배는 우리가 이게 어떻게 동작하는지 알아낼려고 노력하는 동안 우리의 뇌에서 균등하게 생각하도록 해준다. 만약 당신이 천재라 생각되면 그냥 하던대로 해라. 그러나 능력은 선형적으로 커지니 않을 뿐더러 광장히 빨리 한계에 도달해버린다. 그러므로 가장 빨리 복잡한 것을 극복하는 방법은 단일 책임 원칙으로 수많은 개체들의 책임을 쪼개는 것이다.

테스트 가능해야하나?
이미 유닛테스트에대한 중요성을 알고 있는 사람에게 던지는 질문이 아니라, 기능을 추가한 일때나, 클래스의 몇몇 복잡성을 리팩토링을 하기 위해서 테스트에 실패하는 사람들이 하는 의문이기도하다. 이것은 테스트가 런타임 내에서의 이슈를 찾는데 도와주며, 반대로 실유저에게 이슈가 발생한다면 그걸 고친 앱을 다시 실유저가 다시 사용하기까지 일주일씩이나 걸린다.

사용하기 쉬워야하나?
가장 좋은 코드가 뭔지는 한번 언급해 가치가 있다: 하나도 작성하지 않은 코드이다. 따라서 적은 양의 코드는 버그가 적다. 게으른 개발자 말을 빌려 적은 코드를 작성 하기를 갈망하며 이것은 코드를 설명해야하면 안된다. 또한 당신이 눈을 감고 허우적대며 유지보수하는 솔루션을 원치도 않을 것이다.

필수 MV(X)

요즘은 아키텍처 설계를 할때 수많은 선택지가 있다:

위에서 세개(MVC, MVP, MVVM) 아래 3 카테고리중 하나는 들어가있다:

  • Models데이터나 데이터 접근 레이어(Person 클래스나 PersonDataProvider 클래스와 같이 데이터를 다루고있는) 소유를 책임지는 부분
  • Views레이어에 표현되있는 것을 책임지는 부분(GUI), iOS 환경에서는 'UI' 접두로 붙는다(역자주: UILabel, UIView 등등..).
  • Controller/Presenter/ViewModelModel View 붙여준다. 보통 유저가 View에서 어떤 액션을 취할때 Model 변경하거나 Model 변경되었을 , View 갱신하는 책임을 가지는 부분

개체들을 나눌때 이점:

  • 이전보다 이해할 있다(이미 알고 있다 하더라도).
  • 재사용 가능하다(대부분 View Model 적용 가능하다).
  • 독립적으로 테스트 가능하다.

어서 MC(X) 패턴을 시작하고 나중에는 VIPER까지 해보도록 하자.

MVC

이전에는 어떻게 사용해왔을까?

애플의 MVC 살펴보기 전에 전통적인 MVC 어떻게 사용되었는지 보자.

Traditional MVCTraditional MVC

경우는 View 범위가 정확하지 않다. Model 바뀌고나서 Controller에의해 한번 랜더링(rendering) 된다. 웹페이지에서 다른 페이지로  수 있는 링크를 누른 , 다시 로딩되는 것을 생각해봐라. iOS 앱에서 전통적인 MVC 구현하는것은 가능할지라도 구조적인 문제때문에 효과적으로 처리할 없으며 당신 앱이 그러기도 원치 않는다.— 모든 개체가 둘씩 묶여있고, 개체는 다른 두개에 대해 알고있다. 이것은 각기 그들이 재사용성을 심각하게 줄여버린다. 이러한 이유로 우리는 흔히 쓰는 MVC 작성하는 또한 스킵 하겠다.

전통적인 MVC 최신 iOS 개발에 적합해 보이지 않는다


Apple’s MVC

기대한것..

Cocoa MVCCocoa MVC

원래 Controller Model View 연결시켜주는 역할을 하므로 서로에 대해 알필요가 없다. 그중에 가장 재사용 불가능한 것이 Controller이며, 우리도 그걸 알고있다. 따라서 우리는 모든 특이한 로직을 Model 아닌 Controller 넣어야한다.

이론적으로는 굉장이 전략적으로 보이지만 뭔가 문제가 있다. 당신은 MVC 컨트롤러 덩어리(Massive View Controller) 불리는걸 들은적이 있을지도 모른다. 나아가 View Controller offloading iOS 개발자들에게 중요한 토픽이다. 애플은 전통적인 MVC 조금 개선하여 사용하여서 이런 일이 일어나버린건가?

Apple’s MVC

실체는..

Realistic Cocoa MVCRealistic Cocoa MVC

Cocoa MVC View Controller 덩어리 작성하도록 만들어버린다. 이유는 View들의 라이프 사이클 안에서 뒤엉키는데 그것들을 분리해내기가 어렵기 때문이라고 말한다. 너가 Model*비지니스 로직이나 데이터 변환같은 것을 없애는 능력을 가졌을 지라도 대부분의 View에서 반응하면 액션을 Controller로 보내게 될것이다. 뷰 컨트롤러는 결국 모든 것의 델리게이트(delegate)나 데이터소스(data source)가 될테고, 종종 네트워크 요청과같은 처리도 하고 있을지 모른다. 

이런 종류의 코드를 얼마나 많이 보았는가:

Model 함께 직접적으로 구현된 View cell MVC 가이드라인을 위반한다. 그러나 항상 그렇게 사용하며 사람들은 이게 문제가 아니라고 느낄때가 많다. 좀더 MVC 따르고자 한다면 cell Controller에서 구성하고 View 안에 Model 거치지 않아햔다. 그러나 그렇게해버리면 Controller 커져버리게 될것이다.

Cocoa MVC View Controller 덩어리의 이유이기도하다.

문제는 유닛 테스트(여러분 프로젝트에 있기를 바란다)에까지 나타날 거라는걸 확신할수 없다. 당신의 View Controller View 붙어있고, 이렇게하면 그들의 View 라이프 사이클이나 테스트를 위한 View 만들기가 어려워지기 때문에 테스트가 힘들어진다. 반면 View Controller 코드를 작성하고 있으면 당신 비지니스 로직은 가능한 View 레이아웃 코드로부터 분리될것이다.

간단한 예제를 보자:

MVC 분리하면 현재 View Controller안에서 동작되게 있다.

테스트하기 좋아보이지는 않다. 우리는 greeting 생성을 GreetingModel 클래스에 옮겨 넣을 있다. 그러나 GreetingViewController안에서 UIView 연관되어있는 메소드(viewDidLoad, didTapButton) 호출하지 않은체 상연 로직(예제에는 로직이 많이 없지만) 테스트를 수가 없다.

사실, 로딩테스트는 디바이스를 바꿔가며(iPhone4S, iPad 등등으로) 확인해보는 것에대한 이점이 없다. 그래서 Unit Test target configuration에서 “Host Application” 지우고 시뮬레이터 없이 테스트 해보는것을 추천한다.

View Controller 사이의 상호작용은 Unit Test로써 테스트하기에 좋지 않다.

위에서 말한건, Cocoa MVC 사용하는것은 별로 좋지 않은 선택인것 같아 보인다는 것이다. 그러나 글의 서두에 언급했단 특징들의 용어를 정의했었다.

  • Distribution사실 뷰와 모델은 분리되 있지만, View Controller 붙어있다.
  • Testability거지같은(?)분리 때문에 아마 Model 테스트 가능할 것이다.
  • Ease of use다른 패턴에 비해 코드가 적게 든다. 추가로 많은 사람들이 친숙하게 사용하기도하며 경험해보지 못했던 개발자도 쉽게 접근할 있다.

Cocoa MVC 아키텍처 쪽에 시간을 투자할 시간이 별로 없을때 선택하는 패턴이며, 작은 프로젝트에는 지나친 유지보수 비용이 들어간다는 것을 느낄 있을 것이다.

Cocoa MVC 개발 속도면에서는 최고의 아키텍처 패턴이다.


MVP

전달될거라 약속한 Cocoa MVC(Cocoa MVC’s promises delivered)

Passive View variant of MVPPassive View variant of MVP

사진이 애플의 MVC 굉장히 비슷하지 않는가? 이것의 이름은 MVP(Passive View Variant)이다. 그럼 애플의 MVC MVP 같다는 걸까? 그렇지 않다. MVC에서는 View Controller 서로 붙어있지만 MVP에서 중간다리 역할을 하는 Presenter View Controller의 라이프 사이클에 아무런 영향을 끼치지도 않으며, View 쉽게 테스트가능한 복사본(moked) 만들 있다. 그러므로 Presenter에는 레이아웃 관련 코드가 없고 오직 View 데이터와 상태를 갱신하는 역할만 가진다.

만약 UIViewController View라고 말했으면 어떨까.

사실 MVP 입장에서는, UIViewController 자식클래스에 Presenter 아닌 View들이 있다. 이러한 구분은 좋은 테스트 용이함을 제공하지만, 수작업의 데이터나 이벤트 **바인딩 따로 만들어야하기때문에 개발 속도에대한 비용도 따라 온다. 아래 예제에서 확인할 있다:

Important note regarding assembly(중요 요약 모음)

MVP 세개의 다른 레이어를 가짐으로써 이런 문제 집합이 처음으로 나타난 패턴이다. 그러므로 뷰가 Model에대해 알기를 원치 않기 때문에, 현재 View Controller(View 것이다) 모아서 동작시키는건 옳지 않으므로 다른곳에서 동작시켜야한다. 예를들어, 우리는 앱에서 범용적인 모아서 수행하거나 View-to-View 보여주기위한 Router 돌릴 있다. 이슈는 MVP 뿐만아니라 아래 모든 패턴들에게도 나타나는 문제이기도하다.

이제 MVP 특징 보자.

  • DistributionPresenter Model 책임을 거의 분리했고 View 빈껍데기가 셈이다( 예제에서는 Model 빈껍데기 같았지만..)
  • Testability최고로 좋다. View 재사용가능 덕분에 대부분의 비지니스 로직을 테스트 있다.
  • Easy of use위에서 비현실적인 예제에서는 MVC에비해 코드의 양이 2배정도 많이 들지만 MVP 아이디어는 굉장히 명료하다.

iOS에서 MVP 테스트하기엔 좋지만 코드가 길어진다.


MVP

With Bindings and Hooters

MVP 다른 버전(MVP Supervising Controller) 있다. 이러한 다양한 MVP들은 Presenter(Supervising Controller) View로부터 액션을 처리하고 View 적합하게 변경하는 동안 View Model 직접 바인딩을 포함한다(?).

Supervising Presenter variant of the MVPSupervising Presenter variant of the MVP

그러나 우리가 이미 이전에 배웠듯, 막연하게 책임을 나누는건 좋지않은데다, View Model 합쳐버린다. 이것은 Cocoa 데스크탑 개발에서 어떻게 동작하는지와 비슷하다.

전통적인 MVC와같이, 결함이 있는 아키텍쳐의 예제를 찾기 힘들었다.

MVVM

마지막이자 MV(X) 종류의 최고 종류

MVVM은 최근에 나온 MV(X) 종류이다. 그러므로 이전의 MV(X) 문제들을 해결하여 나오기를 기대해보자.

이론적으로는 Model-View-ViewModel이 굉장히 좋아보인다. ViewModel은 이미 우리에게 친숙할테고, View Model 이라불리는 중계자 또한 마찬가지일 것이다.

MVVMMVVM

MVP 비슷하다:

- MVVM View Controller View라고 일컫는다.
- View Model 서로 연결 되어있지 않다.

추가로 MVP Supervising버전에서 처럼 binding 있다; 그러나 여기서는 View Model 관계가 아닌 View View Model 사이의 관계이다.

그래서 실제 iOS에서 View Model 뭘 의미할까? 그것은 기본적으로 UIKit인데 그로부터 View 독립된 표현이거나 상태이다. View ModelModel에서 변경을 호출하고 Model 자체를 갱신한다. 따라서 View나 View Model 사이에서 바인딩을 하며, 적절히 처음것이 갱신된다.

Bindings(바인딩)

MVP 파트에서 간당하게 언급한적이 있다. 그러나 여기서 좀 더 이야기 해보자. 바인딩은 OS X 개발을 위한 박스(역자주: 프레임워크나 툴을 말하는듯 합니다)에서 나왔으나 iOS 툴박스에서는 보지못한다. 물론 KVO나 notification을 가지고 있긴 하지만 그것이 바인딩만큼 편리하지는 않다.

그러므로 

- 바인딩 기반 라이브러리인 KVO에는 RZDataBinding 혹은 SwiftBond 이런게 있다.
- The full scale functional reactive programming beasts like ReactiveCocoa, RxSwift or PromiseKit. (번역하지 못했습니다ㅠ)

사실 요즘엔 MVVM을 들으면 바로 ReactiveCocoa를 말하기도하며, 반대도 그렇다(역자주: 뭐라고??????). 비록 간단한 바인딩으로 MVVM을 만드는게 가능하기는 하나 ReactiveCocoa (혹은 siblings)으로는 최고의 MVVM을 만들수 있게 해준다.

Reactive 프레임워크에는 쓰디쓴 진실이 하나 있다: 큰 책임엔 큰 에너지가 필요하다. Reactive를 사용하게되면 굉장히 혼잡해지기 쉬워진다. 다른말로 설명하자면, 문제가 하나 생기면 앱을 디버깅하는데 시간이 굉장히 많이 걸리며, 아래와 같은 콜 스택을 보게 될것이다.

Reactive DebuggingReactive Debugging

우리의 예제에서는 FRF 프레임워크나 KVO까지도 배보다 배꼽이 식이다. 대신에 showGreeting 메소드를 이용하여 갱신하기 위한 View Model 명백하게 물어 것이고 greetingDidChange 콜백 함수를 위해 작은 프로퍼티를 사용할것이다.

이제 돌아와서 특징들을 나열해보겠다:

  • Distribution우리의 작은 예제에서는 명료하게 나타나지 않았지만, 사실 MVVM View MVP View보다 책임이 많다. 왜냐면 두번째 것이 Presenter 포워드(forward)하고 자신를 갱신 하지는 않은 그 때, 바인딩을 세팅함으로써 View Model에서 처음 것의 상태를 갱신한다.
  • TestabilityView Model View에대해 전혀 모르며, 이것이 테스트하기 쉽게 해준다. View 또한 테스트 가능하지만 UIKit 의존이면 그러고 싶지 않게 원하게 될것이다.
  • Easy of use우리 예제에서는 MVP 비슷한 양의 코드나 나왔으나 View에서 Presenter으로 모든 이벤트를 포워드하고 View 갱신하는 실제 앱에선 바인딩을 사용했다면 MVVM 코드 양이 적을 이다.


MVVM 앞에서 말한 장점들을 합쳐놓은것 같아서 굉장히 매력적이다. 그리고 View입장에서 바인딩을 하기 때문에 View 갱신하는데 추가적인 코드를 필요로 하지도 않는다. 그럼에도불구하고 테스트에도 굉장히 좋은 수준이다. (역자주: 완전 극찬이군요)


VIPER

iOS 설계에 레고 조립 경험을 적용하다

VIPER 마지막 지원자다. 이것이 특별히 흥미로운 이유는 MV(X) 카테고리로 부터 나온 녀석이 아니기 때문이다.

이제부터 당신은 책임의 단위가 매우 좋다고 인정하게 될것이다. VIPER 분리된 책임이라는 아이디어에서 생겨난 다른 iteration 만드며, 이번 시간에는 다섯 레이어를 것이다.

VIPERVIPER

  • Interactor데이터 개체나 네트워킹과 연관되어있는 비지니스 로직을 가지고, 서버로부터 그들을 받아오거나 개체 인스턴스를 만드는것을 좋아한다. 이러한 목적으로을 위해서 당신은 VIPER 모듈의 일부로써 몇몇 Services Managers 사용해야 것이나, 다소 외부 의존도가 있을것이다.
  • Presenter—Interactor에서 발생되고 비지니스 로직과 관련있는 (그러나 UIKit과는 관련없는) UI 가진다.
  • Entities일반적인 데이터 객체이다. (데이터 접근 레이어(data access layer) Interactor 책임이기 때문에 Entities 아니다.)
  • Router—VIPER 모듈 사이의 연결고리(seques) 책임을 가진다.

기본적으로 VIPER 모듈은 스크린(screen)이나 당신 어플리케이션의 모든 ***사용자 스토리(user story) 있다인증을 생각해보면 스크린이나 여러개가 하나에 연관되어 있을 있다. 얼마나 작은 “LEGO” 블럭어여야 할까?—전적으로 당신에게 달려있다.

MV(X) 종류와 비교하면, 우리는 책임의 분리가 다르다는걸 확인할 있다:

  • Model(data interation) 로직은 데이터 구조로써 Entities 함께 Interactor 이동된다.
  • 오직 Controller/Presenter/ViewModel Presenter 이동하는 UI 표시 책임을 갖지만, 데이터를 변경할 능력은 없다.
  • VIPER 명시적으로 Router에의해 결정된 네비게이션 책임을 해결한 패턴이다

iOS 어플리케이션 입장에서는 각기 방법으로 라우팅 하는게 도전이라고 수있다. MV(X) 패턴들은 이러한 이슈가 발생하지 않는다.

토픽이 MV(X) 패턴을 반영하지 못했으므로, 예제 또한 라우팅이나 모듈간의 interaction 반영하지 않았다

이제 다시 돌아와 특징들을 살펴보자:

  • Distribution틀림없이 VIPER 책임 분배의 최고봉이다.
  • Testability분리가 잘되있는만큼 테스트에도 좋다.
  • Easy of use마지막으로 여러분이 이미 추측한것처럼 두배 정도의 유지보수 비용이 들것이다. 매우 작은 책임을 위해 수많은 클래스 인터페이스를 작성해야하는 점이다.

그래서 레고는 뭐였나?

VIPER 사용하는 동안 레고로 엠파이어 스테이트 빌딩(위키:엠파이어 스테이트 빌딩은 1931년부터 1972년까지 세계 최고층 건물이었다.) 쌓는 기분이 들것이자, 이것이 유일한 문제이기도하다. 아마 당신 앱에 VIPER 적용시키기에 이를수도 있고 좀더 간편한것으로 고려해도 좋다. 몇몇 사람들은 이걸 아예 무시하고 대포에다가 화살을 쏘아대는 경우도 있다. 지금은 비록 엄청나게 높은 유지보수 비용이 들지만, 그들이 미래에는 그들의 앱에 VIPER 필요할지도 모른다는걸 알고있을거라 생각한다. 만일 당신도 생각이 같다면 Generamba(VIPER 골격을 제공해주는 ) 한번 사용해보길 바란다. 개인적으로는 이건 새총 대신에 자동 대포 조준 시스템을 사용하는 느낌이긴하다.


결론

우리는 몇몇 다른 아키텍처 패턴을 살펴보았고, 무엇이 당신을 괴롭히는지 찾아냈기를 바란다. 그러나 여기에 완벽한 해답은 없고 아키텍처를 선택하는게 당신의 특별한 상황에서 문제의 비중을 등가교환하게 된다는걸 알게되었음을 의심하지 않는다

그러므로 앱에 다른 아키텍처를 섞어 사용하는것은 자연스러운 일이다. 예를들어 MVC 시작했지만 어떤 화면에서만 MVC 관리하기 어려워지는 상황이 생기면 부분만 MVVM으로 바꿀 있다. 이런 아키텍처들은 서로 공존할 있기때문에, 다른 화면이 MVC 골격으로 동작하면 바꿀 필요가 없다



Make everything as simple as possible, but not simpler 
이론은 가능한 간단해야하지만지나치게 간단해서는 안된다
— Albert Einstein




*비지니스 로직 (business logic)

**바인딩 (binding)

***사용자 스토리 (user story)



iOS 아키텍처 관련 번역글



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,