<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>터프 프로그래머</title>
    <link>https://toughrogrammer.tistory.com/</link>
    <description>tough programmer
toughrogrammer[at]gmail[dot]com</description>
    <language>ko</language>
    <pubDate>Sun, 2 Aug 2026 05:30:50 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>터프 프로그래머</managingEditor>
    <image>
      <title>터프 프로그래머</title>
      <url>https://t1.daumcdn.net/cfile/tistory/2527183659686D902C</url>
      <link>https://toughrogrammer.tistory.com</link>
    </image>
    <item>
      <title>deeplink test</title>
      <link>https://toughrogrammer.tistory.com/242</link>
      <description>&lt;p&gt;&lt;a href=&quot;http://ads.airbridge.io/ablog/adpick/tracking/s?ad_creative=vbcncvncvbn&amp;amp;ad_group=fdghndfghdfghfgh&amp;amp;campaign=%7BADPICK+Campaign+ID%7D-zxccvzcvzxcv&amp;amp;click_id=%7BADPICK+Transaction+ID%7D&amp;amp;label=adpick&amp;amp;og_tag_id=11747&amp;amp;routing_short_id=b537&amp;amp;tracking_template_id=889a5b9559decb2255014ea2e62b3e3b&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;abc&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://ads.airbridge.io/gsshop/kakao/tracking/s?click_id=test_click_id&amp;amp;gaid_raw=abcd&amp;amp;ifa_raw=abcd&amp;amp;medium=medium1&amp;amp;og_tag_id=24100&amp;amp;routing_short_id=wjfz&amp;amp;sub1=s1&amp;amp;sub2=s2&amp;amp;sub_id=subid1&amp;amp;tracking_template_id=2e632b633a056892612da0e8801f6e05&amp;amp;ad_type=click&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;abc2&lt;/a&gt;&lt;/p&gt;</description>
      <author>터프 프로그래머</author>
      <guid isPermaLink="true">https://toughrogrammer.tistory.com/242</guid>
      <comments>https://toughrogrammer.tistory.com/242#entry242comment</comments>
      <pubDate>Fri, 19 Oct 2018 13:41:57 +0900</pubDate>
    </item>
    <item>
      <title>학교에서 배운 것 정리</title>
      <link>https://toughrogrammer.tistory.com/241</link>
      <description>&lt;p&gt;최근 학교에서 그 동안 공부했던 것을 다시 훑어볼 일이 있었다.&amp;nbsp;공부하는 김에 각 강의에서 어떤 것들을 배웠나 정리해두면 도움이 되지 않을까 싶어서 여러 대학교에서 공통적으로 배울만한 강의명 아래 우리 학교에서는 무엇을 배웠는지 큰 분류상으로&amp;nbsp;정리해봤다. 세부 내용을 다 정리하자면 너무 복잡해질 것 같아서... 대충 이 강의에서는 저런 내용을 배우는구나 하고 참고할만한 정도로만 썼다. 좀 더 자세히 쓴&amp;nbsp;부분도&amp;nbsp;있는데, 그 동안의 경험상 해당 부분을&amp;nbsp;왜 배우는지 잘 모르는 경우가 많은 것으로 보였던 것에 대해서 약간의 부연 설명을 추가한 것이다. 나름대로 분류를 해서 정리하긴 했는데, 충분히 잘 분류했는지&amp;nbsp;모르겠다. 소프트웨어 공학 같이 중요하지만 내가 수강하지 않았기 때문에 리스트에 없는 것도 있다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;수학에 대해&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;컴퓨터수학&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;이산 수학에 대해 배움&lt;/li&gt;&lt;li&gt;명제, 증명, 집합, 정수론 기초에 대해 배우고&lt;/li&gt;&lt;li&gt;알고리즘과 시간 복잡도 개념, 재귀 개념에 대해 배우고&lt;/li&gt;&lt;li&gt;순열과 조합에 대해 배우고&lt;/li&gt;&lt;li&gt;이진 관계 속성들에 대해 배우고&lt;/li&gt;&lt;li&gt;부분 순서에 대해 배우고&lt;/li&gt;&lt;li&gt;그래프에 대해 배우고&lt;/li&gt;&lt;li&gt;트리에 대해 배우고&lt;/li&gt;&lt;li&gt;탐색에 대해 배우고&lt;/li&gt;&lt;li&gt;부울 대수에 대해 배움&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;선형대수&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;기하학적인 관점에서 문제를 접근하는 방법에 대해 배움&lt;/li&gt;&lt;li&gt;일차연립방정식을 행렬로 계산하는 법을 배우고&lt;/li&gt;&lt;li&gt;부분 공간을 배우고&lt;/li&gt;&lt;li&gt;가우스 소거법을 배우고&lt;/li&gt;&lt;li&gt;LU 분해를 배우고&lt;/li&gt;&lt;li&gt;벡터 공간을 배우고&lt;/li&gt;&lt;li&gt;행렬식을 배우고&lt;/li&gt;&lt;li&gt;정규 직교화를 배우고&lt;/li&gt;&lt;li&gt;고유값과 고유벡터를 배움&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;오토마타&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;형식 언어와 계산 이론에 대해 배움&lt;/li&gt;&lt;li&gt;형식 언어에 대해 배우고&lt;/li&gt;&lt;li&gt;finite automaton인 DFA, NFA로 regular grammar를 처리할 수 있음을 배우고&lt;/li&gt;&lt;li&gt;pumping lemma를 통해 어떤 언어가 regular language에 속하는지 판단할 수 있음을 배우고&lt;/li&gt;&lt;li&gt;pushdown automaton로 context free grammar를 처리할 수 있음을 배우고&lt;/li&gt;&lt;li&gt;문법 간소화를 위한 촘스키 노말 폼에 대해 배우고&lt;/li&gt;&lt;li&gt;context free grammar를 기반으로 언어를 파싱하여 parse tree를 만드는 방법에 대해 배우고&lt;/li&gt;&lt;li&gt;turing machine의 원리와 계산 가능성에 대해 배우고&lt;/li&gt;&lt;li&gt;계산 복잡도에 대해 배움&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;프로그램 실행 과정에 대해&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;논리회로&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;0, 1로만 구분되는 전기 신호를 이용하여 복잡한 실행을 위해 논리 게이트들을 어떻게 구성하는지 배움&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;컴퓨터구조&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;논리 회로로 만들어진 CPU, 메모리와 같은 하드웨어에서 어셈블리 명령어 실행을 위해 알아야 할 것들을 배움&lt;/li&gt;&lt;li&gt;산술 연산, 논리 연산, 메모리 접근, 실행 제어 명령어를 CPU 구조에서 어떻게 실행하는지 배우고&lt;/li&gt;&lt;li&gt;그 명령어들을 더 빠르게 실행하기 위한 파이프라이닝 기법과 그 기법의 문제 해결 방법에 대해 배우고&lt;/li&gt;&lt;li&gt;메모리 구조와 속도 향상을 위한 캐시, 논리 메모리를 물리 메모리로 맵핑하는 방법에 대해 배우고&lt;/li&gt;&lt;li&gt;IO 장치에서 IO가 어떻게 일어나는지 배우고&lt;/li&gt;&lt;li&gt;인터럽트 처리 방법에 대해 배움&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;시스템 프로그래밍&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;간단한 명령어 집합을 가진 기계를 가정하고, 어셈블리 언어를 CPU에서 실행 가능한 목적 코드로 변환하는 어셈블러를 만들기 위해 알아야 할 것들을 배움&lt;/li&gt;&lt;li&gt;어셈블리 언어를 파싱하여 각 명령어별로 인코딩하는 법을 배우고&lt;/li&gt;&lt;li&gt;명령어별로 주소가 설정되고, 특정 명령어에 설정된 레이블을 이용하여 명령어 주소로 변환하는 법을 배우고&lt;/li&gt;&lt;li&gt;변수에 메모리 할당을 위해 목적 코드를 어떻게 구성해야하는지 배우고&lt;/li&gt;&lt;li&gt;만들어진 목적 코드를 읽어서 실행하는 시뮬레이터를 만듦으로써 프로그램이 CPU에서 어떻게 실행되는지 간접적으로 배움&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;프로그래밍 언어&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;어셈블리보다 더 고수준 언어, 예를 들면 C언어를 만들기 위해 고려해야 할 것들에 대해 프로그래밍 언어의 평가 기준인 readability, writability, reliability, cost 관점에서 살펴봄&lt;/li&gt;&lt;li&gt;더욱 자연어에 가까워진 언어를 프로그램에서 읽어들이기 위한 문법 구조에 대해 배우고&lt;/li&gt;&lt;li&gt;우리에게 익숙한 Expression들을 문법 구조와 LL, LR 파싱 방법에 따라 Syntax Tree를 만드는 법을 배우고&lt;/li&gt;&lt;li&gt;이름, 바인딩, 영역에 대해 배우고&lt;/li&gt;&lt;li&gt;여러가지 Type들을 지원하기 위해 고려해야 할 것들에 대해 배우고&lt;/li&gt;&lt;li&gt;기본적인 expression statements 지원을 위해 고려해야 할 것들에 대해 배우고&lt;/li&gt;&lt;li&gt;selection statements(if, else, switch 등), iterative statements(for, while 등) 지원을 위해 고려해야할 것들에 대해 배우고&lt;/li&gt;&lt;li&gt;서브 프로그램 기능 구현을 위해 알아야 할 것들을 배움&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;컴파일러&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;프로그래밍 언어를 통해 알게된 것들을 어셈블리로 변환하기 위한 컴파일러를 만드는 방법에 대해 배우며, 보통 교재로 많이 쓰는 책에서는 TINY라는 좀 더 간단한 언어에 대한 컴파일러를 만들어보는 식으로 배움&lt;/li&gt;&lt;li&gt;Lex를 이용한 Lexical Analysis 하는 법을 배우고&lt;/li&gt;&lt;li&gt;하향식 파싱에 대해 좀 더 자세히 배우고&lt;/li&gt;&lt;li&gt;상향식 파싱에 대해 좀 더 자세히 배우고&lt;/li&gt;&lt;li&gt;Yacc을 이용한 Syntax Analysis 후 Syntax Tree 만드는 법을 배우고&lt;/li&gt;&lt;li&gt;만들어진 Syntax Tree를 Traverse 하면서 Semantic Analysis 하는 법을 배우고&lt;/li&gt;&lt;li&gt;분석이 끝난 Syntax Tree를 미리 가정한 스택 머신의 명령어 집합의 어셈블리로 변환하는 법을 배우고&lt;/li&gt;&lt;li&gt;만들어진 어셈블리를 실행할 수 있는 시뮬레이터로 실행해 봄&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;프로그래밍에 대해&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;자료구조&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;프로그램에서 자료를 다루기 위해 필요한 여러가지 자료구조에 대해 배움&lt;/li&gt;&lt;li&gt;스택과 큐&lt;/li&gt;&lt;li&gt;연결 리스트, 환형 연결 리스트, 이중 연결 리스트&lt;/li&gt;&lt;li&gt;트리, 트리 순회 방법, 이진 트리, 이진 탐색 트리, AVL 트리, 힙, 우선 순위 큐&lt;/li&gt;&lt;li&gt;그래프, 무방향 그래프, 방향 그래프, 최소 신장 트리, 작업 네트워크&lt;/li&gt;&lt;li&gt;위상 정렬&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;알고리즘&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;문제 해결을 하기 위한 알고리즘을 프로그래밍 언어로 구현하는 방법에 대해 배움&lt;/li&gt;&lt;li&gt;여러가지 복잡도와 계산 방법을 배우고&lt;/li&gt;&lt;li&gt;분할 정복 방법을 배우고&lt;/li&gt;&lt;li&gt;다이나믹 프로그래밍 방법을 배우고&lt;/li&gt;&lt;li&gt;그리디 접근 방법을 배우고&lt;/li&gt;&lt;li&gt;백 트랙킹 방법을 배우고&lt;/li&gt;&lt;li&gt;몬테 카를로 방법을 배우고&lt;/li&gt;&lt;li&gt;분기 한정 방법을 배움&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;문제 해결&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;더 어려운 알고리즘들을 배움&lt;/li&gt;&lt;li&gt;간단한 코딩 워밍업&lt;/li&gt;&lt;li&gt;다이나밍 프로그래밍 - 0/1 Knapsack 문제, Longest Common Subsequence, Longest Increase Subsequence, Edit Distance&lt;/li&gt;&lt;li&gt;기하 문제 - Area Formula, Convex Hull, Triangulation&lt;/li&gt;&lt;li&gt;그래프 문제 - 위상 정렬, Strongly Connected Component, Maximum Flow 문제와 Ford-Fulkerson method, Bipartite Matching 문제와 Hungarian method, Kuhn-munkres algorithm&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;객체 지향 프로그래밍&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;Swing으로 코딩하면서 객체 지향적으로 프로그래밍하는 방법에 대해 배움&lt;/li&gt;&lt;li&gt;Swing을 이용한 코딩을 하면서 객체 지향 프로그래밍의 몇 가지 특징들을 배우고&lt;/li&gt;&lt;li&gt;팀 프로젝트로 프로그램 개발&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;사용자 인터페이스&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;안드로이드 개발을 하면서 객체 지향 프로그래밍을 좀 더 심화하여 배움&lt;/li&gt;&lt;li&gt;안드로이드 개발을 전반적으로 배우고&lt;/li&gt;&lt;li&gt;팀 프로젝트로 안드로이드 앱 개발&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;네트워킹에 대해&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;컴퓨터 통신과 네트워크&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;OSI 5계층의 각 계층, 특히 transport, network 계층에서 일어나는 일들을 자세히 배움&lt;/li&gt;&lt;li&gt;transport 계층의 TCP 프로토콜에서 어떻게 신뢰성 있는 통신을 하는지와 UDP 통신이 어떻게 되는지 배우고&lt;/li&gt;&lt;li&gt;network 계층에서 목적지까지 라우팅하는 방법을 배우고&lt;/li&gt;&lt;li&gt;network access 계층에서 이웃 노드에게 데이터를 전달하는 방법을 배우고&lt;/li&gt;&lt;li&gt;physical 계층에서 비트를 전송하는 방법을 배움&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;네트워크 프로그래밍&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;리눅스에서 소켓 API를 이용하여 네트워킹하는 방법을 자세히 배움&lt;/li&gt;&lt;li&gt;고수준, 저수준 소켓 API들을 배우고&lt;/li&gt;&lt;li&gt;운영체제에서 소켓을 어떻게 관리하고 연결하는지 배우고&lt;/li&gt;&lt;li&gt;서버/클라이언트 구조를 배우고&lt;/li&gt;&lt;li&gt;다중 소켓 처리를 위한 방법인 멀티프로세스, 멀티플렉싱, 멀티쓰레드 방법을 배우고&lt;/li&gt;&lt;li&gt;유명한 프로토콜인 HTTP, FTP을 배워서 구현해봄&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;인공 지능과 기계 학습에 대해&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;지능형 시스템&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;지능형 시스템이 무엇이고 어떻게 작동될 수 있는지 기본적인 부분에 대해 배움&lt;/li&gt;&lt;li&gt;지식 표현 방식에 대해 배우고&lt;/li&gt;&lt;li&gt;reasoning에 대해 배우고&lt;/li&gt;&lt;li&gt;널리 알려진 지능형 시스템들의 작동 방식에 대해 배움&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;인공지능&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;지적 행위를 할 수 있는, 인공 지능 프로그램을 만들기 위한 방법에 대해 배움&lt;/li&gt;&lt;li&gt;에이전트와 환경의 개념에 대해 배우고&lt;/li&gt;&lt;li&gt;문제 해결을 위한 탐색 방법에 대해 배우고&lt;/li&gt;&lt;li&gt;제약 만족 문제와 풀이 방법에&amp;nbsp;대해 배우고&lt;/li&gt;&lt;li&gt;명제 논리와 1차 논리를 이용하여 문제를 해결하는 논리적 에이전트에 대해 배움&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;데이터 마이닝&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;여러가지 감독 기계 학습 모델로 회귀, 분류를 하는 방법에 대해 배움&lt;/li&gt;&lt;li&gt;통계적 학습이 무엇인지 배우고&lt;/li&gt;&lt;li&gt;편향-분산 딜레마에 대해 배우고&lt;/li&gt;&lt;li&gt;선형 회귀 방법을 이용한 회귀에 대해 배우고&lt;/li&gt;&lt;li&gt;로지스틱 회귀, LDA, QDA 방법을 이용한 분류에 대해 배우고&lt;/li&gt;&lt;li&gt;resampling 방법에 대해 배우고&lt;/li&gt;&lt;li&gt;선형 모델 선정과 정규화에 대해 배우고&lt;/li&gt;&lt;li&gt;트리 기반 방법에 대해 배우고&lt;/li&gt;&lt;li&gt;Support Vector Machine에 대해 배움&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;운영체제에 대해&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;운영 체제가 하는 일들과 그 일을 하기 위해 어떤 자료구조와 알고리즘을 사용하는지 배움&lt;/li&gt;&lt;li&gt;프로세스 관리에 대해 배우고&lt;/li&gt;&lt;li&gt;메모리 관리에 대해 배우고&lt;/li&gt;&lt;li&gt;스토리지 관리에 대해 배우고&lt;/li&gt;&lt;li&gt;보호와 보안에 대해 배움&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;데이터베이스에 대해&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;사용에 대해&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;데이터베이스를 사용하는 것에 대해 배움&lt;/li&gt;&lt;li&gt;관계형 데이터 모델에 대해 배우고&lt;/li&gt;&lt;li&gt;SQL으로 질의하는 법에 대해 배우고&lt;/li&gt;&lt;li&gt;상용 RDBMS가 제공하는 추가적인 기능들에 대해 배우고&lt;/li&gt;&lt;li&gt;ER 데이터 모델과 어떻게 RDB 디자인에 대해 배움&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;개발에 대해&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;데이터베이스를 만든다면 고려해야할 것들에 대해 배움&lt;/li&gt;&lt;li&gt;트랜잭션이 무엇인지와 트랜잭션을 어떻게 관리하는지에 대해 배우고&lt;/li&gt;&lt;li&gt;여러 트랜잭션을 동시에 실행하기 위한 방법을 배우고&lt;/li&gt;&lt;li&gt;복구 기능과 안정적인 스토리지 운영 및 버퍼 관리 방법에 대해 배우고&lt;/li&gt;&lt;li&gt;빠른 질의 처리를 위한 인덱스의 종류에 대해 배우고&lt;/li&gt;&lt;li&gt;질의 처리를 위한 과정과 JOIN이나 정렬과 같은 몇 가지 주요 연산을 어떻게 처리하는지 배우고&lt;/li&gt;&lt;li&gt;ORDB 개념에 대해 배우고&lt;/li&gt;&lt;li&gt;데이터 웨어하우스 및 마이닝과 관련된 부분을 조금 배우지만 의미는 크게 없는 듯&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;정리하고 나니까 양이 꽤 된다. 그래도 시간을 아예 날린 것은&amp;nbsp;아닌건지...&lt;/p&gt;&lt;p&gt;몇 개 강의는 굳이 학교에서 들을 필요 없었다 싶은 것들도 있다. 위 리스트에서는 객체 지향 프로그래밍이나 사용자 인터페이스 같은 강의인데, 혼자 공부할 일이 있는 류가 대체로 굳이 학교에서 들을 필요가 없었던 것들에 많이 포함된다. (수강했던 강의들 중 굳이 학교에서 배울 필요 없었다 싶은 강의들은 리스트에&amp;nbsp;추가하지 않았다.)&amp;nbsp;예를 들어 컴퓨터 구조, 프로그래밍 언어, 컴파일러 같은 것들은&amp;nbsp;앞으로 따로 찾아서 공부할 일이 잘 없을 것이라 생각한다. 찾아본다 하더라도 그 때 닥친 문제를 해결하기 위한 아주 지엽적인 부분만 조금 찾아보는 수준일 것이다. 전체를 다 공부하기엔&amp;nbsp;시간이 너무 많이 드니까.&amp;nbsp;그러나 전체적인 흐름을 이해하고 있는 것과 각각 작은 부분만을 알고 넘어가는 것은 그 분야의 이해도에 있어 큰 차이를 보일 것이라 생각한다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;아직 초보 수준인 내가&amp;nbsp;이렇게 말하는게 설득력은 별로 없겠지만...&amp;nbsp;대학교에서 공부하기 전부터 코딩을 해왔지만 근 4년 간 컴퓨터 과학 내에서 다양한 전공들을 공부하고 난 지금, 과거의 나와 지금의 나를 비교해보면 문제를 어떻게 해결할지 생각할 때 좀 더 풍부하게 생각할 수 있게 된 것 같다. 이런 점에서 학교에서 실무를 많이 가르쳐야 한다는 주장에 대해서는 딱히 공감하지 않는 편이다. 예를 들어 학교에서 React를 이용한 웹 개발이나 게임 엔진을 이용한 게임 개발&amp;nbsp;같은 것을 굳이&amp;nbsp;배울 필요가 없다고 생각하는게, 그건 그냥 혼자 알아서 공부하면 되는 것이라고 생각하기 때문이다. 학교에서 배워야 할 것은  좀 더 근본적인 것, 그리고 그것을 바탕으로 새로운 지식을 만들어낼 수 있는 능력이라고 생각한다. 새로운 지식들도 결국 그 지식을 만들기 위해 그 이전의 이론을 활용한 것인 경우가 많으니까 말이다. 아예 새로운 개념조차도 그 개념을 생각해내는 것 자체는 이전의 지식 없이&amp;nbsp;불가능하지 않나 싶다.&lt;/p&gt;&lt;p&gt;마지막으로 대학 생활이 4년 정도를 그냥 일을 하는 것에 비해 더 나은 경험이었는지, 등록금만 따졌을 때&amp;nbsp;도합 3천만원에 달하는 수업료를 낼만한&amp;nbsp;가치가 있는&amp;nbsp;경험이었는지는 섣불리 판단 못 하겠다.&amp;nbsp;어쨌든 이제 거의 끝이다.&lt;/p&gt;</description>
      <category>프로그래밍/잡탕</category>
      <author>터프 프로그래머</author>
      <guid isPermaLink="true">https://toughrogrammer.tistory.com/241</guid>
      <comments>https://toughrogrammer.tistory.com/241#entry241comment</comments>
      <pubDate>Wed, 2 Aug 2017 23:39:02 +0900</pubDate>
    </item>
    <item>
      <title>게임 개발자를 위한 모바일 애플리케이션 빌드 및 실행 과정 자세히 알아보기</title>
      <link>https://toughrogrammer.tistory.com/240</link>
      <description>&lt;p&gt;네이티브에서 직접 작업을 하든, 게임 엔진을 쓰든, 실제 빌드 과정은 동일하다. 하지만 게임 엔진을 쓰는 경우 그 과정이 직접 드러나지 않고 숨겨져 있기 때문에 많은 사람들이 헷갈려 한다. 특히, 게임 개발을 주로 하는 개발자들은 네이티브 애플리케이션 개발 경험이 없다보니 네이티브에 직결되는 기능 개발을 할 때 곤란함을 많이 겪는다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 문제 해결을 위해서는 정확히 어떤 과정을 통해 우리가 배포할 애플리케이션이 만들어지고 실행되는지 알아두면 유용하다. 이번 기회에 그 빌드 및 패키징, 실행 과정에 대해 글을 써보려 한다. 이해를 도우려다보니 배경에 대해 설명을 조금씩 덧붙였고, 그 결과 다소 글 내용이 너저분하다. 하지만 소설 읽듯이 읽어보면 나름대로의 재미가 있지 않을까 생각해본다 ^^;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;&lt;b&gt;Android&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;안드로이드에서 APK가 만들어지고 실행되는 과정은 다음과 같다.&amp;nbsp;&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot;&gt;&lt;li&gt;Java 코드 작성&lt;/li&gt;&lt;li&gt;Java 코드를 컴파일하여 `class` 파일 생성&lt;/li&gt;&lt;li&gt;class 파일을 `dex` 파일로 변환&lt;/li&gt;&lt;li&gt;dex 파일과 사용중인 리소스들을 `apkbuilder`로 apk 파일 생성&lt;/li&gt;&lt;li&gt;`zipalign`으로 apk 파일 binary aligning&lt;/li&gt;&lt;li&gt;`apksigner` 로 apk 파일 signing&lt;/li&gt;&lt;li&gt;`adb` 혹은 스토어를 통해 apk 다운로드&lt;/li&gt;&lt;li&gt;apk의 sign 체크&lt;/li&gt;&lt;li&gt;애플리케이션이 실행될 때 VM을 켜고 자신의 코드 실행&lt;/li&gt;&lt;/ol&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 536px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/2578364258817CC518&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F2578364258817CC518&quot; width=&quot;536&quot; height=&quot;882&quot; filename=&quot;android-1.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify; clear: none; float: none;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;아마 감이 잘 안 올 것이다. 좀 더 자세히 살펴보기 전에 배경 지식들을 알아보자.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify; clear: none; float: none;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;&lt;b&gt;일반적인 Java 프로그램 실행 환경&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;어디선가 들어본 그 이름, `JVM(Java Virtual Machine)`. Java는 여러 플랫폼에서 동일하게 실행 가능한데, JVM 위에서 `바이트 코드`가 실행되기 때문이다. JVM은 Java 측에서 OS, CPU 아키텍처마다 빌드한 실행 가능한 프로그램으로 제공된다. `JRE(Java Runtime Environment)`는 JVM을 포함하며, 프로그램 실행 과정에 필요한 여러 라이브러리들을 포함하고 있다. `JDK(Java Development Kit)`는 JRE와 동시에 Java 언어 컴파일을 위한 컴파일러 및 여러 툴들도 포함한 패키지이다. `javac`가 바로 그 Java 언어를 위한 컴파일러다. JDK를 설치하고 나면 javac를 사용할 수 있다. 이것이 eclipse 같은 프로그램을 실행하기 위해 JRE가 꼭 필요한 이유이다. 만약 추가적으로 JRE를 설치하지 않아도 된다면 이미 JRE가 설치되어 있기 때문일 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 549px; text-align: center;; height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/2573C54258817CC61B&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F2573C54258817CC61B&quot; width=&quot;549&quot; height=&quot;507&quot; filename=&quot;android-2.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;text-align: center;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Java 코드를 javac 명령어로 컴파일하면 바이트 코드인 `*.class` 파일을 만들어내고, `java` 명령어로 컴파일된 class 파일을 메모리에 올린다. JVM에서는 메인 클래스의 `Main(String[] args)` 시그니쳐를 가진 메소드를 찾는다. 그리고 처음 실행할 프로그램 명령어 주소(Program Counter)를 해당 메소드의 처음 지점으로 설정한다. 그리고 만나는 명령어마다 정해진 규칙으로 연산을 수행한다. 이는 CPU가 어셈블리 명령어를 실행하는 것과 비슷하다.&lt;/p&gt;
&lt;p&gt;덤. `JAR(Java Archive)` 파일은 이름에서 나타나듯이 Java의 아카이브 파일이다. class 파일 뿐만 아니라 이미지, 사운드 파일 등을 마치 zip 파일처럼 함께 아카이빙할 수 있다. 이 jar 파일에 아예 Main 메소드까지 포함시킬 수 있다. jar 파일을 만들 때 `jar` 명령어를 사용하는데, jar 명령어를 실행하면서 메인 클래스를 알려줄 수 있다. 그렇게 만든 jar 파일은 `java -jar jar-file` &amp;nbsp;명령어를 통해 바로 jar 파일 내의 바이트 코드들을 실행할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;&lt;b&gt;안드로이드 실행 환경&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Android 운영체제는 모바일 기기 위해서 실행되는 Linux OS라고 생각하면 된다. 데스크톱 환경에서 `java …` 명령어를 통해 JVM 프로세스를 켜고, 그 위에서 바이트 코드를 실행하는 것처럼 모바일 기기에서도 *거의* 똑같은 일이 일어난다.&lt;/p&gt;
&lt;p&gt;JVM은 라이센스 문제가 걸려있어 안드로이드에서는 JVM을 바로 사용하지 않는다. 대신 JVM을 변형한 `Dalvik Virtual Machine`이라는 가상 머신을 이용한다. 달빅 가상 머신에서는 바이트 코드로 `dex` 파일을 이용한다. 안드로이드 SDK에는 `dx` 라는 프로그램이 포함되어 있는데, SDK의 루트 디렉토리에서 `build-tools/24.0.3/dx` 프로그램이 바로 그것이다. SDK 매니저에서 허구한 날 자꾸 업데이트하거나 설치하라고 뜨는 Build-Tools에 포함되어 있다.&lt;/p&gt;
&lt;p&gt;Dalvik은 여러가지 문제가 많아 롤리팝 버전부터는 `ART(Android RunTime)`라는 새로운 런타임으로 교체되었다. ART에서는 애플리케이션이 처음 설치될 때 dex 파일을 또 다시 컴파일하여 `OAT` 파일을 만들어 실행한다. 이 파일 포맷 이름의 유래는 다소 특이하다. 원래 dex 파일은 `dex2opt` 프로그램을 통해 `odex(optimized dex)` 라는 최적화된 dex 파일로 바뀌어 사용가능했었다. 이와 비슷하게 `dex2oat` 프로그램은 dex 파일을 받아 `oat(optimized ahead-of-time)` 파일을 만든다. 그래서 oat 파일이 된 것이다. dex2oat 프로그램은 안드로이드 OS 내에 설치되어 있고, 앞서 설명한 것과 같이 처음 apk이 설치될 때 실행된다.&lt;/p&gt;
&lt;p&gt;oat 파일은 `ELF(Executable and Linkable Format)` 파일이다. 즉, 직접 CPU에 의해 실행 가능하다. 따라서 VM 위에서 실행되는 것에 비해 훨씬 빠른 실행 속도를 보여준다. 또한, 좀 더 훌륭한 메모리 할당과 가비지 컬렉션 성능을 가진다. 더 자세한 설명은 너무 원글의 주제에서 벗어나므로 생략한다. 일단은 이해를 쉽게 하기 위해 일반적인 Java 프로그램들과 비슷하게 VM 위에서 코드가 실행된다고 알아두자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;좀 더 전반적인 내용에 대해 알고 싶다면 &lt;a href=&quot;https://developer.android.com/guide/components/fundamentals.html&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;Application Fundamentals&lt;/a&gt; 문서를&amp;nbsp;읽어보자.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;JNI(Java Native Interface), NDK(Native Development Kit)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;`JNI`를 이용하면 C/C++로 작성된 네이티브 코드에서 JVM을 생성할 수도, JVM에서 실행된 Java 프로그램에서 Shared Library 로딩을 할 수도 있다. 네이티브 코드에서 `JNI_CreateJavaVM` 함수를 실행하면 그 함수를 실행한 쓰레드가 JVM의 메인 쓰레드가 된다. 만들어진 JVM은 `JNIEnv*`가 되어 포인터로 자유롭게 이용 가능하다. 어떤 클래스를 찾아서 그 클래스의 인스턴스를 만들수도, 만들어진 인스턴스의 메소드 혹은 클래스의 static 메소드를 실행할 수도 있다. Java에서 할 수 있는 일의 거의 전부를 할 수 있다고 생각하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;역으로 JVM에서 실행된 Java 프로그램에서 `System.loadLibrary` 메소드를 실행하면 인자로 주어진 Shared Library를 메모리에 로딩하고, Shared Library에 선언된 함수들과 Java 측의 클래스들에 `native` &amp;nbsp;키워드로 선언된 메소드들을 매칭시킨다. native 메소드들을 실행하면 Shared Library에 선언된 함수를 실행하는데, 각 함수들은 항상 인자로 `JNIEnv*`를 받도록 되어 있다. 따라서, 받아온 JVM을 자유롭게 이용하면 된다.&lt;/p&gt;
&lt;p&gt;그렇기 때문에 게임 엔진들(언리얼 엔진, 유니티 엔진, Cocos2d-x 등)은 NDK를 이용해 코드를 Shared Library로 빌드하고 안드로이드 애플리케이션에서 해당 Shared Library를 로딩하는 방식으로 게임을 실행한다. Shared Library를 빌드할 때는 C++ 코드가 그 라이브러리가 사용될 CPU(거의 보통은 ARM 아키텍처)에서 실행 가능한 어셈블리로 컴파일 되어야 한다. 이를 위해 사용되는 것이 `NDK`다. NDK로 빌드하면 네이티브 코드가 원하는 CPU 아키텍처의 Intstruction Set으로 컴파일 되는 것을 믿고 쓸 수 있다. 또한, 안드로이드 측의 여러 시스템들을 좀 더 편리하게 사용할 수 있게 하기 위한 라이브러리들이 포함되어 있다.&lt;/p&gt;
&lt;p&gt;이런 방식으로 작업하는 가장 큰 이유는 속도다. 일단 네이티브 코드가 직접 CPU에 의해 실행되기 때문에 명령어 실행 속도가 더 빠르다. 그리고, 메모리 관리 차원에서도 Java의 메모리 관리보다는 네이티브 코드에서 `malloc`, `free`, `new`, `delete`와 같은 명령어들을 사용하는게 더 효과적이다. JVM이 아닌 OS를 통해 직접 관리하는 것이기 때문이다.&lt;/p&gt;
&lt;p&gt;Shared Library의 네이티브 코드에서는 하드웨어 가속을 받아 열심히 렌더링하여 최종 결과물 비트맵을 만든다. 만들어진 비트맵을 애플리케이션의 View에 입힌다. 그러면 우리가 보는 게임 화면이 완성된다. &amp;nbsp;&lt;/p&gt;
&lt;p&gt;IBM의 웹 페이지 중 &lt;a href=&quot;http://www.ibm.com/developerworks/java/tutorials/j-jni/j-jni.html&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;JNI에 설명이 잘 되어 있는 글&lt;/a&gt;이 있으므로 참고하자.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;ant, gradle, 빌드를 돕는 녀석들&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;앞서 간략한 빌드 순서를 보면 알겠지만 이것 저것 귀찮은 짓을 많이 필요로 한다. 하지만 실제로 IDE를 통해 안드로이드 개발을 할 때는 빌드 버튼만 누르면 apk 파일이 툭 나온다. 그 뒤에는 `ant`나 `gradle`과 같은 빌드 툴들의 도움이 있다.&lt;/p&gt;
&lt;p&gt;과거에는 거의 항상 ant를 이용해서 빌드했었지만, 2013년 쯤부터 혜성처럼 나타난 gradle이 점차 세력을 넓혀갔다. (2013년은 내가 기억하는 와닿는 시기이다.) 그러던 차에 구글에서는 Eclipse를 버리고 Android Studio를 공식 IDE로 선정했다. Android Studio는 기본적으로 gradle을 쓰고 있었는데, Android Studio가 공식 IDE로 선정된 뒤부터 쭉쭉 성장해서 이제는 거의 항상 gradle을 쓴다고 봐도 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;어쨌든, ant나 gradle이나 컴파일러 사용하는 것과 어느 정도 비슷하다고 봐도 된다. `ant build`를 실행하면 ant를 이용하여 미리 안드로이드 SDK에 명세된 대로 이런 저런 작업을 한 뒤 apk 파일이 만들어진다. `gradlew assembleDebug`를 실행하면 gradle을 이용하여 미리 명세된 대로 이런 저런 작업을 한 뒤 debug용 apk 파일이 만들어진다.&lt;/p&gt;
&lt;p&gt;이런 저런 작업에는 앞서 제시한 순서인 Java 소스 컴파일, dex 파일 생성, aapt로 apk 생성, zipalign으로 aligning, apksigner로 signing이 포함된다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;AndroidManifest.xml&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;공식문서에서는 안드로이드 OS가 멀티 유저 리눅스 시스템이라고 말하고 있다. 설치된 애플리케이션에 대해 안드로이드 OS는 각각의 애플리케이션을 하나의 유저처럼 인식한다. 이것이 안드로이드에서 말하는 샌드박스다. 다른 유저의 홈 디렉토리를 함부로 접근할 수 없는 것으로 샌드박스가 되는 것이다.&lt;/p&gt;
&lt;p&gt;`AndroidManifest.xml` 파일은 시스템에게 애플리케이션의 몇 가지 꼭 필요한 정보를 제공하기 위해 필요하다. 예를 들면 애플리케이션에서 사용하는 기능들에 대한 권한, 빌드하는데 사용된 SDK 버전, 코드에 존재하는 App 컴포넌트들(Activity, Service, Broadcast Receiver, Content Provider) 등이 있다. AndroidManifest.xml에 명시된 App 컴포넌트로는 아마 `activity` element가 가장 익숙할 것이다. 애플리케이션에 어떤 Activity가 존재하는지 알려주는 element이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;빌드를 통해 만들어진 dex, oat 파일들은 바이트 코드 뭉치들에 불과하다. 안드로이드 OS에서 애플리케이션을 실행하면서 VM 프로세스를 켠 뒤, 바이트 코드들도 메모리에 올린다. 그 다음은? Java에서 메인 클래스의 Main 메소드를 Entry 함수로 지정하듯이, 어디서부터 코드 실행을 시작할지 VM에게 알려줘야 한다. `activity` element 내의 `intent-filter`에서 그 정보를 제공한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/growingdever/36e91d00bdca7e8baae76dafef42892c.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;처음 시작될 Activity에 대한 전형적인 선언이다. 이 내용을 조금만 더 깊게 보면 이렇다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;나의 패키지에 MainActivity 라는 클래스가 존재한다.&lt;/li&gt;&lt;li&gt;이 MainActivity는 메인 Activity니까, 애플리케이션이 처음 시작될 때 MainActivity의 인스턴스를 만들어서 실행해달라.&lt;/li&gt;&lt;li&gt;그리고 Launcher를 통해 애플리케이션을 노출시킬 때, 이 Activity를 Entry Point로 생각해 달라.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;이런 식으로 App 컴포넌트인 Activity, Service, Broadcast Receiver, Content Provider에 대한 정보들을 명시적으로 제공하는 것이 안드로이드의 구조다. 특히, 메인 Activity에 대한 부분은 엔진들로부터 만들어지는 Intermediate 소스들을 보기 위해 꼭 필요한 지식이므로 좀 더 자세히 설명했다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 정도면 필요한 배경 지식이 어느 정도 설명된 것 같다. 이제 예시를 보자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;예시1: 언리얼 엔진에서의 안드로이드 빌드&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;언리얼 엔진에서는 `UAT(Unreal Automation Tool)`라는 것으로 이 과정들이 숨겨져있다. 여러가지 복잡한 인자들을 많이 요구하는데, UAT를 이용하여 리소스 쿠킹, 바이너리 빌드, 기기에 배포 등을 모두 할 수 있도록 만들었기 때문이다. 언리얼 에디터에서 패키징 혹은 프리뷰 런칭을 할 때도 내부적으로는 UAT를 통해 빌드하고 기기에 배포까지 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;사실 그 모든 것이 내부적으로는 적절하게 프로젝트 파일들을 세팅한 뒤 `ant build`, `adb install apk-file` 명령어 같은 것을 실행하는 것이다. 그렇기 때문에 프로젝트 디렉토리의 Intermediate 파일들을 보면 전형적인 안드로이드 프로젝트의 디렉토리 구조를 갖고 있으며, UAT 명령어의 실행 로그를 보면 도중에 ant 명령어 실행과 그 로그들을 확인할 수 있다. 안드로이드 프로젝트의 AndoridManifest.xml 파일을 보면 메인 Activity로 `GameActivity`라는 언리얼 엔진 측의 클래스를 사용하는 것을 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;당연히 프로젝트에 생성한 C++ 클래스들에서 JNI를 사용할 수 있기 때문에 직접 Java의 코드들을 실행할 수도 있다. 이를 이용하여 Tapjoy와 같은 네이티브 기능을 사용하는 SDK를 사용할 수 있다. 물론, 그 역도 성립한다. 프로젝트의 C++ 코드들은 빌드를 통해 Shared Library `libUE4.so`로 만들어진다. 그리고 `GameActivity`에서는 `System.loadLibrary`로 `libUE4.so`를 로드함으로써 네이티브 코드들이 실행 가능해진다.&lt;/p&gt;
&lt;p&gt;써드파티 Java 클래스들을 사용하기 위해서는 먼저 사용하려는 Java 클래스들을 컴파일하여 jar 파일로 만들어야 한다. 다음으로 이 jar 파일이 함께 패키징되도록 해야 한다. 슬프게도 언리얼 엔진의 빌드 프로세스는 굉장히 복잡하게 숨겨져있다. 가장 쉬운 방법은 사용하려는 jar 파일을 &amp;nbsp;엔진 디렉토리의 안드로이드 템플릿 프로젝트의 `libs` 디렉토리에 넣어버리는 것이다. 이 방법의 문제점은 해당 엔진을 사용하는 모든 프로젝트에 영향을 미친다는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다른 방법으로는 4.13버전까지 기준 Plugin Language 기능을 이용하는 방법이 있다. Plugin Language에 명세로 어떤 파일들을 Intermediate 프로젝트에 복사시키거나, 추가 권한 획득이나 App 컴포넌트 추가를 위한 AndroidManifest.xml 수정, GameActivity의 몇몇 라이프 사이클 메소드에 코드 추가 등이 가능하다. 그러나 굉장히 제한적인 수준이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;예시2: 유니티 엔진에서의 안드로이드 빌드&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;유니티 엔진에서도 에디터를 통해 빌드하는 것이 일반적이다. 안드로이드 빌드를 하면 프로젝트의 `Temp/StagingArea` 디렉토리에 안드로이드 빌드 관련 파일들이 생성된다. &amp;nbsp;이를 똑같이 ant나 gradle로 빌드하면 APK를 만들게 되는 것이다.&lt;/p&gt;
&lt;p&gt;`AndroidManifest.xml` 을 확인해보면 `UnityPlayerActivity`라는 Activity로 시작하게 되어 있음을 알 수 있다. 또한, `libs/armeabi-v7a` 디렉토리에 `libmain.so`, `libmono.so`, `libunity.so` 파일이 생성되어 있다. 조금 검색해보면 `UnityPlayerActivity`는 &amp;nbsp;`UnityPlayer`를 실행하도록 하고, `UnityPlayer`는 `libmain.so`를 로드하게 되어 있음을 찾아볼 수 있다. 아마 `libmono.so`, `libunity.so`도 어디선가 로드해서 사용하고 있을 것이다. 유니티 엔진에서는 특별히 콘솔 같은 곳에서 로그를 출력해주지 않아 더 자세한 것은 좀 더 수고를 들여 찾아봐야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;유니티 엔진에서는 C#으로 코딩하기 때문에 C/C++로 된 JNI를 직접 사용하는 경우는 드물다. `AndroidJavaClass`, `AndroidJavaObject` 등 클래스로 Wrapping 된 것을 사용하는데, 결국 이는 내부적으로 JNI를 사용하는 것이기 때문에 사용법만 조금 달라졌을 뿐, C/C++에서 JNI를 사용하는 것과 본질적으로는 비슷하다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;써드파티 Java 클래스들을 사용하기 위해서는 먼저 사용하려는 Java 클래스들을 컴파일하여 jar 파일로 만들어야 한다. 그리고 `Assets/Plugins/Android` 디렉토리에 그 jar 파일을 두면 빌드 과정에서 함께 패키징되므로 Java 측에서 바로 이용하든, JNI로 찾아와서 이용하든 편한대로 사용하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;웃기는 것은 Temp 디렉토리를 유니티 엔진 에디터가 종료될 때 삭제하도록 되어 있다. 굳이 왜 그랬을까… 그 용량 얼마를 위해…?&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;예시3: Cocos2d-x에서의 Downloader&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Cocos2d-x에는 `Cocos2dxDownloader` 라는 것이 있는데, 인터넷을 통해 어떤 데이터를 다운로드 받아와서 쓰기 위핸 클래스로 생각하면 된다. 정확한 버전은 기억나지 않지만, 안드로이드에서는 네트워킹과 같은 Blocking operation을 수행하는 코드를 메인 쓰레드에서 실행하면 애플리케이션이 꺼져버리게 변경되었다. 그렇기 때문에 HTTP 통신과 같은 작업을 하기 위해서는 백그라운드 쓰레드를 만들고, 그 쓰레드에서 작업을 수행해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Cocos2d-x에서는 C++로 코드를 작성하며, `libc`에 포함된 &amp;nbsp;`pthread`를 쓸 수도 있고, ARM 아키텍처로 빌드된 `curl` 라이브러리를 쓸 수도 있다. 하지만 엔진에서는 Java로 만들어진 `Cocos2dxDownloader` 클래스도 잘 쓰고 있다. 다운로딩 기능이 필요할 때 C++ 측에서 JNI로 `Cocos2dxDownloader`를 생성해주는 static 메소드를 찾고 그 메소드를 실행한다. 만들어진 클래스는 안드로이드의 VM 위에서 잘 작업이 수행되고, 작업이 끝나면 native 메소드 실행을 통해 C++ 측으로 Callback 함수를 호출해준다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;JNI에서는 함수 포인터를 넘겨줄 수 없는 한계가 있다. 그렇기 때문에 &amp;nbsp;`Cocos2dxDownlaoder`를 생성할 때 id를 지정해주고, Callback 함수를 호출할 때는 어떤 다운로더가 끝났는지를 함께 넘겨준다. C++ 측에서는 각 다운로더 id별 Callback 함수들을 따로 관리하고 있기 때문에, Callback 함수로부터 받은 id로 적절한 함수를 찾아 실행해준다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;&lt;b&gt;iOS&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;iOS에서 애플리케이션이 만들어지고 실행되는 순서는 다음과 같다.&amp;nbsp;&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot;&gt;&lt;li&gt;각 소스 파일 컴파일 - ARM 아키텍처의 목적 파일 생성&lt;/li&gt;&lt;li&gt;각 목적 파일 링킹&lt;/li&gt;&lt;li&gt;스토리보드를 비롯한 각종 리소스들 처리&lt;/li&gt;&lt;li&gt;애플리케이션 패키징&lt;/li&gt;&lt;li&gt;패키지 내에 번들 리소스들 복사&lt;/li&gt;&lt;li&gt;패키지 내에 프로비전 파일 내장&lt;/li&gt;&lt;li&gt;패키지 내 파일들 기반으로 signing&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;안드로이드와 다르게 iOS에서 애플리케이션은 완벽하게 네이티브 애플리케이션으로써 실행된다. 하지만 `Framework` 같은 생소한 용어와 `modulemap`, 그리고 안드로이드 작업시에는 만날 일이 드문 CPU 아키텍처 심볼 에러 때문에 많은 사람들이 당황한다. 게다가 XCode는 겉보기에는 많은 부분을 감춘 채 작업하기 때문에 그 속에서는 어떤 작업을 하는지 잘 모르는 경우가 많다. 컴파일러를 비롯하여 좀 더 근본적인 부분부터 살펴보면 앞으로 그 문제들에 대해 덜 고통스러울 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;잠깐. Swift가 이제는 많이 쓰이고 있지만, 이 글에서는 일단 Objective-C 기준으로 설명을 하려 한다. 그 이유는 Swift에서 Static Library를 사용하는 과정이 다소 복잡하고, 아직 많은 엔진들이 Objective-C 기준으로 만들어져 있기 때문이다. 다만 중간 중간 비교를 위해 Swift에 대한 설명을 조금 곁들이도록 하겠다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;Xcode Build 디렉토리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;XCode에서 빌드를 하면 `Build` 디렉토리가 생성된다. 이 디렉토리 내의 구조를 알아두는건 앞으로 이야기할 것들을 이해하는데 좀 더 도움이 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;`Intermediate` 디렉토리는 말 그대로 중간 파일들을 모아두는 디렉토리다. 컴파일 과정에서 생겨나는 목적 파일, 디버거를 위한 파일들, 후 처리 된 리소스들 등이 이 디렉토리 내에 생성된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;`Products` 디렉토리에는 빌드 최종 결과물이 위치하게 되는 디렉토리이다. XCode에서 빌드 대상으로 선택한 기기와 Debug, Release 여부에 따라 이름이 정해지게 되는데, iOS에 한정하여 보면 다음과 같다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Debug-iphoneos : General iOS Devices 선택 상태에서 Debug 빌드 결과물들이 위치&lt;/li&gt;&lt;li&gt;Debug-iphonesimulator : Simulator 선택 상태에서 Debug 빌드 결과물들이 위치&lt;/li&gt;&lt;li&gt;Release-iphoneos : General iOS Devices 선택 상태에서 Release 빌드 결과물들이 위치&lt;/li&gt;&lt;li&gt;Release-iphonesimulator : Simulator 선택 상태에서 Release 빌드 결과물들이 위치&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;그렇기 때문에 `Debug-iphonesimulator` 디렉토리에 생성된 정적 라이브러리 파일에 대한 아키텍처 정보를 보면 x86_64와 같은 Intel 아키텍처로, `Debug-iphoneos` 디렉토리에 생성된 정적 라이브러리 파일에 대한 아키텍처 정보를 보면 arm64와 같은 ARM 아키텍처로 나타난다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;LLVM 빌드 과정과 그 결과물&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;위에서 iOS의 애플리케이션은 VM 같은 것이 없는 네이티브 애플리케이션이라 했다. 어쨌건, C/C++ 류의 언어를 컴파일하여 어떤 실행 가능한 프로그램을 얻기 위해서는 컴파일러가 꼭 필요하다. 애플 진영에서는 그 컴파일러로 LLVM이라는 컴파일러 기반 구조를 운영하고 있다. 그리고 문법 분석, 의미 분석 등을 위한 프론트엔드로 clang이라는 프로젝트를 이용한다. LLVM의 빌드 과정은 다음과 같다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot;&gt;&lt;li&gt;clang으로 컴파일하여 Bitcode를 만든다.&lt;/li&gt;&lt;li&gt;만들어진 Bitcode를 Optimizer로 최적화 한다.&lt;/li&gt;&lt;li&gt;최적화된 Bitcode를 원하는 CPU 아키텍처의 Instruction Set으로 변환한다.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;사실 intermediate 파일을 만들고, 그것을 이용하여 최종 Instruction Set의 어셈블리로 만든다는 점에서 기존의 컴파일러 구조와 큰 차이는 없다. 그러니 용어를 숙지하는 차원에서 언급한 것으로 알고 넘어가자.&lt;/p&gt;
&lt;p&gt;안드로이드는 어떤 작업을 하기 위해 세부적으로 숨겨진 옵션을 많이 건드려야 하는 경우가 흔치 않지만 iOS는 은근히 그런 경우가 많다. 그래서 앞서 Android 빌드 과정에서는 실행 파일이 만들어지기까지의 과정에 대한 자세한 언급 없이 넘어갔지만, iOS의 빌드 과정에서는 설명이 필요할 것 같으므로 아래 배경 지식들에서 설명한다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;링킹&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;링킹은 나눠진 각각의 목적 파일들을 연결(Link) 하는 작업이라는 의미에서 링킹이라 불린다. 링킹은 정적 링킹(Static Linking)과 동적 링킹(Dynamic Linking)으로 나뉜다.&lt;/p&gt;
&lt;p&gt;`gcc -o output file1.o file2.o` 와 같이 여러 목적 파일을 링킹한 경험이 있을 것이다. 이렇게 링킹하는 것을 컴파일 타임에 링킹하기 때문에 정적 링킹이라 한다. 정적 링킹을 할 때 Static Library(정적 라이브러리)도 함께 링킹할 수 있다. `gcc -o output main.c -lsome.a` 와 같이 명령어를 실행하면 `main.c` 소스 파일과 함께 `libsome.a` 라는 정적 라이브러리를 함께 링킹하여 output 이라는 실행 파일을 만들겠다는 뜻이다. (`-l` 옵션 뒤에는 라이브러리의 파일명에서 `lib` 를 뗀 이름이 온다.) 정적 라이브러리는 보통 `*.a`, `*.lib` 파일로 많이 알려져 있다.&lt;/p&gt;
&lt;p&gt;동적 링킹은 실행 파일을 먼저 실행한 뒤 링킹되는 것을 말한다. 정적 링킹을 하는 경우에는 실행 파일에 정적 라이브러리가 포함되어야 하기 때문에 실행 파일의 용량도 커지고, 메모리에 올려둬야 할 코드 영역도 너무 커지게 된다. 동적 라이브러리는 여러 프로세스가 동일한 코드 영역을 공유할 수 있도록 함으로써 실행 파일의 용량과 필요한 메모리 양을 줄여준다. 윈도우에서 `C:\Windows\System32` 디렉토리에 수많은 `dll` 파일들이 모여있는 것을 본 적 있을 것이다. 윈도우에서는 dll 파일이 동적 라이브러리에 해당되는 파일인데, 윈도우 실행 파일은 처음 실행되면서 동적 링킹 대상 dll 파일을 찾기 위해 `C:\Windows\System32` 디렉토리를 먼저 뒤져본 뒤, 파일을 찾으면 링킹 작업을 수행한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;링킹에 대해서 너무 깊게 다루는 것은 글의 초점을 흐리게 할 것 같으므로 이 정도까지만 설명하고, 애플 진영과 iOS에 한정된 지식을 좀 더 알아보자. 링킹을 더 알아보고 싶으면 아래 레퍼런스를 참고.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%A0%95%EC%A0%81_%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;정적 라이브러리 - 위키백과, 우리 모두의 백과사전&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%8F%99%EC%A0%81_%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;동적 라이브러리 - 위키백과, 우리 모두의 백과사전&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;리눅스에서 `/usr/lib` 디렉토리에 각종 시스템 공용 라이브러리가 있듯이, iOS에서도 마찬가지다. iOS라는 OS도 리눅스 OS와 비슷하게 시스템 공용 라이브러리가 `/usr/lib` 디렉토리에 존재한다. 애플 진영에서는 동적 라이브러리를 `dylib` 라는 확장자로 표시하는데, 예를 들면 `libz` 라이브러리의 핸들을 얻은 뒤 `deflate` 함수에 대한 포인터를 얻기 위해 다음과 같이 코딩한다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;사용자 정의 프레임워크 빌드 결과물 뜯어보기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;시스템 라이브러리가 아닌 것들을 사용하는 경우에는 어떻게 될까? 예를 들어, 내가 만든 라이브러리를 포함시켜 사용하기 위해서는 어떤 작업이 필요한지 알아보자. 이를 위해 샘플 프로젝트로 `SimpleProjectWithFramework` 프로젝트를 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 820px; text-align: center;; height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/24033D4258817CC713&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F24033D4258817CC713&quot; width=&quot;820&quot; height=&quot;459&quot; filename=&quot;ios-1.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;text-align: center;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;XCode에서는 각각 빌드 결과물을 Target이라는 용어로 부른다. 이 프로젝트는 SimpleProjectWithFramework라는 iOS 애플리케이션 타겟과 SimpleFramework라는 Cocoa Touch 프레임워크 타겟으로 구성되어 있다. 여기서 Cocoa Touch 프레임워크는 앞서 언급한 dylib와 비슷하다고 생각하면 되는데, iOS 어느 버전부터는 dylib를 포함시켜 빌드한 애플리케이션은 배포할 수 없게 바뀌었다. 대신, framework 확장자를 가진 패키지들이 기존의 동적 라이브러리 파일 역할을 수행한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 820px; text-align: center;; height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/2256C74258817CC831&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F2256C74258817CC831&quot; width=&quot;820&quot; height=&quot;110&quot; filename=&quot;ios-2.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;text-align: center;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 820px; text-align: center;; height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/26676E4258817CC823&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F26676E4258817CC823&quot; width=&quot;820&quot; height=&quot;102&quot; filename=&quot;ios-3.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;text-align: center;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위 스크린샷은 SimpleFramework.framework의 내용물이다. Headers 디렉토리에 Public 헤더로 추가했던 파일들이 위치하고, Modules 디렉토리에는 `module.modulemap` 이라는 파일이 존재한다. 이 파일은 프레임워크 내에 있는 모듈들을 찾기 위해 존재하는 파일이다. Umbrella Header와 어떤 모듈을 노출시킬 것인지에 대한 설정 파일으로, clang에서 컴파일 할 때 이 파일을 참고하도록 되어 있다. 그리고 Umbrella Header 이름은 보통 프레임워크 이름으로 지정되어 있다.&lt;/p&gt;
&lt;p&gt;이는 Swift 언어가 추가되면서 생긴 것으로 보인다. C/C++ 계열과 비슷하게 &amp;nbsp;`#import` 매크로로 헤더 파일을 추가하던 방식에 비해 Swift에서는 `import FrameworkName` 과 같이 클래스를 import 하도록 바뀌면서, 이에 대한 언어간의 대응을 위해 필요하게 되었다. 이 modulemap 파일을 통해 컴파일러는 컴파일 과정에서 어떤 심볼이 무슨 모듈에 대한 심볼인지 알 수 있게 되는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 820px; text-align: center;; height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/2603C54258817CC912&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F2603C54258817CC912&quot; width=&quot;820&quot; height=&quot;67&quot; filename=&quot;ios-4.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;text-align: center;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Modules 디렉토리에 대한 좀 더 극명한 예를 위해 언어를 Swift로 설정한 SimpleSwiftFramework 타겟의 빌드된 결과물에서 Modules 디렉토리를 스크린샷으로 찍었다. `arm64.swiftmodule` 이라는 파일이 추가된 것을 알 수 있다. (`arm64.swiftmodule` 이라 이름 붙게 된 것은 General iOS Device를 선택하여 빌드했기 때문이다. Simulator를 선택하여 빌드하면 `x86_64.swiftmodule` 이라는 파일로 생성된다.) Swift 언어는 Java와 같은 언어와 다르게 컴파일 결과 생성되는 목적 파일을 이용하는데, 이는 C 계통 언어와 비슷하다. 프레임워크 내에 여러개의 Swift 소스 파일이 존재할 수 있기 때문에, 컴파일된 목적 파일들은 swiftmodule 파일로 모두 합쳐지며, 이는 Xcode 빌드 메세지에서도 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 662px; text-align: center;; height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/230E6B4258817CC90A&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F230E6B4258817CC90A&quot; width=&quot;662&quot; height=&quot;73&quot; filename=&quot;ios-5.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;text-align: center;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;사용자 정의 프레임워크 사용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;어쨌건, 만들어진 프레임워크를 사용하여 애플리케이션 개발을 할 수 있어야 한다. 다른 프레임워크를 import해와서 사용하는건 어떻게 되는걸까?&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 컴파일 타임을 생각해보자. 사용하려는 프레임워크의 심볼을 찾아 존재하는 것을 확인해야한다. 따라서, 컴파일 할 때는 프레임워크를 마치 헤더 찾듯이 찾을 수 있어야 한다. 그러기 위해 clang으로 컴파일 할 때 &amp;nbsp;`-F` 옵션으로 프레임워크 검색 디렉토리를 추가해준다. 프레임워크 검색 디렉토리에서 사용하려는 프레임워크가 존재하면 그 안의 modulemap을 통해 Umbrella 헤더를 import 할 수 있음을 알게된다. 정적 링킹을 할 때도 마찬가지로 `-F` 옵션으로 프레임워크 검색 디렉토리를 추가해주고, 그 중에서도 링킹 대상 프레임워크의 이름을 `-framework` 옵션으로 지정해준다. 예를 들면 `-framework SimpleFramework` 와 같은 식이다.&lt;/p&gt;
&lt;p&gt;SimpleFramework.framework는 시스템에 미리 존재하는 프레임워크가 아니므로 애플리케이션에 함께 포함되어 있어야 한다. 프로젝트 세팅에 대한 스크린샷을 보면 SimpleProjectWithFramework 타겟에서는 Embedded Binaries로 SimpleFramework.framework를 포함하고 있는 것을 볼 수 있는데, 이렇게 프레임워크를 Embedded Binaries에 추가하면 애플리케이션이 빌드될 때 애플리케이션의 패키지 내에 해당 프레임워크를 포함시켜준다. 그 결과, 빌드된 iOS 애플리케이션의 내부는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 748px; text-align: center;; height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/2104304258817CCA12&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F2104304258817CCA12&quot; width=&quot;748&quot; height=&quot;169&quot; filename=&quot;ios-6.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;text-align: center;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;빌드된 iOS 애플리케이션은 `SimpleProjectWithFramework.app` 이라는 패키지 파일인데, 맥에서 더블 클릭하면 실행할 수 없는 애플리케이션이라는 에러 메세지가 뜬다. 사실 이 파일은 디렉토리라서 오른쪽 클릭 후 패키지 내부 보기 옵션을 선택하면 패키지 내 파일들을 볼 수 있는데, 파일들을 살펴보면 Frameworks라는 디렉토리 내에 SimpleFramework.framework가 추가된 것을 볼 수 있다. 이를 통해 iOS에서 애플리케이션을 실행할 때 Frameworks 디렉토리 내의 framework들을 모두 동적 링킹을 해줄 것음을 추측할 수 있다. 윈도우에서 프로그램을 배포할 때 사용중인 dll 파일을 함께 패키징하여 배포하는 것과 같은 맥락이라 생각하면 이해하기 쉬울 것이다.&lt;/p&gt;
&lt;p&gt;참고로, Objective-C 프로젝트에 Swift로 만들어진 프레임워크를 쓰고자 하는 경우에는 `Always Embed Swift Standard Libraries` 옵션을 Yes로 설정하면 되는데, 이는 Swift를 위한 표준 라이브러리들을 동적 링킹시키겠다는 의미이다. 이렇게 하면 Frameworks 디렉토리에 `libswiftCore.dylib`, `libswiftFoundation.dylib` 와 같은 표준 Swift 라이브러리들이 추가되며, 동적 링킹시 이 표준 라이브러리들과 링킹시킴으로써 정상적으로 Swift의 심볼들을 이용할 수 있게 된다.&lt;/p&gt;
&lt;p&gt;덤. &amp;nbsp;`ipa` 파일은 위의 애플리케이션 패키지를 그냥 압축한 것이므로 결과적으로는 큰 차이 없다고 보면 된다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;정적 라이브러리 사용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;위에서 언급한 것들은 동적 라이브러리에 해당하는 프레임워크에 대한 이야기였다. 정적 라이브러리는 링킹에 대해 설명한 것처럼 컴파일 타임에 함께 링킹되어야 한다. XCode에서는 정적 라이브러리를 Embedded Binaries 메뉴 아래의 `Linked Frameworks and Libraries` 메뉴를 통해 추가하도록 되어 있다. 만약 여기에 정적 라이브러리를 추가해주지 않은 상태로 빌드할 경우 다음과 같은 에러가 발생할 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 534px; text-align: center;; height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/235BDB4058817CCA20&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F235BDB4058817CCA20&quot; width=&quot;534&quot; height=&quot;68&quot; filename=&quot;ios-7.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;text-align: center;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;SimpleStaticLibrary라는 심볼을 링킹하려 하는데 찾지 못했다는 의미의 에러 메세지이다. XCode 프로젝트에서 타겟 추가를 통해 정적 라이브러리를 추가하는 경우 자주 만나게 되는 에러 메세지이다. `Product/SCHEMA/include` 디렉토리는 기본적으로 헤더 검색 대상 디렉토리에 추가되어 있기 때문에 해당 소스만을 컴파일 할 때는 헤더 파일을 통해 심볼을 찾을 수 있어 에러가 나지 않지만, 소스 컴파일 후 링킹 과정에서는 정작 해당 심볼의 내용물이 들어있는 정적 라이브러리 파일을 찾을 수 없기 때문이다. Linked Frameworks and Libraries 메뉴에 원하는 정적 라이브러리를 추가해주면 링킹 명령어에 해당 라이브러리 파일을 명시해주기 때문에 에러가 나지 않게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;또한, 이 링킹 대상은 반드시 같은 CPU 아키텍처로 빌드된 것이어야 한다. 예를 들어 arm64 ABI로 빌드된 목적 파일에 x86_64 ABI로 빌드된 정적 라이브러리를 링킹하려 하면 arm64 아키텍처를 위한 심볼을 찾을 수 없다는 에러가 뜰 것이다.&lt;/p&gt;
&lt;p&gt;덤으로, 정적 라이브러리 만드는 것에 대해서는 &lt;a href=&quot;https://github.com/jverkoey/iOS-Framework&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;이 글&lt;/a&gt;을 추가로 살펴보는 것도 좋다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;tbd 파일&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;tbd는 `text-based stub libraries` 의 약자이다. 이제는 개발자에게 노출되는 라이브러리, 예를 들면 Foundation 프레임워크 같은 라이브러리들은 직접적으로는 tbd 파일을 가지고 있기만 한 것으로 바뀌었다. tbd 파일은 라이브러리를 설명하는 파일이라고 볼 수 있다. 아래는 libsqlite3.tbd 파일의 내용 중 일부이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 820px; text-align: center;; height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/220E1F4058817CCB03&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F220E1F4058817CCB03&quot; width=&quot;820&quot; height=&quot;316&quot; filename=&quot;ios-8.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;text-align: center;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;내용물을 보면 어떤 아키텍처에 대해 어떤 심볼들이 존재하고, 또 어떤 dylib를 참조하고 있는지 등에 대한 내용이다. libsqlite3는 OSX에서도, iOS에서도 `/usr/lib` 디렉토리에 미리 존재하는 라이브러리인데, 이런 것들을 잘 관리하기 위해 정리된 모듈 맵 파일 같은 것이다. tbd 파일에 명시된 정보를 바탕으로 앱스토어에서 애플리케이션을 다운로드 받을 때 [다운로드 용량을 줄일 수 있다는 글](https://forums.developer.apple.com/message/9176#9176)을 애플측 포럼에서 볼 수 있지만, 나도 실험을 해본 것은 아닌지라 확신은 못하겠다. 일단은 여러 심볼들과 빌드된 바이너리의 CPU 아키텍처, 공유 라이브러리의 위치 등의 정보를 제공한다는 것 정도까지만 알고 있어도 이해하는데 문제는 없다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 파일을 참조하여 컴파일 할 때 심볼이 존재하지 않으면 심볼이 존재하지 않다는에러를 띄울 수 있고, 애플리케이션 프로세스를 띄운 뒤 동적 링킹을 할 때는 어떤 경로에 공유 라이브러리가 있는지 찾아 로드한 뒤 링킹할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;Packaging, Provisioning 및 Signing&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;이제 링킹까지 끝내고 iOS 기기들의 CPU인 ARM 아키텍처에서 실행될 수 있는 실행 파일 만들기까지 할 수 있다. 마지막으로 필요한 것은 패키징 작업 및 프로비저닝, 사이닝이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;패키징 작업은 필요한 파일들을 정해진 규칙에 맞게 디렉토리 구조로 만드는 작업이다. 실행 파일과 Info.plist 파일, 각종 이미지, 스토리보드 리소스 등을 적절한 디렉토리에 둔다. 필요한 경우 Frameworks 디렉토리 내에 사용 중인 프레임워크들을 둔다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기에 프로비전 파일이 임베디드 된다. 별 거창한 것은 아니고, 개발자 등록을 하고 애플 개발자 콘솔에서 만든 프로비전 파일을 패키지에 둠으로써 누가 만든 애플리케이션인지 알도록 하는 것이다. 간단하게 프로비전 파일을 복사하여 패키지 내에 위치시키는 것으로 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 `codesign`으로 사이닝 작업을 한다. 패키지 내에 있는, 프레임워크들까지 포함한 모든 파일들에 대해 특정 키로 연산한 결과값을 리스팅한다. 이로써 누군가 패키지 내 파일을 변조한 뒤 다시 배포하는 경우 값 대조를 통해 변조 여부를 알 수 있게 된다. 사이닝 결과 값들은 `_CodeSignature/CodeResources` 파일에 있다. 아래는 그 내용의 일부이다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;xcodebuild 명령어 사용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;XCode 프로젝트 혹은 워크스페이스 파일에 이미 저장된 설정들을 기반으로 빌드 명령어를 실행할 수 있다. `man xcodebuild` 명령어로 매뉴얼을 보면 더 자세한 설명을 볼 수 있는데, 이 항목에서는 매뉴얼 페이지의 유용한 예제 몇 개만 소개한다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;`xcodebuild clean install`&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Products 디렉토리를 정리한 뒤 프로젝트의 첫 번째 타겟을 빌드하여 설치한다.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;`xcodebuild -project MyProject.xcodeproj -target Target1 -target Target2 -configuration Debug`&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;MyProject 프로젝트의 Target1, Target2를 Debug 모드로 빌드한다.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;`xcodebuild archive -workspace MyWorkspace.xcworkspace -scheme MyScheme`&lt;/li&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;MyWorkspace의 MyScheme에 해당하는 빌드 결과를 아카이빙한다.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;이렇게 명령어를 실행하면 XCode의 UI에서 빌드하는 것과 동일하게 작업을 수행할 수 있다. 다만, Provisioning, Signing 작업을 위해 프로젝트 세팅에서 미리 개발자 정보를 세팅해두는 것이 불필요한 삽질을 막을 수 있는 길이다.&lt;/p&gt;
&lt;p&gt;컴파일 및 빌드 과정에 대해 다루다보니 배경 지식 설명이 길었다. 이제 예시를 보자!&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;예시1: 언리얼 엔진에서의 iOS 빌드&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;언리얼 엔진에서는 iOS 빌드 역시 Automation Tool로 빌드 과정이 감싸져있다. 특이사항으로, 언리얼 엔진의 Automation Tool, Build Tool, Header Tool 등이 mono로 실행된다. 각각의 툴들은 C#으로 짜여져 있어 .NET Framework 기반이기 때문에 OSX에서도 mono로 실행 가능하기 때문이다. 그리고 블루프린트 프로젝트가 아닌 C++ 네이티브 프로젝트는 OSX에서만 iOS 패키징이 가능하다. 블루프린트 프로젝트는 블루프린트 관련 리소스만 추가 바이너리로 업데이트하고 Siging만 하면 되지만, C++ 네이티브 코드들은 빌드를 위해 iOS SDK와 XCode 빌드 툴 체인이 꼭 필요하기 때문이다.&lt;/p&gt;
&lt;p&gt;clang은 C/C++ 문법과 Objective-C 문법을 섞어 코드를 짜도 컴파일이 정상적으로 되는 놀라운 컴파일러다. 이 덕분에 C++로 짜여진 모듈들을 고통 없이 컴파일하여 사용할 수 있다. 홈페이지에서 배포하는 엔진을 받으면 iOS 관련 엔진 소스들이 이미 iOS SDK 기반으로 정적 라이브러리로 빌드된 상태이기 때문에 프로젝트의 코드들만 빌드한 뒤 링킹하여 최종 정적 라이브러리를 생성한다. 만약 소스 기반으로 빌드를 진행한다면 엔진 소스를 빌드하는 것부터 시작할 것이다. 엔진 소스를 빌드할 때 iOS SDK 기반으로 컴파일되는 것이다.&lt;/p&gt;
&lt;p&gt;만들어진 정적 라이브러리는 그냥 사용된다. iOS 네이티브 애플리케이션에서 빌드된 정적 라이브러리의 이런 저런 메소드를 그냥 호출하여 사용할 수 있다. 왜냐하면 그 라이브러리는 iOS 네이티브로 컴파일된 정적 라이브러리니까! 원래 XCode의 Objective-C 언어 기반 iOS 프로젝트를 만들면 기본적으로 만들어지는 AppDelegate 같은 것도 언리얼 엔진의 소스에 이미 포함되어 있다. `IOSAppDelegate.cpp`, `LaunchIOS.cpp` 파일 등을 참고해보면 iOS 애플리케이션의 라이프사이클 위에서 언리얼 엔진을 잘 실행하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;예시2: 유니티 엔진에서의 iOS 빌드&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;유니티 엔진에서는 iOS 빌드시 XCode 프로젝트와 파일들을 만들도록 되어있다. iOS 빌드를 하면 지정한 디렉토리에 XCode 프로젝트가 생긴다.&lt;/p&gt;
&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 820px; text-align: center;; height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/23601B4058817CCC1E&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F23601B4058817CCC1E&quot; width=&quot;820&quot; height=&quot;401&quot; filename=&quot;ios-9.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;text-align: center;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위 스크린샷에서 포커스 되어 있는 파일은 유니티 엔진에 대한 정적 라이브러리이다. 유니티 엔진이 작동하기 위해 Mono를 비롯한 여러가지 모듈들이 한 정적 라이브러리로 제공된다. 물론 이 파일은 유니티 엔진이 배포될 때 함께 받은 라이브러리이다. `/Applications/Unity/PlaybackEngines/iOSSupport/Trampoline/Libraries` 디렉토리에 존재하고, iOS 빌드를 할 때 복사된다. 만들어진 XCode 프로젝트에서 애플리케이션 타겟 설정을 확인해보면 당연히 이 정적 라이브러리가 Linked Frameworks and Libraies로 추가되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;만들어진 XCode 프로젝트 내부를 보면 이런 저런 소스가 굉장히 많은 것을 알 수 있다. 살짝 더 뒤져보기 위해 `Rotator`라는 간단한 C# 소스 파일을 만들고, 여기에 `Debug.Log` 메소드를 사용하는 메소드를 &amp;nbsp;추가한 뒤 다시 iOS 빌드를 하도록 해봤다. 아래는 그 결과 생성되는 cpp 파일의 내용 중 일부이다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/growingdever/7e53be3fac651638a575d078a1a26d50.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;`Debug_Log_m920475918` 이라는 함수를 사용하는 것이 보이는가? 이 함수는 `Build_UnityEngine_0.cpp` 소스에 있는 함수인데, 이 소스 파일 내용을 보면 Debug 클래스를 비롯한 온갖 클래스에 대한 Wrapping 함수들로 가득한 것을 알 수 있다. 그렇다. 유니티에서 작성한 C# 코드들은 C++ 소스로 변환되면서, 원래 C#에서 호출하려 했던 함수를 C++에서 호출하도록 interpret 된다. 이것이 유니티 엔진의 작동 원리였던 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;어쨌건, 이렇게 프로젝트 세팅이 만들어진 뒤라면 기존의 iOS 애플리케이션 빌드하는 것과 마찬가지로 XCode에서 원하는 빌드 Scheme으로 빌드를 하기만 하면 된다. 아마 General iOS Devices를 대상으로 Release 모드로 빌드하여 나온 애플리케이션을 배포하면 될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;예시3: Cocos2d-x&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Cocos2d-x는 최근에는 어떻게 바뀌었는지 모르겠지만, 예전에 내가 작업할 때(3.x 버전) 기준으로 보면 완전히 XCode 기반의 작업이다. Cocos2d-x에서는 템플릿 프로젝트를 복사해주는 것으로 유저의 프로젝트를 생성해주는데, 그 프로젝트의 구조는 다음과 같다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: square;&quot;&gt;&lt;li&gt;Cocos2d-x 엔진을 위한 XCode 프로젝트가 존재하고, 이 프로젝트는 Cocos2d-x 엔진을 정적 라이브러리로 빌드하는 것을 담당한다.&lt;/li&gt;&lt;li&gt;유저의 프로젝트는 Cocos2d-x 엔진을 위한 XCode 프로젝트를 하위 프로젝트로 두고 Target Dependency를 걸고, Cocos2d-x 엔진의 정적 라이브러리를 유저 프로젝트의 Linked Frameworks and Libraries에 추가한다.&lt;/li&gt;&lt;li&gt;Cocos2d-x 엔진의 소스가 빌드된 적이 없거나 수정되는 경우에는 유저의 프로젝트를 빌드할 때 함께 빌드된다.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;누누히 말해온 것처럼 clang은 C++ 빌드를 문제없이 진행할 수 있기 때문에 이 프로젝트를 iOS SDK 기반으로 컴파일한 결과물을 이용하기만 하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이것으로 각 모바일 OS에서의 빌드 과정과 대표적인 게임 엔진이라 할 수 있는 언리얼, 유니티, Cocos2d-x 엔진에서의 빌드 과정에 대해 설명을 마친다. 모바일 게임을 개발하면서 네이티브 OS에 대한 작업을 하는 것을 피할 수 없지만 게임 개발자들은 대체로 게임 개발 자체만 집중해서 학습을 하다보니 사소한 네이티브 관련 작업도 큰 고통이 되곤 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;시간을 투자해서 자세한 프로세스를 보고 나면 그러한 작업과 네이티브 OS와 직결되는 문제 해결에 큰 도움이 되지만 게임 개발이라는게 워낙 일정에 치이는 경우가 많아서 그런 시간 투자를 쉽사리 할 수 없는 것이 현실이다. 나 역시 이 짧은 글로 완벽히 빌드 프로세스를 전달하는 것은 너무 큰 기대라고 생각하기 때문에 이 글을 바탕으로 앞으로 문제 해결을 할 때 조금의 도움이나마 된다면 충분히 가치있다고 생각한다. 부디 많은 게임 개발자들에게 도움이 되었기를!&lt;/p&gt;</description>
      <category>프로그래밍</category>
      <author>터프 프로그래머</author>
      <guid isPermaLink="true">https://toughrogrammer.tistory.com/240</guid>
      <comments>https://toughrogrammer.tistory.com/240#entry240comment</comments>
      <pubDate>Fri, 20 Jan 2017 12:16:24 +0900</pubDate>
    </item>
    <item>
      <title>S3 Content-Disposition 업데이트, Gevent로 좀 더 빠르게 하기</title>
      <link>https://toughrogrammer.tistory.com/239</link>
      <description>&lt;p&gt;Flask로 개발된 대학교 커뮤니티를 취미삼아 운영중이다. 이전에는 XE로 개발되어 있었는데, Flask로 새로 개발하면서 기존의 글들에 등록되어 있던 첨부 파일들을 이용하지 못하는 문제를 최근에 해결하였다. 그 과정에서 있었던 이야기들을 포스팅해볼까 한다. 혹시나 XE에서 새로운 프레임워크로 직접 개발한 사이트로 마이그레이션하고자 하는 분들에게 참고가 됐으면 한다.&lt;/p&gt;&lt;p&gt;Gevent가 들어가는 부분은 content disposition을 직접 변경하는 요청을 보내는 부분인데, 이 부분은 사실 직접 코딩하지 않아도 awscli를 이용하여 처리할 수 있음을 사전에 알린다. 대신 이 글을 통해 gevent를 이용하여 다수 요청을 병렬적으로 보내는 방법을 소개하고자 한다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;&lt;b&gt;## XE에서의 파일 관리&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;XE에서는 `xe_files` 테이블을 통해 첨부 파일을 관리한다. &amp;nbsp;`uploaded_filename` 컬럼에 업로드 된 파일의 경로가 저장되고, `source_filename` 컬럼에 원본 파일 이름이 저장되며, `direct_download` 컬럼은 따로 다운로드 가능한 파일 목록에 보여줘야 하는지의 여부를 알려주는 것으로 보인다. 이미지 업로드 같은 것도 파일 업로드이므로 `xe_files ` 테이블에 추가되지만 글 내용에서 이미지를 띄워주기 위한 용도로는 그냥 `img` 태그의 `src` 속성에 업로드 된 파일 경로를 넣어주면 바로 이용 가능하므로 직접 다운로드 받는 파일은 `direct_download` 라는 컬럼을 이용하여 구분한 것으로 생각된다.&lt;/p&gt;
&lt;p&gt;나 같은 경우, EC2에 Flask로 개발된 웹 애플리케이션을 배포하는 과정에서 기존 호스팅 사이트에 저장된 XE의 &amp;nbsp;`files` 디렉토리를 통째로 S3에 업로드 해뒀었다. `xe_files` 테이블의 존재는 알고 있었으니 나중에 고치면 될 것이라 생각했기 때문이다. 하지만 내가 놓친 것이 있었는데, 바로 다운로드 되는 파일의 이름 문제였다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;&lt;b&gt;## Anchor tag의 download 속성 이용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;`&amp;lt;a&amp;gt;` tag에 `download` 속성을 이용하면 다운로드 되는 파일 이름을 설정할 수 있다고 w3schools에 나와있길래 시도해봤다. 하지만 이 방법은 작동하지 않았는데, 그 이유는 `download` 속성이 동일한 origin에 대해서만 적용되기 때문이다. S3는 다른 도메인이므로 적용될 수 없는 것이다. 또한, `download` 속성은 사파리에서 작동되지 않는다는 문제도 있다. &lt;b&gt;따라서 이 방법으로의 해결은 불가능하다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;&lt;b&gt;## Response Header 오버라이딩&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;S3에서는 Response Header를 오버라이딩할 수 있는 방법을 제공한다. 예를 들어 `LINK_TO_S3_OBJECT?response-content-disposition=new_name`와 같은 식으로 URL 파라미터를 넘겨주면 해당 값으로 결과를 돌려주는 방식이다. 이 방법에 대한 자세한 내용은 &lt;a href=&quot;http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;공식 문서&lt;/a&gt;를 참고. 하지만, 이 방법 또한 내 상황에서는 해결 방법이 될 수 없었다. 왜냐하면 &lt;b&gt;익명 GET 요청에는 이 방법이 유효하지 않기 때문&lt;/b&gt;이다. 하지만 이와 비슷한 방법으로 Response Header 오버라이딩을 할 수 있다는 것을 알게 되어 다른 해결 방법을 더 찾아볼 수 있었다. 이에 대한 좀 더 자세한 설명을 덧붙인다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;&lt;b&gt;### HTTP 프로토콜을 통한 파일 다운로드와 업로드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;예를 들어 S3에 `./files/attach/binaries/(생략)/cf8a0af2ed6202bab6dfcf0fdd296f30` 라는 경로로 Object가&amp;nbsp;존재한다고 해보자. 이 경로로 접근하면 파일이 다운로드 되는데, `cf8a0af2ed6202bab6dfcf0fdd296f30` 라는 이름의 파일이 다운로드 된다. 당연히 사용자 입장에서는 이상한 파일이 다운로드 되었다고 볼 수 밖에 없는 상황이다. 딱 보면 알겠지만, 다운로드 되는 파일의 key와 동일한 이름으로 파일이 다운로드 된다. 왜냐하면 S3에서는 기본적으로 특별한 Response Header를 주지 않고, 브라우저에서는 파일 이름에 대한 특별한 지시가 없는 경우 해당 URL을 통해 다운로드 될 파일 이름을 결정하게 되기 때문이다.&lt;/p&gt;
&lt;p&gt;다른 파일 다운로드의 예시를 보자. 다음은 학교 홈페이지에 첨부된 파일에 대한 다운로드 요청이다. 브라우저에서 이 URL로 접근하여 파일을 다운로드 받으면 파일 이름이 정상적으로 만들어진다. 통신을 통해 어떤 데이터가 오고가는지 보여주기 위해 `curl`을 이용했다. 명령어에 여러 옵션들은 특별한 것이 없고, HTTP Header를 보여주고 Body를 숨기는 것과 추가적인 HTTP Header에 대한 것들이다. 그냥 요청을 날리면 요청을 거절하길래 `User-Agent`를 추가해줬다.&lt;/p&gt;
&lt;p&gt;&lt;script src=&quot;https://gist.github.com/growingdever/db189a8a1c2c2a0bc9a70fc462e34643.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;마법은 바로 Response의 `Content-Disposition` 헤더가 일으킨다. 사실 특별한 것은 아닌데, 브라우저에서 파일 이름 설정과 관련해서는 `Content-Disposition` 에 명시된 `filename` 값으로 파일 이름을 설정하게 되어 있는 것이다. `Content-Disposition` 헤더는 파일 업로드시에도 비슷한 역할을 한다. 다음은 `file`이라는 이름으로 `IMG_1464.PNG` 파일을 업로드하는 HTTP Request이다. POST 메소드를 이용하여 `multipart/form-data` Content-Type으로 파일을 전송한다.&lt;/p&gt;
&lt;p&gt;&lt;script src=&quot;https://gist.github.com/growingdever/46531ea1606f0e08da83b83cf14094d3.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;마찬가지로 `Content-Disposition` 정보가 해당 파일의 multipart boundary에 &amp;nbsp;함께 주어지는 것을 볼 수 있다. 이를 통해 서버에서는 원본 파일 이름을 알 수 있게 된다. 자세한 것은 &lt;a href=&quot;https://tools.ietf.org/html/rfc6266&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;RFC6266&lt;/a&gt;을 참고하자.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;&lt;b&gt;## Presigned URL 활용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;S3에서 object에 대한 Presigned URL(미리 서명된 URL)를 만들 수 있다. Presigned URL은 해당 URL을 생성할 때 몇 가지 속성을 미리 지정해놓을 수 있다. &lt;a href=&quot;http://boto3.readthedocs.io/en/latest/reference/services/s3.html#S3.Client.generate_presigned_url&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;boto3 문서&lt;/a&gt;에 따르면 `Params`를 통해 이를 지정할 수 있음을 알 수 있고, &lt;a href=&quot;https://github.com/boto/boto3/issues/356&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;github 이슈&lt;/a&gt;에서도 이 방법을 제안하고 있다. 하지만 이유를 알 수 없게도, `ResponseContentDisposition`이나 `ResponseContentType`을 지정하면 `SignatureDoesNotMatch` 에러가 발생하여 &lt;b&gt;문제가 여전히 해결되지 않았다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;## S3 Object에 Metadata로 Content-Disposition 적용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;S3의 Object에 Metadata로 직접 몇 가지 Header 설정이 가능하다. 다음 그림을 참고하자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/26537B4557C7E9420A&quot;&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;출처 : http://interconnection.tistory.com/52&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 방법을 통해 Metadata 설정이 가능하다. 여기서 발견한 S3의 버그(?)가 있는데, 콘솔에서 유니코드를 직접 Metadata로 입력이 불가능하다는 것이다. 콘솔에서 유니코드를 입력하여 저장하면 에러가 발생했고, `boto3` 라이브러리를 이용하여 강제로 유니코드를 입력하였더니 콘솔에서 Properties 메뉴에 에러가 떠서 문제가 발생했다. 따라서, 콘솔에서 에러를 만나지 않으면서 유니코드를 쓰고 싶으면 Percent Encoded 문자열을 써야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 Percent Encoded 문자열을 이용할 때는 `Content-Disposition`에 filename을 추가로 명시해줘야 한다. 크롬에서는 `filename=`으로 명시한 Percent Encoded 문자열을 잘 해석해서 파일 이름을 설정해주지만, 사파리와 같이 다른 브라우저에서는 적용이 안 된다. 그럼 어떻게 해야할까? `filename*=UTF-8''` 뒤에 똑같이 Percent Encoded 문자열을 넣어주면 된다. 다소 지저분해보이지만, S3 콘솔에서 에러를 만나지 않으면서도 여러 브라우저에서 문제 없이 파일 이름이 설정되는 방법이다. 이 방법으로 안드로이드의 몇 가지 브라우저에서도 잘 작동되는 것을 확인했다. 따라서 예시 Content-Disposition은 다음과 같다. `%EC%8B%9C%ED%97%98%EB%AC%B8%EC%A0%9C.hwp`은 `시험문제.hwp`를 Percent Encoding한 것이다.&lt;/p&gt;&lt;div class=&quot;txc-textbox&quot; style=&quot;border-style: solid; border-width: 1px; border-color: rgb(238, 238, 238); background-color: rgb(238, 238, 238); padding: 10px;&quot;&gt;&lt;p&gt;Content-Disposition: attachment; filename=%EC%8B%9C%ED%97%98%EB%AC%B8%EC%A0%9C.hwp; filename*=UTF-8''%EC%8B%9C%ED%97%98%EB%AC%B8%EC%A0%9C.hwp&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;업데이트 해줘야 할 파일이 한 두개도 아니고, 하나하나 손으로 직접 업데이트 해주는 것은 어불성설. 이를 위한 스크립트를 만들기로 했다. `python`에서 `boto3` 라이브러리를 이용하여 S3 Object들을 수정해주는 것이다. 주의할 점은, Object의 Header에서 Metadata를 수정하면 안 된다. 그러면 `x-amz-meta-&amp;lt;name&amp;gt;`에 해당되는 Metadata가 생성된다. 이 말인 즉슨, 해당 Object를 직접 수정하는 것은 안 되고, Copy를 하면서 `copy_object`의 `ContentDisposition` 인자를 활용해야 한다는 것이다. 아래 샘플 코드를 참고하자.&lt;/p&gt;
&lt;p&gt;&lt;script src=&quot;https://gist.github.com/growingdever/f874328e3fb7860f3d18b2b118361683.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;`MetadataDirective` 를 `REPLACE`로 지정해주지 않으면 `An error occurred (InvalidRequest) when calling the CopyObject operation: This copy request is illegal because it is trying to copy an object to itself without changing the object's metadata, storage class, website redirect location or encryption attributes.` 와 같은 에러가 발생한다. 유의하도록 하자.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;&lt;b&gt;## Gevent로 병렬 처리하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위 샘플 코드의 `update_object` 함수로 S3 Object를 업데이트할 수 있다. 하지만, 많은 수의 Object를 수정하려고 할 때 위 함수를 일반적인 for-loop를 통해 실행하면 굉장히 오랜 시간이 걸린다. 왜냐하면 `boto3`의 메소드를 실행하는 것은 하나 하나가 AWS에 대한 네트워크 통신을 하는 `blocking operation`이기 때문이다. 한 요청이 끝날 때까지 매번 기다려야하니 오래 걸릴 수 밖에 없다. 큰 용량의 파일을 다운로드 받는 것과 같은 작업이 아니라면 여러건의 요청을 동시에 보내는 것이 효율적이다.&lt;/p&gt;
&lt;p&gt;`Gevent`를 이용하면 이와 같은 작업을 쉽게 할 수 있다. `Gevent`에 대한 설명은 이 글에서 다루기 어렵고, `Gevent`의 `Pool`을 이용하여 `greenlet`을 Pooling 하는 것이 핵심이다. 보통 `Gevent` 를 이용할 때 `greenlet`을 spawn하여 join하면 각각의 `greenlet`을 실행하게 되는데, 요청을 보내야 할 것이 수천, 수만 개이므로 모든 `greenlet`을 동시에 실행하는건 어렵다. 이를 해결하기 위해 제한된 갯수의 `greenlet`만 실행하도록 하기 위해 `Pool`을 사용한다. 말이 어렵지 코드로는 매우 간단하다. 샘플 코드는 다음과 같다. `Gevent`를 사용하는 코드 외에 CSV 파일로부터 파일 목록을 불러오는 코드도 추가되었다.&lt;/p&gt;
&lt;p&gt;&lt;script src=&quot;https://gist.github.com/growingdever/99caf4ac2cbc0ebcc90c2c999c83f1f1.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;자! 드디어 준비가 끝났다. 카페에서 작업한 것이라 양심이 찔려 Pool의 크기를 조금 작게 잡았다. 이 스크립트를 실행시켜 놓고 잠깐 커피를 즐기고 나니 처리가 끝난 것을 확인할 수 있었다.&lt;/p&gt;</description>
      <category>프로그래밍</category>
      <author>터프 프로그래머</author>
      <guid isPermaLink="true">https://toughrogrammer.tistory.com/239</guid>
      <comments>https://toughrogrammer.tistory.com/239#entry239comment</comments>
      <pubDate>Mon, 2 Jan 2017 17:52:38 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드에서 JNI를 이용하여 C/C++에서 Java 메소드 호출하기</title>
      <link>https://toughrogrammer.tistory.com/237</link>
      <description>&lt;p&gt;최근 cocos2d-x로 작업을 하면서 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;JNI&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`&lt;/span&gt;를 좀 알아봐야 했다. 먼저 글에서는 편의상 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;C/C++&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`&lt;/span&gt;을 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;Native&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`&lt;/span&gt;라고 말함을 알린다. 이에 따라 JNI가 왜 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;Java Native Interface&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`&lt;/span&gt;라고 이름지어졌는지 알 수 있을 것이다. 이 글에서는 Java 자체보다는 안드로이드 환경, 그리고 크로스 플랫폼 환경에 대한 설명에 좀 더 초점을 맞춰 썼다. 실행 환경 자체는 맥과 안드로이드이기 때문에 리눅스에 치중되어 있지만, 공유 라이브러리와 빌드 개념 자체에 대한 이해만 있으면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;앞서 cocos2d-x를 언급한 것에서 눈치챘겠지만, 게임 엔진측 코드(C++)에서 안드로이드 플랫폼에 의존성이 있는 부분(Java)을 처리하기 위해 삽질하면서 정리한 것이다. JNI를 쓰는 방법에 대한&amp;nbsp;자료 조사를 하면서 대충 사용법만 찾는 것은 어렵지 않았다. 하지만 안드로이드 환경에서 JNI라는 기능을 어떻게 사용할 수 있는지, 그리고 이게 실제로는 어떻게 동작되는지에 대한&amp;nbsp;설명이 부족함을 느꼈다.&amp;nbsp;개념 차원에서의 정리를 해두면 많은 분들에게 도움이 되지 않을까 하여 정리해본다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;매우 잘 정리되어 있는 &lt;a href=&quot;http://www.ibm.com/developerworks/java/tutorials/j-jni/j-jni.html&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;영어 문서&lt;/a&gt;를 참고 자료로 보시길 권한다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;## 서론&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여러가지 이유에 의해서 Java에서 Native 함수를 호출하거나, Native에서 Java의 메소드를 호출해야 할 일이 생긴다. 예를 들면 안드로이드에서 기기 정보를 읽어오는 일을 생각해보자. 안드로이드 SDK는 Java로 제공되기 때문에 Native에서 그냥은 Java로 제공되는 API를 호출하여 결과를 얻을 수가 없다. 따라서, Java로 구현된 메소드를 호출할 수 있는 방법이 꼭 필요하다. JNI는 바로 그것을 위한 것이다. 이 정도면 필요성에 대해서는 충분히 전달이 되었으리라 생각하고, 배경 지식에 관련된 부분을 설명하고자 한다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;### JVM 환경&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Java는 모두들 알듯이 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;JVM&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`&lt;/span&gt;이라는 가상환경에서 구동된다. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;javac&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`&lt;/span&gt;라는 컴파일러를 통해 Java 코드를 컴파일하여 바이트 코드를 만들고, 해당 바이트 코드는 JVM 위에서 실행된다. 이해를 돕고자 다른 예를 들자면, &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`javac`&lt;/span&gt;로 Java 코드를 컴파일하고 나면 어떤 어셈블리 코드(바이트 코드)가 만들어지고, 그 어셈블리 코드를 실행해주는 컴퓨터, OS(JVM)가 또 따로 존재한다고 생각하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;어쨌든 컴파일 된 바이트 코드는 실행할 때 JVM에 어떤 방식으로든 로드되어 있다. 사실 이것은 OS에서 프로세스를 실행시킬 때의 이야기와 비슷하다. 흔히 프로세스를 실행하면 OS가 프로세스를 위해 코드 영역, 데이터 영역, 힙 영역, 스택 영역을 할당해준다고 알려져있다. 코드 영역에 실행하고자 하는 명령어(Instruction)들이 쓰여 있는 것이고, 어떤 코드를 실행할 때는 해당 코드가 존재하는 메모리 영역의 데이터를 CPU로 불러와 실행하는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 부분을 현재 문제 상황에 맞게 간략화하면 다음과 같다: &lt;b&gt;어떤 함수를 실행하기 위해서는 원하는 함수가 존재하는 위치를 찾고, 그 위치에 존재하는 명령어들을 실행하면 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;### 심볼 테이블과 타입 시그니쳐(Type Signature), 네임 맨글링(Name Mangling)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;컴파일러에 의해 소스 코드가 컴파일되면서 &lt;b&gt;심볼 테이블&lt;/b&gt;이 만들어진다. 심볼 테이블은 심볼로 이뤄진 테이블인데, 클래스 이름, 함수 이름, 변수 이름 등이 심볼에 해당된다. 예를 들어 다음과 같은 코드가 있다고 하자.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/growingdever/a5bc3005f21d8dc2246de6d180ccd8c9.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`Helloer`, `say(std::string)`, `say(std::string, std::string)`, `myName`, `firstName`, `lastName`&lt;/span&gt; 등이 모두 심볼에 해당한다. C++은 함수 오버로딩을 지원하는 언어이기 때문에, 심볼 테이블을 만들 때 함수 이름이 같더라도 인자 타입에 따라 다른 함수로 처리해준다. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`say(std::string)`&lt;/span&gt; 메소드와 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`say(std::string, std::string)`&lt;/span&gt; 메소드는 메소드 이름이 같을 뿐, 완전히 다른 메소드다. 코드에서 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`say(std::string)`&lt;/span&gt; 메소드를 호출하게끔 했는데 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`say(std::string, std::string)`&lt;/span&gt; 메소드가 호출되면 잘못된 실행이지 않은가? 어셈블리 레벨에서 간단하게 생각하면 함수를 호출한다는 것은 다음 실행할 명령어 위치를 다른 위치로 바꾸는 것인데, 심볼 테이블을 통해 그 위치를 정하는 것이다. 따라서 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`say(“Loki”, “Jung”)`&lt;/span&gt;과 같이 호출할 경우 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`say(std::string, std::string)`&lt;/span&gt;에 대한 심볼로 테이블에서 주소를 찾고, &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`say(“Loki Jung”)`&lt;/span&gt;과 같이 호출할 경우 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`say(std::string)`&lt;/span&gt;에 대한 심볼로 테이블에서 주소를 찾아야 한다. 이렇게 심볼의 주소를 찾아 연결해주는 것을 &lt;b&gt;링킹&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이렇게 타입과 인자 갯수에 따라 다르게 보면서 타입 시그니쳐(Type Signature)라는 것을 곁들여서 생각하면 좋다. 사실 변수명 같은건 이미 심볼 테이블에 정리되었으므로 중요한건 어떤 타입이 쓰이는지이다. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`say(std::string)`&lt;/span&gt; 메소드를 시그니쳐로 풀어보면 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`(std::string)(std::string);`&lt;/span&gt; 이고, &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`say(std::string, std::string)`&lt;/span&gt;을 풀면 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`(std::string)(std::string, std::string)`&lt;/span&gt;이다. 딱 보면 알겠지만 그냥 변수명을 빼고 타입에 대한 정보만 남겨놓은 것이다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;네임 맨글링 작업은 쉽게 생각했을 때, 시그니쳐, 네임 스페이스 등을 풀어서 심볼을 유니크하게 만드는 것이다. &lt;a href=&quot;https://en.wikipedia.org/wiki/Name_mangling&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;위키피디아 예제&lt;/a&gt;에 너무 잘 설명되어 있어서 예제를 위키피디아의 내용으로 잠깐 바꿔보자. wikipedia 네임 스페이스의 article 클래스의 print_to 메소드는 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`_ZN9wikipedia7article8print_toERSo`&lt;/span&gt;라는 이름의 메소드가 된다. 컴파일 후 만들어지는 오브젝트 코드에서는 이런 풀어헤쳐진 이름으로 바뀐 코드가 되어 있고, 링킹 과정을 거치면서 심볼 테이블에서 적절한 주소값으로 대체된다고 보면 컴파일 후 링킹하는 과정을 대충 설명한 것이다. 이것에 대해서 자세히 설명하려면 너무 글이 글어지기 때문에 이만 줄이겠다. (이미 길어진 것 같지만)&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;위 예제는 C++ 얘기였다. 하지만 개념 자체는 Java에서도 비슷하게 적용된다. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`String String.substring(int, int)`&lt;/span&gt; 메소드는 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`Ljava/lang/String/substring(II)Ljava/lang/String;`&lt;/span&gt; 으로 풀어헤쳐져서 바이트코드에 명시될 것이다. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`Ljava/lang/String/substring`&lt;/span&gt; : java.lang 패키지의 String 클래스의 substring 메소드는 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`(II)`&lt;/span&gt; : int 타입 인자 2개를 받고, &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`Ljava/lang/String;`&lt;/span&gt; : java.lang 패키지의 String 타입을 리턴한다는 의미이다. 이 부분을 알아야 하는 이유는, 나중에 어떤 클래스의 메소드를 찾아올 때 이 심볼 정보를 통해 찾아오기 때문이다. JVM에서는 이러한 심볼 테이블을 따로 저장해두고 있기 때문에 런타임에 심볼 정보를 통해 동적으로 타입을 찾을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 232px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/24653E3D57B86D8D18&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F24653E3D57B86D8D18&quot; width=&quot;232&quot; height=&quot;219&quot; filename=&quot;jni-fig-01.gif&quot; filemime=&quot;image/gif&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;### 공유 라이브러리&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;윈도우에서는 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`.dll`&lt;/span&gt;, 리눅스에서는 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`.so`&lt;/span&gt; 파일 익스텐션으로 익숙한 그것이다. 작동 원리는 정적 라이브러리와 링킹 과정에서 차이가 있다. 자세한 것은 따로 찾아보고, 중요한 것은 프로그램이 실행될 때 공유 라이브러리와 결합된다는 것이다. 공유 라이브러리는 프로그램 실행시 결합이 일어나기 때문에 미리 로드시켜놔야 한다. Java에서 익숙한 jar 파일도 Java 프로그램이 실행되면서 미리 로드 시켜놓고, &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`.so`&lt;/span&gt; 파일도 미리 로드되는 등, 방식이 비슷하다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;안드로이드 프로젝트를 생각해보자. 다들 사용하고자 하는 라이브러리들(jar)을 함께 패키징하여 빌드한 경험이 있을 것이다. 앞서 설명한 것과 같이, Java 프로그램은 실행하면서 jar를 함께 JVM에 올려둔다. 이렇게 JVM에 올리면서 라이브러리의 심볼 테이블도 함께 메모리에 올라가는 것이다. 그렇기 때문에 JNI를 통해 클래스나 메소드를 찾을 때 jar에 담긴 라이브러리의 클래스나 메소드를 찾는다고 해서 특별히 다른 방법이 요구되지 않는다. 자세한 찾는 방법을 알 필요 없이, JVM이나 JNI에서 제공해주는 인터페이스를 이용하여 사용하기만 하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;앞서 언급한 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`.so`&lt;/span&gt; 파일과 같은 공유 라이브러리를 JVM에 올려주는 기능도 제공된다. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`System.loadLibrary`&lt;/span&gt; 메소드가 바로 그것이다. 이 메소드의 인자로 적절한 공유 라이브러리 파일 경로를 넣어주면 JVM이 메모리에 잘 올려준다. JNI 기반으로 잘 컴파일된 공유 라이브러리라면 JNIExport 되어 있는 메소드는 자동으로 Java의 메소드에 맵핑까지 해준다. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`public native int intMethod(int n)`&lt;/span&gt; 과 같은 메소드 프로토타입 선언을 해두면 로드한 공유 라이브러리에서 형식이 맞는 함수를 찾아서 맵핑해준다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;### 안드로이드 NDK와 JNI&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;안드로이드는 결국 리눅스 기반의 OS이다. 권한에 의해 불가능한 일들을 제외하고는 데스크톱 리눅스 환경에서 할 수 있는 일은 할 수 있다. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`adb shell`&lt;/span&gt; 명령어를 실행해보면 안드로이드 기기에서의 shell이 열리는 것을 확인할 수 있다. 간략하게 생각해보면 이러한 안드로이드 OS 위에서 JVM을 구동하고, JVM을 이용하여 일반적으로 개발하는 Java 언어로 작성된 애플리케이션을 실행시켜주는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;하지만 여기서 유념해둬야 할 것이 있다. 윈도우 환경을 대상으로 만들어진 실행 프로그램은 당연히 리눅스 환경에서 실행이 안 된다. CPU x86 아키텍쳐를 대상으로 컴파일된 코드는 ARM 아키텍쳐에서 그냥 실행되지 않는다. 마찬가지로 윈도우, 맥 환경을 대상으로 컴파일 된 공유 라이브러리는 안드로이드 환경에서 실행이 안 되거나, 안 될 가능성을 갖고 있다. 그리고 안드로이드에는 많은 API 버전이 존재한다. 복잡하다. 이를 해결하기 위해 NDK라는 툴셋이 제공된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;좀 더 자세히 알아보자. 일단 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`Application Binary Interface`&lt;/span&gt; 라는 것이 있다. 안드로이드는 여러가지 CPU 아키텍쳐 위에서 구동될 수 있게 디자인되어 있다. 따라서 다른 CPU 아키텍쳐에서는 다른 명령어 셋으로 이뤄진 어셈블리 코드로 빌드되어야 한다. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`armeabi`, `armeabi-v7a`, `arm64-v8a`, `x86`, `x86_64`, `mips`, `mips64`&lt;/span&gt;와 같이 정말 많은 아키텍쳐가 존재한다. (사실 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`armeabi`, `armeabi-v7a`&lt;/span&gt;이 대부분이긴 하다.) NDK를 이용하여 빌드하면 이 부분에 있어서 도움을 받을 수 있다. NDK 빌드시 참조되는 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`Android.mk`&lt;/span&gt; 파일에서 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`TARGET_ARCH_ABI`&lt;/span&gt; 변수에 어떤 ABI를 지원할 것인지 써주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;안드로이드에는 OS 버전이라는 개념도 존재한다. 안드로이드 OS 버전에 따라서 어떤 기능은 존재하고, 어떤 기능은 존재하지 않기 때문에 이에 대한 처리가 필요하다. Target SDK 버전을 통해 빌드될 때 자동으로 처리되는데, 보통 가장 최신의 버전을 쓰면 빌드할 때 안드로이드 SDK에 의해서 자동으로 처리되지만, 프로젝트에서 이에 대한 명시는 꼭 필요하다. JNI도 마찬가지로 안드로이드 버전 문제가 있다. 당장 차이를 확인하고 싶다면 NDK 디렉토리에서 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`platforms`&lt;/span&gt; 디렉토리를 확인해보자. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`android-9`&lt;/span&gt;에서는 GLES3 헤더와 라이브러리가 없지만 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`android-24`&lt;/span&gt;에서는 존재한다. 이에 대한 처리도 NDK를 통해 빌드할 때 자동으로 처리할 수 있다. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`TARGET_PLATFORM`&lt;/span&gt; 변수에 그냥 명시해주면 된다. 이제는 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`android-24`&lt;/span&gt; 정도로 써주면 될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기서 빌드 과정에 대해 조금만 더 언급하고 넘어가보자. NDK로 Native 빌드를 할 때 어떤 컴파일러가 쓰일까? NDK의 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`toolchains`&lt;/span&gt; 디렉토리를 보면 몇 가지 플랫폼에 따른 prebuilt 파일들이 존재한다. 아마 맥에서는 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`llvm`&lt;/span&gt; 디렉토리에 있는 것들이 쓰일 것이다. 우리가 Native 코드를 작성하면서 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`#include &amp;lt;jni.h&amp;gt;`&lt;/span&gt; directive를 쓰면 어떻게 JNI 라이브러리를 쓸 수 있게 되는 걸까? &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`platforms/android-*/arch-arm/usr/include`&lt;/span&gt; 디렉토리리 내에&amp;nbsp;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`jni.h`&lt;/span&gt; 파일이 존재한다. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`TARGET_PLATFORM`&lt;/span&gt;에 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`android-24`&lt;/span&gt;, &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`TARGET_ARCH_ABI`&lt;/span&gt;를 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`armeabi`&lt;/span&gt;로 명시했으면 NDK 빌드를 할 때 컴파일러에게 알려주는 헤더 파일 탐색 경로상에 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`${NDK_ROOT_PATH}/platforms/android-24/arch-arm/usr/include`&lt;/span&gt;가 추가되고, pcc에 의해 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`jni.h`&lt;/span&gt; 헤더 파일을 잘 처리될 수 있는 것이다. 그리고 아마 빌드 과정에서 포함되는 라이브러리의 오브젝트 코드와 우리의 코드가 링킹되면서 우리가 작성한 JNI 코드가 정상 작동 될 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 18pt;&quot;&gt;## 실제 작업&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;배경 지식들을 소개하느라 서론이 길었다. 이제 실제로 안드로이드에서 Native측에서 Java의 메소드를 호출하는 방법에 대해 알아보자. 배경 지식 설명에 비해 이 부분은 너무 간단해서 당황스러울지도 모른다. 로직은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;1. 어떻게든 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`JNIEnv`&lt;/span&gt; 객체를 찾는다. 이 부분을 이렇게 밖에 설명할 수 없는게 어떤 환경이느냐에 따라서 좀 복잡하게 숨겨져 있다. 일단 원칙적으로는 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`JNI_OnLoad`&lt;/span&gt; 함수를 통해 안드로이드로부터 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`JavaVM`&lt;/span&gt; 객체를 전달받는다. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`JavaVM`&lt;/span&gt; 클래스에는 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`GetEnv`&lt;/span&gt; 메소드가 있는데, 이 녀석으로 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`JNIEnv`&lt;/span&gt; 객체를 얻을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;2. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`FindClass`&lt;/span&gt; 함수에 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`JNIEnv`&lt;/span&gt;와 Java 클래스 path를 넣어주고 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`jclass`&lt;/span&gt; 변수를 얻는다. 즉, 어떤 JVM의 메모리에 올라와있는 심볼 테이블에서 Java 클래스 path에 해당하는 심볼을 찾아오는 것이다. 예를 들어 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`org.cocos2dx.lib`&lt;/span&gt; 패키지의 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`Cocos2dxDownloader`&lt;/span&gt; 클래스의 메소드를 사용하고 싶다면 Java 클래스 path는 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`org/cocos2dx/lib/Cocos2dxDownloader`&lt;/span&gt; 이다. 라이브러리의 클래스, 즉, jar&amp;nbsp;내에 존재하는 클래스도 상관없다. 안드로이드 빌드를 할 때 해당 jar를 잘 로드하게끔 해두면 된다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;3. 메소드 정보를 찾는다. static 메소드를 호출하려는 경우 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`GetStaticMethodID`&lt;/span&gt; 함수, 인스턴스 메소드를 호출할려는 경우 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`GetMethodID`&lt;/span&gt; 함수로 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`jmethodID`&lt;/span&gt;를 얻어온다. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`JNIEnv`&lt;/span&gt;와 아까 찾아왔던 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`jclass`&lt;/span&gt;, 사용하고자 하는 메소드의 이름, 해당 메소드의 시그니쳐를 넣어주면 된다. 시그니쳐 인코딩은 위 배경지식 부분에서 설명한 규칙을 참고하자.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;4. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`CallObjectMethod`&lt;/span&gt;류 메소드로 실제 메소드 호출을 한다. static 메소드면 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`CallStaticObjectMethod`&lt;/span&gt;류 함수이다. &quot;류&quot;라는 단어를 붙인 이유는 리턴 타입에 따라 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`Call(Static)BooleanMethod`&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;, `Call(Static)VoidMethod`&lt;/span&gt;와 같이 함수가 여러가지 존재하기 때문이다. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`Call(Static)VoidMethod`&lt;/span&gt; 함수를 제외하고는 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`jobject`, `jboolean`, `jbyte`, `jchar`, `jshort`, `jint`, `jlong`, `jfloat`, `jdouble`&lt;/span&gt;를 리턴한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;5. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`Call(Static)ObjectMethod`&lt;/span&gt; 함수로 Java 메소드 호출 결과를 받아온 경우, &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`jobject`&lt;/span&gt;를 얻게 된다. 기본적으로 이는 로컬 레퍼런스이다. 즉, 찾아온 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`jobject`&lt;/span&gt;를 어딘가에 저장해두고 나중에 또 쓸 수가 없다. (운이 좋으면 될 수도 있지만 기본적으로 안 된다고 생각해야 한다.) 나중에 또 쓰고 싶으면 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`NewGlobalRef`&lt;/span&gt; 함수를 호출하여 리턴되는 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`jobject`&lt;/span&gt; 글로벌 레퍼런스를 사용해야 한다. 기본적으로 로컬 레퍼런스는 자동으로 메모리가 해제되지만 글로벌 레퍼런스는 자동으로 해제되지 않으니, 메모리 릭이 생기지&amp;nbsp;않도록&amp;nbsp;나중에 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`DeleteGlobalRef`&lt;/span&gt; 함수를 호출하여 메모리 해제를 해줘야 한다. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`Call(Static)ObjectMethod`&lt;/span&gt; 함수를 반복문 안에서 호출하는 경우에는 로컬 레퍼런스에 대한 메모리가 너무 많아질 수도 있으니 경우에 따라 알아서 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`DeleteLocalRef`&lt;/span&gt; 함수를 호출하여 메모리 해제를 잘 해주자.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;6. &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`FindClass`&lt;/span&gt; 함수의 결과로 얻은 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`jclass`&lt;/span&gt;도 사실은 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`jobject`&lt;/span&gt;이다. (&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`jni.h`&lt;/span&gt;에 typedef 되어 있다.) &amp;nbsp;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`jclass`&lt;/span&gt;를 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`DeleteLocalRef`&lt;/span&gt;로 메모리를 해제한 뒤에는 해당 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`jclass`&lt;/span&gt;를 이용하여 메소드 호출이 불가능하다. JNI 예제를 찾아보면 정말 열심히 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`DeleteLocalRef`&lt;/span&gt; 함수를 호출해주는데, 자동으로 메모리 해제가 되기 전에 메모리가 부족해질 경우로 많이 쓰이는 경우는 사실상 거의 없다고 봐야하지 않나 싶다. 나 같은 경우엔 꼭 필요한 때를 제외하고는 직접 &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;`DeleteLocalRef`&lt;/span&gt;를 호출해주지 않는다. 이 부분은 내 자의적 판단이 들어간 것이니 각자 알아서 잘 판단하도록 하자.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;## 결론&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;원하는대로 작동하기는 하는 코드를 만들어 문제를 해결한 뒤에도 뭔가 마음이 많이 쓰였다. 빌드 과정을 곁들여 개념적으로 JNI를 설명한 글이 없다보니 내가 만든 코드가 정확히 어떻게 돌아가는지에 대해 확신을 갖지 못해서 그랬던 것 같다. 우연히 크로스 플랫폼 작업을 집중적으로 많이 하다보니 빌드 과정에 대해서도 많이 신경써야 했고, 그러다보니 NDK 빌드 과정에 대해서도 좀 더 생각해봐야만 했다. 그렇게 삽질을 통해 나름대로 내린 개념적 결론이 생겼는데, 이걸 그냥 생각만 해두기엔 좀 아까워서 포스팅으로 남겨둬야겠다고 결심했다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;사실 빌드라는 것을 생각 없이 그냥 하는 경우가 많은데, 워낙 툴들이 잘 만들어져 있다보니 그 과정에 대해 자세히 알지 못하게 되는 것 같다. 이 부분을 알아가면서 코드를 짜니 좀 더 내 코드에 대한 확신을 가질 수 있는 것 같다. 컴파일러 수업을 들었던게 이렇게 도움이 되는구나.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>프로그래밍</category>
      <author>터프 프로그래머</author>
      <guid isPermaLink="true">https://toughrogrammer.tistory.com/237</guid>
      <comments>https://toughrogrammer.tistory.com/237#entry237comment</comments>
      <pubDate>Sun, 21 Aug 2016 00:01:33 +0900</pubDate>
    </item>
    <item>
      <title>libpng unhandled critical chunk 에러 해결</title>
      <link>https://toughrogrammer.tistory.com/236</link>
      <description>&lt;p&gt;`&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;libpng error: CgBI: unhandled critical chunk&lt;/span&gt;`&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;iOS에서&amp;nbsp;libpng 라이브러리를 사용할 때,&amp;nbsp;위와 같은 에러가 뜨면서 텍스쳐를 잘 불러오지 못하는 문제가 있다. 특이한 것은 시뮬레이터에서는 문제가 없고, 실기기에서만 에러가 발생한다는 점이다. 해결 방법은 Xcode Build Settings에서&amp;nbsp;&quot;Remove Text Metadata From PNG Files&quot; 옵션을 &quot;No&quot;로 변경하면 된다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;아마 libpng 라이브러리를 사용할 때 시뮬레이터에서는 OSX의 libpng를 바로 사용해서 위 옵션 여부가 영향을 주지 않지만, 실기기에서는 영향을 받아서 그런 것으로 추측된다.&lt;/p&gt;</description>
      <category>프로그래밍/게임 개발</category>
      <author>터프 프로그래머</author>
      <guid isPermaLink="true">https://toughrogrammer.tistory.com/236</guid>
      <comments>https://toughrogrammer.tistory.com/236#entry236comment</comments>
      <pubDate>Mon, 1 Aug 2016 18:57:06 +0900</pubDate>
    </item>
    <item>
      <title>Flask-RESTful Output Fields 기능을 활용하여 마샬링 로직 분리하기</title>
      <link>https://toughrogrammer.tistory.com/234</link>
      <description>&lt;p&gt;Flask-RESTful 라이브러리에는 Output Fields 라는 기능을 제공하여 response를 만드는 것에 대한 편의를 제공하고 있다. API의 response로는 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;application/json&lt;/span&gt;`포맷을 자주 사용한다.&amp;nbsp;일단 flask의 기능을 사용한다고 하면, dictionary를 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;jsonify&lt;/span&gt;` 함수의 인자로 넣어주면 적절한 response 객체가 만들어지긴 한다. (참고:&amp;nbsp;Flask-RESTful Resource에서는 그냥 dictionary를 return 해도 json 포맷으로 response를 준다.) 그러나 출력하고자 하는 것을 dictionary로 만드는 것부터가 일단 일이다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;flask 환경에서는 SQLAlchemy를 이용하는 경우가 많다. 이 때, SQLAlchemy를 어떻게 사용하느냐에 따라 모델 클래스를 얻기도 하고, SQLAlchemy의 커스텀 클래스를 얻기도 한다. 일반적인 사용 방법인 모델 클래스에 바로 query 함수를 호출하여 데이터를 로드하는 경우에는 해당 모델 클래스의 인스턴스를 얻고, `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;with_entities&lt;/span&gt;`와 같은 메소드를 쓰는 경우에는 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;sqlalchemy.util._collections.result&lt;/span&gt;` 타입의 인스턴스를 얻게 된다. 후자의 경우엔 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;keys&lt;/span&gt;` 같은 편리한 메소드가 있어서 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;dict(zip(data.keys(), data))&lt;/span&gt;`와 같이 호출하면 dictionary가 잘 만들어지긴 한다. 하지만 일반적인 클래스(SQLAlchemy 모델 클래스 등)의 경우 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;keys&lt;/span&gt;` 메소드 같은 것이 없기 때문에&amp;nbsp;dictionary를 쉽게 만들어내기&amp;nbsp;어렵다.&amp;nbsp;따라서 SQLAlchemy 모델 클래스를 상속받은, serialize를 쉽게 해주도록 다듬은&amp;nbsp;클래스를 쓰도록 한다던가 해야 한다. 말이 길어졌는데, 하여튼 귀찮은 일들을 해야한다. 이 정도까지만 언급해도 뭔가 좋은 해법이 필요하다는 설득이 어느 정도 전달되었으리라 믿는다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&quot;출력에 대한 부분만 깔끔하게 별도 로직으로 분리시키고 싶다.&quot;&lt;/b&gt; 이 부분을&amp;nbsp;나름대로 깔끔하게 해소해주는 기능이 바로 Output Fields 이다. 일단 &lt;b&gt;&lt;a href=&quot;http://flask-restful-cn.readthedocs.io/en/0.3.5/fields.html&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;공식 문서&lt;/a&gt;&lt;/b&gt;를 통해 간단한 사용법은 알 수 있지만, 공식 문서는 상당히 설명이 빈약함을 매번 느낀다. 영어라서 진입 장벽을 느끼는 분들도 계시고,&amp;nbsp;몇 몇 유용한 기능들을 눈에 잘 보이지 않게 써놔서 놓치는 부분도 있고 해서 이번 기회에 몇 가지를 언급한 가이드를 써봐야겠다고 생각하였다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;기본적인 API를 먼저 살펴보자. 예제들은 일단 모두 하드코딩 된 데이터지만, 이 글을 끝까지 읽어보면&amp;nbsp;SQLAlchemy를 이용하여 데이터베이스에서 로드한&amp;nbsp;데이터여도 문제가 없음을 알 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/growingdever/dfcbdfa1a784e56dd913ae85203f15fd.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;게시글 목록을 조회하는 매우 간단한 API이다. 이 형태에서 조금씩 변화를 줘보자. 만약 IP 주소 전체가 보이는 것이 부담스러워 중간의 일부를 `*` 기호로 변경하여 보여주고 싶다고 해보자. 이 정도 쯤은 직접 API 함수에서 해줄 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/growingdever/e4403221ca23d69037fd009e26da1dbb.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;ip attribute를 얻어와 직접 수정하는 코드를 추가하였다. 하지만 댓글 목록 조회 API에서도 이런 식으로 추가 처리를 해줘야 한다면? 이는 분명히 중복을 일으킨다. 게다가 일단 나 같은 경우&amp;nbsp;저런 코드를 API 코드에 함께 둔다는 사실 자체가 맘에 들지 않는다! 이 때 Flask-RESTful의 Output Fields를 쓸 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/growingdever/a11a347bafdd8c98022b1c8a83cbb1ca.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;코드가 갑자기 좀 많아져서 약간 설명을 부연한다. 기본적인 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;fields.Integer&lt;/span&gt;`, `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;fields.String&lt;/span&gt;` 같은 경우 별 설명이 없어도 쉽게 이해될텐데, 자신에게 주어진 객체로부터 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;attribute&lt;/span&gt;`에 해당되는 이름의 필드를 찾아오고,&amp;nbsp;`&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;default&lt;/span&gt;` 값을 설정해둘 수도 있다.&amp;nbsp;`&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;fields.Nested&lt;/span&gt;` 이 녀석이 매우 특이한 녀석이다. depth가 더 깊어지게 되는 경우에 활용될 수 있는 필드 클래스인데, 특정 객체 속에서의 필드를 찾을 때나, 위 예제에서처럼 리스트 형태로 결과를 마샬링할 때 유용하게 쓰인다. 특이한 것은 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;fields.Nested&lt;/span&gt;`의 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;attribute&lt;/span&gt;`를 지정해주지 않는 경우, 자신에게 할당된 필드명으로부터 자동으로 객체를 찾은 뒤, 그것을 사용한다는 것이다. 그 차이를 보여주는 예가 바로 다음의 예이다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/growingdever/34740fbd4543463261183b83f9418ec3.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;API 함수에서는 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;item-list&lt;/span&gt;` 라는 필드명으로 객체를 넘겨줬다. 하지만&amp;nbsp;`&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;fields.Nested&lt;/span&gt;`은 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;items&lt;/span&gt;`라는 이름의 필드로 쓰이고 있기 때문에, 정상적으로 객체를 찾아오지 못한다.&amp;nbsp;이를 올바르게&amp;nbsp;찾아 쓸 수 있도록 하기 위해 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;attribute&lt;/span&gt;`에 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;item-list&lt;/span&gt;`&amp;nbsp;라고 명시를 해주었다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기서 멈출 수 없다. 왜 굳이 리스트를 dictionary로 만들어서 반환해줘야 하는가? 그냥 리스트를 넘겨주면 자동으로 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;items&lt;/span&gt;`는 필드명으로 넣어서 response를 만들어주면 편하지 않을까? 당연히 제공되는 기능이다. `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;marshal_with&lt;/span&gt;`에서 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;envelop&lt;/span&gt;`를 명시해주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/growingdever/8798542471d170be7e74dd154259f1a4.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;`&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;document_list_fields&lt;/span&gt;`의 내용물이 이전의 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;fields.Nested&lt;/span&gt;`의 안의 것과 같이 된 것을 알 수 있다. `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;envelop&lt;/span&gt;`을 명시하면 API 함수의 return 값이 리스트일 경우 각 리스트의 내용물을 주어진 marshal rule에 따라 마샬링한 후 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;envelop&lt;/span&gt;` 이라는 이름을 가지는&amp;nbsp;리스트 타입의 필드로 추가한다. 리스트가 아닌 경우에는 그냥 json 을 추가한 형태가 된다. (차이를 확인해보려면 API 함수의 return 값을 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;items[0]&lt;/span&gt;`으로 바꿔보라.)&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;더 나아가보자. 각 게시글에 글쓴이 필드를 추가한다고 생각해보자. 이 때 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;fields.Nested&lt;/span&gt;`를 쓰고 싶지 않다면? `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;attribute&lt;/span&gt;`에 &lt;b&gt;dot notation&lt;/b&gt;을 활용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/growingdever/f13431ce0d959f5e10c8110d062369bc.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;주어진 객체에서 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;writer&lt;/span&gt;` 객체를 찾고, 그 객체의 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;id&lt;/span&gt;` 값을 가져오기 위해 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;attribute&lt;/span&gt;`를 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;writer.id&lt;/span&gt;`로 명시한 것을 확인할 수 있을 것이다. 사실 위 예제로는 왜 굳이 이런식으로 달리 표현해야 하는지 알기 어렵다. 이 방법이 꼭 필요한 경우는 내부 객체에서 외부 객체의 값을 참조해야 하는 경우이다. 만약 게시글이 익명 모드일 경우 이름을 별도로 변경해줘야 한다고 생각해보자. 이 때는 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;fields.Nested&lt;/span&gt;`로 내부 객체에 들어간 상태에서 외부 객체의 값을 참조할 수가 없다. 이 문제를 해결한 예는 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/growingdever/436a2782daf92b52be71271f6924cf90.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;`&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;attribute&lt;/span&gt;`에는 &lt;b&gt;lambda 함수&lt;/b&gt;를 쓸 수도 있다. lambda 함수에서 외부 객체(게시글)의 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;is_anonymous&lt;/span&gt;` 값을 확인하여 글쓴이의 이름을&amp;nbsp;`&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;anonymous&lt;/span&gt;`로, `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;id&lt;/span&gt;`는 기본 값으로&amp;nbsp;변경하여 출력하도록 하였다. 필드 클래스를 구현하기에는 간단한 것일 때, 이렇게 lambda 함수를 쓸 수 있다. 하지만 lambda 함수에서는 복잡한 코드를 넣을 수 없기 때문에 한계가 존재한다. 따라서 필요한 경우에는 필드 클래스를 구현해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;현재 주어진 객체에서의 적절한 출력값을 만들어내기 좋은 방법은 커스텀 필드 클래스의 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;output&lt;/span&gt;` 메소드를 활용하는 것이다. 어떤 필드명으로 쓰였는지와 주어진 객체가 무엇인지를 함수 인자로 받을 수 있다. 위에서 lambda 함수로 구현한 것을 커스텀 필드 클래스를 활용한 방법으로 다시 구현한 예가 아래의 코드이다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/growingdever/790187793b9dc36c1b9df0ca8e7b5550.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;`&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;fields.get_value&lt;/span&gt;`라는 새로운 함수를 사용하고 있다. 이는 문서에서 알려주지 않는 유용한 함수 중 하나인데, dot notation을 이용하여 내부 객체를 쉽게 찾아 쓸 수 있도록 해준다. (실제로 직접 만들기에는 귀찮은 기능이다.) `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;writer&lt;/span&gt;` 필드에는&amp;nbsp;`&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;fields.Nested&lt;/span&gt;`를 쓸 수 없다. `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;fields.Nested&lt;/span&gt;`를 쓰면 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;AnonymousFields.output&lt;/span&gt;` 함수에서 전달받은 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;obj&lt;/span&gt;`에서 외부 객체의 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;is_anonymous&lt;/span&gt;`를 참조할 수 없기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;`&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;fields.get_value&lt;/span&gt;`가 정말 유용한 또 다른 이유는 바로 dictionary 뿐만 아니라 다양한 형태의 객체에 사용할 수 있다는 점이다. 현재 API 함수에서는 그냥 dictionary를 넘겨줬기 때문에 일반적인 dictionary 사용 방법으로 값을 찾아 썼다. 하지만,&amp;nbsp;클래스일 경우에는 그렇게 하면 당연히 에러가 난다. 나 같은 경우 API 함수에서는 그냥 SQLAlchemy 모델 클래스를 찾아서 return 시켜주면 그냥 잘 마샬링이 되길 원했다.&amp;nbsp;그렇다고 클래스일 때랑 dictionary일 때를 구분하도록 추가적인 코드를 쓰는 것도 보기 좋지 않고.... `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;fields.get_value&lt;/span&gt;` 함수를 쓰면 이 문제가 그냥 해결된다. 다음의 코드가&amp;nbsp;그 예이다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/growingdever/46a1f4192a65293b4de91a3188afd7a2.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;저 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;Document&lt;/span&gt;`, `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;User&lt;/span&gt;` 클래스가 SQLAlchemy 모델 클래스여도 별 문제 없이 동작한다. 클래스는 subscriptable 객체가 아니라서 대괄호와 key를 이용한 접근시 에러가 난다. 따라서 이전의 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;is_anonymous&lt;/span&gt;` 값을 참조하던 부분도 `&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;fields.get_value&lt;/span&gt;` 함수로 대체해줘야 한다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Flask-RESTful의 Output Fields를 이용함으로써&amp;nbsp;출력에 대한 로직을 분리하는 예를 살펴보았다. 개인적으로 SQLAlchemy의 relationship과 위에서 소개한 Flask-RESTful의 Output Fields 기능을 이용함으로써 API들의 코드가 훨씬 더 간결해져서 나름 만족스러웠다. API 함수와의&amp;nbsp;로직 분리를 얼마나 할 것인가는 각자 판단의 몫이니 잘 생각해야 할 것이다.&amp;nbsp;이 글을 통해 위 기능의 유용함을 많은 이들이 쉽게 사용할 수 있게 되길 바란다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>프로그래밍/서버</category>
      <author>터프 프로그래머</author>
      <guid isPermaLink="true">https://toughrogrammer.tistory.com/234</guid>
      <comments>https://toughrogrammer.tistory.com/234#entry234comment</comments>
      <pubDate>Sun, 19 Jun 2016 15:58:02 +0900</pubDate>
    </item>
    <item>
      <title>Flask 웹 앱과 Flask-OAuthlib를 이용하여 구현한 OAuth 2.0 서버 연동 및 토큰 갱신 방법</title>
      <link>https://toughrogrammer.tistory.com/233</link>
      <description>&lt;p&gt;지금 개발 중인 프로젝트에서는 웹&amp;nbsp;서버와 API 서버가 분리되어 있다. 웹 서버에서는 페이지마다 필요한 데이터들을 API 서버에 요청을 보내 받아와 사용자가 접근하고자 하는 뷰를 렌더링한다. 이에 따라 API 서버에서는 OAuth 기능이 필연적으로 필요하다. 웹 서버와 API 서버 둘 다 flask로 구현되어 있기 때문에 API 서버에서는 flask와 연동이 잘 되는 OAuth 2.0을 지원하는 라이브러리를 찾았고, &lt;a href=&quot;https://flask-oauthlib.readthedocs.io/en/latest/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;Flask-OAuthlib&lt;/a&gt;를 사용하고 있다. Flask-OAuthlib를 이용하여 OAuth 2.0을 지원하도록 하는 작업은 &lt;a href=&quot;https://flask-oauthlib.readthedocs.io/en/latest/oauth2.html&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;다음 링크&lt;/a&gt;를 참고하자.&amp;nbsp;차후 가능하면 추가로 블로깅을 할 생각은 있으나 그게 언제가 될지는....&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;웹 서버와&amp;nbsp;API 서버의 연동을 설명하면 다음과 같다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;가장 우선 로그인이 필요하다.&lt;/p&gt;&lt;p&gt;1. 유저가 로그인을 시도하면 API 서버의 로그인 페이지로 이동한다.&lt;/p&gt;&lt;p&gt;2. API 서버의 로그인 페이지에서 로그인을 성공하면 API 서버가 response로 웹 서버의 callback url로 redirect 시키는 302 response를 준다. 이 때 임시로 발급되는 code 값을 획득한다.&lt;/p&gt;&lt;p&gt;3. 브라우저에서는 302 response에 의해 웹 서버의 callback url로 이동한다.&lt;/p&gt;&lt;p&gt;4. 웹 서버의&amp;nbsp;해당 url에서는 이전에 획득한 code 값을 이용하여 oauthlib의 token_handler에 요청을 보내&amp;nbsp;access token과 refresh token을 획득한다. 획득한 토큰들은 쿠키로 저장하던가 아니면 세션에 저장하여 앞으로 요청시 계속 사용한다. (access token과 refresh token 등을 만들어주는 것은&amp;nbsp;create_token_response 메소드를 참고. 이 글에서는&amp;nbsp;자세히 다루지 않는다.)&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;API 서버의 로그인 페이지로 이동할 때는 조금 복합한 URL 생성이 필요하다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;script src=&quot;https://gist.github.com/growingdever/4d2a16c4004b28964130257199343aa9.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;params를 보면 여러가지 추가적인 파라미터를 명시해 놓은 것을 알 수 있다. 이는 OAuth 2.0 스펙에 필요한 추가적인 내용들이다. 예를 들면 `client_id`를 통해 어떤 애플리케이션에서 토큰 요청을 한 것인지 판별하고, `redirect_url`을 통해 OAuth 2.0 서버의 authorize 작업 후 callback할 url을 명시하여준다. 나 같은 경우 어떤 링크에 접근하고자 하는데, 로그인이 되지 않은 상태였을 경우, 로그인 후 원래 이동하려던 링크로 이동이 될 수 있도록 하기 위해 `next` 필드를 추가해줬었다. `response_type`이 code인 것을 보아 위에서 설명한 4번의 code 값을 이렇게 얻는다는 것을 알 수 있을 것이다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;다음은 callback url에서의 처리에 대한 코드이다. form 데이터로 어떤 것을 어떤 필드명으로 넣어줘야 하는지에 대한 설명이 잘 되어 있는 곳이 별로 없다는게 황당했다. flask-oauthlib 라이브러리의 예제에서도 자신의 라이브러리에 있는 클라이언트 클래스를 사용하는 것으로 되어 있었으니... 나중에 토큰 갱신과 비교하였을 때 봐두면 좋은 필드는 `grant_type`이다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;script src=&quot;https://gist.github.com/growingdever/b353a7568884b993e0361e29f2b395cd.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;OAuth 2.0 과정 중&amp;nbsp;Bearer 토큰을 생성하기 전에 임시로 Grant 토큰이라는 것을 생성한다. 이 Grant 토큰에 지정한 expire time에 따라 Bearer 토큰의 expire time이 결정된다. Grant 토큰의 주요 역할은 `scope` 등에 대한 것도 있겠지만, 역시&amp;nbsp;`code` 값을 갖고 있는 것이 가장 주요 역할이다. 이렇게 만들어진 `code` 값을 정상적으로 획득한 애플리케이션이 다시 토큰을 얻기 위한 API 호출을 했을 때 정상적인 Bearer 토큰을 얻게 된다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;`RequestOauth.get_token` 함수에서는 requests 라이브러리를 이용하여 POST 요청을 보낸다. 이 때 위에 써둔 form 데이터를 함께 보내야 한다. 이 때 Grant 토큰의 code 값을 사용하는 것을 확인할 수 있다. 요청 결과로는 access token과 refresh token을 획득하게 된다. 얻은 토큰은 어딘가에 잘 저장해두자. 위 코드에서는 저장 방법을 숨기기 위해 따로 적어두진 않았다. 세션이나 쿠키 등에 저장해둘 수 있을 것이다.&amp;nbsp;토큰을 획득한 뒤부터는 HTTP 헤더로&amp;nbsp;Authorization 헤더를 추가하고 그 값으로&amp;nbsp;`'Bearer {}'.format(access_token)`의 형태로&amp;nbsp;문자열을&amp;nbsp;지정하여 API 서버에 요청을 보낸다. API 서버에서는 `tokengetter`를 통해 적절한 토큰인지 확인하고, `usergetter`를 이용하여 유저 모델을 로드하여 사용한다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Bearer 토큰의 expire time이 지나면 해당 Bearer 토큰의&amp;nbsp;access token은 더 이상 사용할 수 없고, 다시 로그인을 하여 새로운 Bearer 토큰을 만들거나 refresh token을 이용하여 access token을 갱신해야 한다. 아무래도 사용자가 좀 더 편하게 애플리케이션을 사용하기 위해선&amp;nbsp;아이디와 비밀번호를 다시 입력하게 하는 것보다 refresh token을 이용하여 토큰 갱신을 하는 것이&amp;nbsp;훨씬 나은 방법이다. Flask-OAuthlib의 OAuth 2.0 Client를 사용하면 이것을 자동으로 해주는지 모르겠으나, 나 같은 경우 웹 서버에서는 모든 API 요청을&amp;nbsp;requests 라이브러리를 활용한 클래스로 만들어 이용하고 있었다. 따라서 토큰 갱신 작업은 직접 해줘야 했다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Flask-OAuthlib의 서버에 `invalid_response` 데코레이터를 이용하여 인증 과정 중 어떤 문제가 있는 경우 따로 처리를 해줄 수 있다. 아래 코드를 보면 토큰이 만료되었을 때, 접근 권한이 부족할 때, 그 외 상황 3가지로 나눠 예외를 띄워주도록 한 것을 볼 수 있다. 예외를 이용하여 HTTP 에러를 반환하는 방법에 대해서는 &lt;a href=&quot;http://growingdever.tistory.com/232&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;다음 글&lt;/a&gt;을 참고. 모두 다 401 에러이지만, 클라이언트에서 상황에 따라 다른 처리를 해주기 위해서는 추가 메세지를 써주는 것이 필요하기 때문에 이렇게 하였다. 직접 비교되는 문자열은 flask-oauthlib에 의해 정해진 상황별 에러 메세지이다. 이를 이용하여 각각의 다른 예외를 생성한 것이다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;script src=&quot;https://gist.github.com/growingdever/ab08f4384862cbd0bdaf6e5eb2b5f245.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;현재 우리 API 서버에서는 모든 사용자 정의 예외에 `reason` 필드를 추가한 json을 response로 전달하도록 되어 있다. 따라서, 클라이언트에서 요청을 보낸 결과 HTTP 에러가 발생한 경우 얻어온 response에서 `reason` 값을 확인하여 다른 처리를 할 수 있다는 의미이다. 웹 서버에서 API 요청을 보낼 때는 try-catch문을 사용하도록 되어 있고, requests 라이브러리를 이용하기 때문에 다음과 같은 정도의 코드가 될 것이다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;script src=&quot;https://gist.github.com/growingdever/b579424278a274a1dcfcdd89cac2a554.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;flask에서는 abort를 이용하여 에러를 띄울 경우 내부적으로 `werkzeug.exceptions`에 정의된 `HTTPException` 예외를 상속받은&amp;nbsp;여러가지 에러 코드별 예외 클래스의 인스턴스를 만들어 사용한다. 그리고, 사용자 정의 에러 핸들러를 등록해놓을 경우 해당 예외 객체를 받을 수 있다. 예를 들면 `abort(401)`를&amp;nbsp;호출하면 `Unauthorized` 예외가 만들어져 사용되는 것이다.&amp;nbsp;abort에 추가적인 인자로 문자열을 넘겨주면 해당 `HTTPException` 객체에 description을 추가할 수 있다.&amp;nbsp;위 소스에서는&amp;nbsp;abort 함수에 인자로 에러 코드 뿐만 아니라 reason을 함께 넣어주는 것을 알 수 있다. 이렇게 넣어준 description을 사용하는 예는 다음과 같다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;script src=&quot;https://gist.github.com/growingdever/dde88d81ea36d1567c0df1d3a0435c37.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;앞서 예시로 든 401 에러를 대응하기 위한 에러 핸들러이다. 함께 전달된 예외의 description을 확인하여 각각 다른 response를 만들도록 한다. 일단 에러 핸들러의 주요 역할은 적절한 response를 만들어 돌려주는 것이다. 에러 페이지를 띄워야 할 때는 템플릿을 로드하여 response로 만들어 돌려주고, 로그인이 필요한 경우에는 로그인 페이지로 이동하게 하기 위한 URL을 만들어주는&amp;nbsp;`get_oauth_req_url` 함수를 호출하여 얻은 URL을 redirect location으로 사용한다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;`refreshing_oauth_token`는 아직 설명되지 않은 함수이다. 앞서 언급한 바와 같이 나는 우리 웹 애플리케이션에서 사용자의 access token이 만료된 경우 refresh token을 이용하여 토큰을 갱신하고자 했다. 이 때도 이전의 로그인 때와 크게 다르지 않다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;script src=&quot;https://gist.github.com/growingdever/447c50b16a41761aec53255be1bbfbd0.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;위에서 로그인 callback 함수에서의 form 데이터와 다른 점은 `grant_type`이다. 이전에는&amp;nbsp;authorization_code이었던 것이 `refresh_token`으로 바뀌었다. 이 내용이 은근히 찾아도 잘 안 나온다는게 황당했다. 하여튼 마찬가지로 위 form 데이터를 이용하여 API 요청을 보내면 refresh token을 이용한 새로운 Bearer 토큰 생성이 완료된다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;OAuth 2.0 스펙에 맞는 서버 개발을 직접 하는 것이 불가능한 일은 아니지만, 이미 구현된 라이브러리가 있기 때문에 갖다 쓰는게 더 나을 것이다. 생각보다 복잡한 구석이 있어서 직접 개발하려면 시간이 꽤 걸릴 수 있다. 서버 구현 측은 flask-oauthlib의 문서를 보면 되긴 했다. 다만 flask-oauthlib에서는 내부 실행이 상당히 캡슐화 되어 있어서 나 같이 애플리케이션에서 flask-oauthlib 라이브러리의 클라이언트 클래스를 사용하지 않으려는 개발자에게 난감함을 줬다. 게다가 꾸준히 언급하는 것이지만 검색을 해도 잘 안 나온다.... 스택오버플로우 글에도 vote 수가 높지 않아서 이게 맞나 의심하면서 시도해보는 안습한 상황도 있었다. 코딩한 코드 자체는 얼마 안 되지만 진입 장벽이 좀 있겠다 싶은데다가&amp;nbsp;한글로 된 가이드 문서는 거의 없다시피 해서 나중에 글을 써봐야겠다고 생각했었는데, 거의 몇 달을 미루다가 이제서야 쓰는 것 같다. 많은 이들에게 도움이 됬으면 좋겠다!&lt;/p&gt;</description>
      <category>프로그래밍/서버</category>
      <author>터프 프로그래머</author>
      <guid isPermaLink="true">https://toughrogrammer.tistory.com/233</guid>
      <comments>https://toughrogrammer.tistory.com/233#entry233comment</comments>
      <pubDate>Mon, 23 May 2016 15:10:52 +0900</pubDate>
    </item>
    <item>
      <title>Flask에서 예외(Exception)를 이용하여 HTTP 에러 응답에 사용자 정의 메세지 추가하기</title>
      <link>https://toughrogrammer.tistory.com/232</link>
      <description>&lt;p&gt;파이썬에서는 `raise` statement를 이용하여 예외를 던질 수 있다. 예를 들면 `raise KeyError` 와 같은 식이다. 이렇게 raise한 exception은 인스턴스가 만들어지면서 던져지게 된다. flask에서는 `errorhandler` 데코레이터를 이용하여 에러 핸들러를 등록할 수 있도록 하고, route 함수에서 예외가 raise 되었을&amp;nbsp;때 등록된 에러 핸들러들 중에 적합한 핸들러가 있는지 찾아 해당 핸들러 루틴을 실행하도록&amp;nbsp;되어 있다. (Flask 클래스의&amp;nbsp;handle_http_exception 메소드 참고)&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://flask.pocoo.org/docs/0.10/patterns/errorpages/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;flask 문서&lt;/a&gt;에서 소개되는 방법은 숫자로만 등록하는 예제를 보여준다. 흔한 에러 코드 400, 401, 404와 같은 것들이 각각 한 가지의 메세지만 전달하는 경우에는 이 방법이 전혀 문제가 되지 않는다. 하지만 같은 에러 코드임에도 불구하고 상황에 따라 다른 메세지를 보여주고 싶은 경우가 있다. 예를 들면 401 에러 낼 때, 세션 유지 시간이 초과된 경우와&amp;nbsp;가진 권한이 부족한 경우를 분리하여 다른 메세지를 전달해주고 싶을 때가 있을 것이다. 이 경우 에러 코드만으로는 표현이 안 된다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;다행히도 flask에서 에러 핸들러를 등록할 때 사용자가 정의한&amp;nbsp;클래스도 등록 가능하다. raise를 활용하기 위해서는 Exception 클래스를 상속받은 클래스를 등록하도록 하면 좋을 것이다. 다음 코드를 참고하자.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;script src=&quot;https://gist.github.com/growingdever/7e7112730b370d0d1b6c531669395aa2.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이렇게 여러가지 예외들을 정의해둔 뒤,&amp;nbsp;route 함수에서 해당 예외를 raise 해주면 response의 body에 에러 핸들러에서 정의한 response의 내용이 잘 출력된다. (예: `raise ExpiredError`)&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;사실 이 글을 쓰기 전에 삽질했던 내용은 flask-restful에서의 문제였다. 위에서 보인 에러 핸들러는 flask의 route 함수에서의 raise한 예외는 잘 전달받는다. 하지만 flask-restful의 Api 클래스를 이용하여 만든 API 함수들에 대해서는 debug 모드일 경우에만 에러 핸들러가 작동하는 문제가 있었다. flask-restful의 Api 클래스는 생성시 `errors` dictionary를 인자로 받을 수 있게&amp;nbsp;되어 있다. flask의 설정으로 debug 모드가 켜져있지 않으면&amp;nbsp;해당 errors에 존재하는 내용들을 제한적으로 response로 전달할 수 있는 것으로 보였다. 이것을 이용하여 기존 방법을 그대로 활용할 방법을 찾느라 시간을 많이 낭비했다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;script src=&quot;https://gist.github.com/growingdever/427409cb64eab7ff3614851e6685a18c.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이는 내 착각에 의해 발생한 문제였다. 기본적으로 flask-restful에서는 예외를 만났을 때 `handle_error` 함수가 실행되고, 해당 함수에서는 잡힌 예외가 `HTTPException`의 자식 클래스들이 아닐 경우&amp;nbsp;등록된 Flask 객체의 `propagate_exceptions` 속성을 확인하여 flask의 에러 핸들러로 넘어가거나, 아니면 flask-restful에 등록된 errors를 참조하여 예외를 출력하도록 되어 있다. flask에서 `propagate_exceptions` 속성값이 결정되는 부분을 보면 `PROPAGATE_EXCEPTIONS` 설정값을 켜놓았거나 아니면 debug 모드일 경우 참을 반환하게 되어 있다. 그 동안 개발을 위해 항상 debug 모드를 켜놨었기 때문에 놓쳤던 부분이었는데, 배포를 위해 uwsgi를 통해 애플리케이션을 실행함으로써 만나게 된 문제였다. 설명이 장황했지만 &lt;b&gt;`PROPAGATE_EXCEPTIONS` 옵션을 켜놓으면 문제가 해결된다.&lt;/b&gt;&amp;nbsp; `HTTPException`은 flask에서 사용하는&amp;nbsp;werkzeug에서 정의된 클래스인데, 처음부터 커스텀 예외 클래스를 정의할 때 `HTTPException` 클래스를 상속받아 정의하였으면 만나지 않았을 문제임을 생각하면 너무 슬프다...&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>프로그래밍/서버</category>
      <author>터프 프로그래머</author>
      <guid isPermaLink="true">https://toughrogrammer.tistory.com/232</guid>
      <comments>https://toughrogrammer.tistory.com/232#entry232comment</comments>
      <pubDate>Sun, 22 May 2016 16:48:15 +0900</pubDate>
    </item>
    <item>
      <title>pyenv로 python 설치시 openssl을 찾지 못해 발생하는 문제 해결 방법</title>
      <link>https://toughrogrammer.tistory.com/231</link>
      <description>&lt;p&gt;`&lt;span style=&quot;color: rgb(51, 51, 51); font-family: 'Helvetica Neue', Helvetica, 'Segoe UI', Arial, freesans, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; line-height: 22.4px;&quot;&gt;The Python ssl extension was not compiled. Missing the OpenSSL lib`&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Ubuntu 13.04에서 pyenv를 이용하여 python을 설치하려고 할 때,&amp;nbsp;위와 같은 에러 로그가 나타나면서 설치가 안 되는 문제가 있었다. 이는 pyenv의 문제가 아니라 openssl 개발용 패키지를 찾을 수 없어 발생하는 문제다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;해결 방법 : `libssl-dev`을 설치해주면 된다. `sudo apt-get install libssl-dev`를 실행하면 된다.&lt;/p&gt;</description>
      <category>프로그래밍/잡탕</category>
      <author>터프 프로그래머</author>
      <guid isPermaLink="true">https://toughrogrammer.tistory.com/231</guid>
      <comments>https://toughrogrammer.tistory.com/231#entry231comment</comments>
      <pubDate>Fri, 20 May 2016 19:46:08 +0900</pubDate>
    </item>
  </channel>
</rss>