parseSdkContent failed

Could not initialize class android.graphics.Typeface


정확히는 위와같이 발생합니다. 그래서 다른 프로젝트를 가져와서 막상 실행시킬려고 하면 에러가 발생하는데 OSX  에서는 $HOME/.android 폴더를 강제로 지워주고 다시 시작하니까 문제 없이 동작합니다. 



NDK (Native Development Kit Environment) 입니다. 구글은 Java 환경의 SDK 만 가지고 모든 것이 가능할 것이라고 주장했었으나, 몇 버젼 지나지 않아 자신들의 주장을 철회하고 Native C/C++ 프로그램이 가능한 NDK 를 공개했습니다. 

 왜 NDK 를 사용하는가에 관해서는 몇가지 이유가 있겠지만 

 - 빠른 연산을 위해서 (아무래도 C/C++ 이 자바 보다 속도가 빠른것은 주지의 사실입니다) 
 - 수많은 Open Source 들이 아직도 C/C++ 로 되어 있기 때문에


일단 이 두개를 크게 들 수가 있겠습니다. 그렇다고 해서 윈도우 다루는 부분까지 전부 NDK 로 하는 것을 추천하지는 않습니다. 속도가 필요한 부분이나 위에서 언급된 오픈 소스로 만들어진 라이브러리를 응용하는 부분에 사용할 것을 추천합니다. 

 NDK 를 컴파일 하기 위해서 알아야 할 구글에서 만들어둔 Makefile 입니다. Makefile 의 문법을 쓰고 있지만 세세하게 다 작성할 필요 없이 형태에만 맞춰주면 나머지는 빌드스크립트가 알아서 처리를 해 줍니다. 물론 이 방법 말고 OpenSource 를 컴파일하기 위해서는 방법이 따로 필요하지만 그 건 다른 포스트에서 다루기로 하겠습니다. 

 실제로 예제를 따라 만들어 보면 쉽게 이해가 될 것 같습니다. 
 

 NDK 를 개발하기 위한 준비 과정입니다. SDK 와 NDK 를 받아서 적당한 곳에 풀고 SDK 에 관련된 명령을 어디서든 실행할 수 있게 PATH 에 걸어주고, NDK 또한 어디서건 실행이 가능하게 PATH 에 걸어줍니다. 

안드로이드 프로젝트를 만들어줍니다. Eclipse (ADT) 에서도 만들어 줄 수 있지만 저는 기본적으로 Command Line 을 선호하므로 Terminal 에서 만들어 줍니다. 

필수로 입력해야할 내용들입니다. -n 은 프로젝트의 이름 (위에서는 HelloNDK) -t 은 현재 자신의 개발 머신에 설치되어 있는 SDK 버젼의 id 입니다. -k 는 패키지 명입니다. (위에서는 com.comjuck) -a 는 Activity 의 이름입니다. 

 디자인 부분을 손봐줍니다. 그냥 간단하게 TextView 하나 생성해 줍니다. 

 MainActivity 에서 Layout 에서 만들어준 TextView 에 NativeCode 에서 리턴한 문자열을 출력하는 예제를 만들 예정입니다. 따라서 위와 같이 작성해주면 됩니다. 

 실제로 Android 프로젝트를 빌드하는 과정입니다. 그냥 위 명령 그대로 입력하면 컴파일 될 것입니다. 

EDIT: ubuntu 에서 javah 사용시 classpath 를 명시해 줘야 하더군요. 

javah -classpath <android-sdk-path>/platforms/android-20/android.jar:$PROJECT_PATH/bin/classes com.comjuck.MainActivity 


Android 부분이 에러 없이 컴파일 된다고 해도 실제로 Native Code 부분이 없기 때문에 실행하면 에러가 발생합니다. 따라서 Native 부분을 작성해 주는 과정입니다.  javah 명령을 이용해서 header 를 자동으로 생성해 주기로 합니다. javah 는 native 키워드가 붙은 부분에 대해서 자동으로 선언을 만들어 줍니다. (JNI 가 기본으로 제공해 주는 기능입니다) 

그래서 생성된 파일(이름에 주목하면 package 명 + Class 명이 합쳐진 형태)을 $PROJECT_PATH/jni 디렉토리로 복사해줍니다. 

Native 코드들이 위치하는 곳은 $PROJECT_PATH/jni 입니다. 

 Native Code 가 위치해야 하는 $PROJECT_ROOT/jni 에 javah 를 이용해서 생성해준 com_comjuck_MainActivity.h 에 대응하는 Body 를 만들어줍니다. 여기서는 HelloNDK.c 로 만들어 주고 위와 같은 코드를 채워줍니다. 

AndroidManifest.xml 파일을 열어서 이제부터 NDK 를 사용할 것이라는 것을 지정해줘야 합니다. 딱히 해줄것은 없고 minSDKVersion 정보를 최소 14로 맞춰주는 것입니다. 

Native Code를 컴파일 하기 위해서 필요한 Android 용 Makefile 입니다. 현재 지금 있는 것을 컴파일 하기 위해서는 단지 위와 같이 작성해주면 됩니다. 

Android.mk 의 예제입니다. 

LOCAL_PATH 는 ndk-build 가 실행될 때의 위치에 따라서 정보가 다르게 저장되긴 하지만 기본적으로 $PROJECT_ROOT/jni 을 지정하게 되어 있습니다. 

include $(CLEAR_VARS) 는 LOCAL_PATH 를 제외한 모든 LOCAL_ 로 시작되는 변수들이 초기화 됩니다. 왜 필요한가 의문을 가질 수 있지만 여러개의 프로젝트를 컴파일할 때 꼭 필요한 명령입니다. 

LOCAL_CFLAGS 는 추가해줄 컴파일 옵션에 관한 세팅입니다. 

LOCAL_MODULE 은 만들고자 하는 모듈의 이름입니다. 

LOCAL_LDLIBS 는 연결하고자 하는 링크옵션입니다. 

LOCAL_SRC_FILES 는 컴파일 될 대상인 소스파일들의 리스트입니다. 여기에 나열만 해주는 것으로 알아서 처리가 됩니다. 

include $(BUILD_SHARED_LIBRARY) 는 shared object (so 파일) 을 생성하기 위한 명령입니다. BUILD_STATIC_LIBRARY 는 static object (a 파일)을 만들어주기 위한 명령입니다. 

$PROJECT_ROOT 나 jni 디렉토리에서 ndk-build 만 입력하면 컴파일 됩니다. 그리고 다시 $PROJECT_ROOT 에 가서 ant -e debug 를 입력하셔서 다시 컴파일 해주시면 됩니다. 

여러가지 방법이 있겠지만 Command Line 을 이용하자면 

$ ant -e installd


를 이용하면 Device 에 인스톨 됩니다. 
 


  오픈소스를 만지작 거리다 보면 남의 소스를 볼 일이 무지하게 많습니다. 가장 최근만 해도 이동통신 관련한 소스를 줄기차게 보고 있는데 (지금까지 본것중에서 난이도가 openssl 에 필적합니다.) 이러한 소스는 보통 여러 사람이 몇년동안 작업을 한 것이 대부분 이기 때문에 처음부터 전부 이해하려고 하다보면 들어가는 시간이 엄청 걸립니다. 단기간에 필요한 부분만 꺼내서 이용할 수 있는 신공을 익혀야만 제대로 쓸 수가 있다는 이야깁니다. 이러한 오픈소스 (그 중에서 특히 C 로 만들어진) 들이 어려운 이유가 몇가지가 있습니다. 그중에서 개인적으로 어렵게 만드는 요인이라고 볼 수 있는 것 몇가지만 이야기 해 볼까 합니다. 

  1. 엄청 많은 매크로 
     
     코드를 이해하는 것만큼 이 엄청난 수의 매크로도 이해해야 합니다. 

  2. 핸들러(handler)로 표현되는 다양한 함수 포인터 (Function Pointer)
     
    왜 C 로 C++ 형식처럼 짜는지 대충 이해가 갑니다만 (속도를 위해서겠지요..) static 키워드를 이용해서 C를 마치 OOP 처럼 구현해 놨습니다. 물론 이해하기는 OOP 보다 어렵습니다. (OOP 도 남의 소스는 보기가 쉽지 않지요) 다양한 구조체와 이에 연관된 함수 포인터를 마치 멤버 함수처럼 이용하는 형식으로 구현했기 때문에 추적하기도 쉽지가 않고 어디서 어떻게 불리는 지 파악도 어렵습니다. 


  이 외에도 몇가지는 있겠지만 제가 느끼기에는 이것들이 중요한 요인입니다. 그렇다고 해도 소스를 못 따라갈 정도는 아니지만 일일이 따라가기에는 너무나 방대한 양이기에 제대로 추적하기가 쉽지가 않습니다. 

  그래서 이러한 오픈소스를 분석하거나 자신이 필요한 기능을 추출할 때 쓸만한 팁을 드릴까 합니다. 사실 팁이라고 할만한 것도 아닐 수 있습니다. 일단 '돌아가는 소스를 받아서 구동시켜야 한다' 가 전제가 되어야 하며 이 때 돌아가는 '흐름'을 추적해야 합니다. 너무 난해할 수도 있는데 세부적인 것을 다 파고 들어가는 것이 아니라 필요한 기능이 어떻게 흘러가는지 전체적인 흐름을 파악하고 나면 그 때서야 어디를 어떻게 시작해야 할지 목표를 설정할 수가 있는 것입니다. 흐름을 파악해야 하는 주요 목적은 어디를 어떻게 봐야 하는지 목표를 설정하는 것과 일맥 상통한다고 볼 수 있습니다. 이리 되면 필요한 부분에 Log 를 심는다던지 디버거를 이용한 다던지 하면서 데이터가 어떻게 흐르는지 파악을 하고 자세하게 필요한 부분을 살펴볼 수가 있습니다. 즉 정리하자면 

 

1. 프로그램이 어떻게 구동되는지 흐름을 파악한다. 
2. 세부 프로세스가 어떤식으로 함수가 호출되는지 파악한다. 
3. 함수에서 사용되는 데이타가 어떤식으로 흘러가는지 파악한다. 
4. 그 데이타가 어떤 구조체 (structure, class)에 담겨있는지 파악한다. 


  이런식으로 굵직하게 흐름을 파악하는 식으로 나아가면 단기간에 빠르게 소스를 분석할 수가 있습니다. 어느 정도에 이르신 분들에게는 필요 없는 팁일 수가 있지만, 이제 막 프로그램의 재미를 익혀나가서 다른 고수들의 프로그램을 살펴볼 필요가 있는 분들에게는 도움이 될 것입니다. 

ps. 
 
요즘 블로그에 소홀한 이유는 변명 아닌 변명이겠지만 지금 하고 있는 일이 바쁘고 난이도가 높기 때문입니다. 간만에 청춘으로 돌아간 듯 밤 늦게 까지 모니터를 쳐다보며 지내는 시간이 많아서 입니다. (물론 그렇다고 놀지 않는다는 말은 아니지요 ㅎㅎ)  
ClojureScript 라고 들어 보셨는가요? 자바 스크립트 (Javascript) 는 대단히 편리한 언어이고 렉시칼 스코프 (Lexical Scope)를 지원해서 Anonymous Function 이나 Closure 를 지원하는 등, 고급 추상적인 언어가 지녀야할 몇가지 장점을 지니고 있습니다. 또한 문법도 쉽고 그래서 빠르게 전파되고 있습니다. 게다가 V8 엔진을 쓰는 Node.js 등 이제는 속도면에서도 자바(Java)에 필적하고 있습니다. (예전 제 포스트 참조) 

그러나 자바스크립트는 또한 약점이 존재합니다. 자바스크립트 지지자들도 여러번 지적하는 사항이라고 하는데요 (사실 저는 잘 모르겠습니다 -ㅅ- ). 지나치게 단순하고 어딘가 허술하며, 확장시키기 좋지 않으며 어떤 기능을 구현하기 위해서 불편하게 추가해야 하는 사항이 많다는 것입니다. 그런 이유로 커피스크립트 (CoffeeScript) 같은 것이 나와서 자바스크립트의 그러한 문제점들을 보완하고 있다는 것입니다. 

그래서 숨어있던 리습(Lisp)의 추종자들이 구글이 제공하는 Google Closure Compiler 의 힘을 바탕으로 해서 강력한 어둠의 Lispy Magic 을 이용해서 커피 스크립트 같은 것을 만들어 냈습니다. 그것이 바로 ClojureScript 입니다. 

예전에 폴 그레이엄이 말하길 '자신이 개발해야 하는 언어로 가장 생산성 있게 개발하고 싶다면, 그 언어를 이용해서 Lisp 해석기를 만들고 그 리습으로 코딩을 하고 나중에 원래 언어로 컴파일 하라.' 라는 이야기를 한 적이 있습니다. 바로 그렇게 만들어 버린 것입니다. 개발자들은 ClojureScript 로 개발을 하고 Google Closure Compiler 로 컴파일을 하면 자바스크립트 파일이 튀어 나옵니다. 이 파일을 이용해서 Client Side 의 작업과 심지어는 Server Side (Node.jsJavascript 로도 바꾸어 줍니다) 작업까지도 일원화 할 수가 있습니다. 

이름에서도 유추할 수 있듯이 사용하고 있는 Lisp 의 방언 (dialect) 은 바로 Clojure 입니다. 

개요만 찾아서 보시고 바로 튜토리얼로 넘어가면 좋을 것 같습니다. 

예전에 언급 했듯이 확산성 밀리언 아서 게임을 열심히 하고 있습니다. 뭐 불법적인것은 안하는 주의지만 부캐가 필요해 지더군요. AP 를 이빠이 올려서 요정만 팝시키고 다닐 수 있는 ... 그렇다고 그 목적을 위해서 기기 한개를 새로 살 수는 없지 않겠습니까? (사실 살려고도 생각했습니다.. 넥서스가 30만원 선이라고. 쩝쩝) 

게임은 현질은 죽어도 안하면서 이런 기계는 마구 마구 사지르다니 그럴 수는 없다고 생각했습니다. 공짜로 해결할 수 있으면 공짜로 해결하자! 가 제 모토기 때문에 편안한 길을 찾다가 답을 찾았습니다.

http://www.bluestacks.com/bstks_mac.html

바로 안드로이드 머신을 맥 OSX 에서 돌릴 수 있게 해주는 어플입니다. (제가 주로 사용하는 노트북이 맥이 관계로 OSX 용입니다. PC 용도 그리 다르지 않을 것이라 생각합니다) 

1. 위에서 언급된 주소에서 블루스택을 다운 받아서 설치해줍니다. 
    http://www.bluestacks.com/bstks_mac.html 받아서 더블 클릭해서 설치하는 일반적인 맥용 DMG 파일입니다. 

2. 설치된 블루스택을 실행해줍니다. 
 실행하면 뭐 셋업이니 뭐니 해서 시간이 좀 오래 걸리고 나면 일반적인 안드로이드 폰하고는 전혀 다른 모습의 메인 화면이 떡하니 뜹니다. (귀찮아서 사진이 없는 것은 양해해 주시기 바랍니다)

3. 오른쪽 상단 구석에 있는 검색에서  'actoz' 로 검색해 보지만 나와 있는 리스트에 있는 '확산성 밀리언 아서'는 설치할 수가 없다고 합니다. 
  좌절하지 마시고!!

4. 왼쪽에 있는 'app stores' 를 클릭합니다.  

 
5. 'Get Jar' 를 클릭합니다.  

 

6. 오른쪽 상단 구석의 검색창에서 'firefox' 를 검색하고 'Go' 버튼을 클릭해 줍니다.

 

7. 'Firefox Browser for Android' 를 선택해서 설치해 준다고 합니다. 이때 정말 중요!! Google Play  계정으로 설치해 준다고 선택해 줘야 합니다.

8. 그러면 '구글 계정'을 만들라는 창이 나옵니다.  하나 빠르게 만들어 주시고 (저는 날림으로 만들어서 계정하고 비밀번호를 적어뒀습니다..)

9.  FireFox 가 인스톨이 끝나면 그 창 그대로 머문 상태에서 검색 버튼을 누르시고 'actoz' 로 검색하시면 '확산성 밀리언 아서' 라고 나오는 항목을 설치해 주시면 됩니다. 

 
10. 화면 맨 아래의 '블루스택' 아이콘을 클릭하시면 메인 화면으로 돌아오는데 그 중에서  위 사진 처럼 'My apps' 를 클릭하시면 설치되어 있는 밀리언 아서가 보입니다. 

11. 밀리언 아서를 실행하시면서 추천인 노가다 같은 행위를 하시면서 계정 블락 당하는 사태가 벌어지지 않았으면 좋겠습니다. (저는 단지 노예로만 활용 예정)


실행화면 인증샷 입니다. 
 
Node.js express 를 이용해서 개발하다 보면 아쉬운 것이 한가지 있습니다. 본의 아니게 서버가 죽어버리는 일입니다. 물론 이런일이 발생하지 않게 에러 핸들링을 속된 말로 빡세게 해야 하지만 뭐 어떻게 알아서 그걸 다 하고 있습니까... (후다다다닥) 

만약 서버가 죽는다고 하더라도 우아하게 재시작 된다면 (gracefully restart 라는 표현을 쓰더군요) 추후에 비슷한 에러가 발생하지 않도록 조치를 취해줄 수가 있습니다.  

역시나 설치법은 무지 쉽습니다. npm 이 설치되어 있다고 가정한다면

$ sudo npm install forever -g 



이러면 설치되고, 원하는 스크립트 서버를 띄워주고 싶다면

$ forever start app.js


하면 데몬 형식으로 구동됩니다. 아! 로그도 봐야 하지요?

$ forever logs



하면 현재 띄워져 있는 스크립트랑 거기에 관한 로그에 대한 내역이 나옵니다.  내용을 보고 싶다면 , 예를 들어 한개의 스크립트 서버를 띄웠다고 가정한다면

$ forever logs 0



하면 tail 로 로그를 뒤지는 효과가 나옵니다. 자세한 설명은 원문을 찾아보시면 됩니다.

원문링크 : https://github.com/nodejitsu/forever 

 You think you know when you learn, are more sure when you can write, even more when you can teach, but certain when you can program

 - Alan Perlis (Yale University computer scientist)




배울때 알게되고 , 글로 쓸 때 더 분명해 지며, 가르칠 수 있을 때 더욱 더 분명해진다. 그러나 프로그램을 할 수 있다면 더욱 확실 해진다.  

 
"How to Design Programs" 라는 책에서 언급된 DESIGN RECIPE 라고 불리우는 프로그램 디자인을 하는데 필요한 몇가지 단계 


Contract 단계 

   area-of-ring : number number -> number 

   area-of-ring 이라는 프로그램은 number 2개를 인자로 받아서 number 를 리턴하는 프로그램이다. 라고 구상하는 단계 


Purpose 단계 

outer 라는 바깥쪽의 원의 반지름과 inner 라는 안쪽 원의 반지름을 가진 두원의 사이에 끼어져 있는 고리의 영역을 계산하기 위한 것이 목적이다. 


Example 단계 

   (area-of-ring 5 3) 이거나 area-of-ring (5, 3); 은 50.24 의 값이 나와야 한다. 


Definition 단계 

 

 (define (area-of-ring outer inner)
     (- (area-of-disk outer)
        (area-of-disk inner)))



이건 Scheme 으로 구현한 것이고 

 

  int area-of-ring (int outer , int inner) {
     int outer-area = area-of-disk(outer);
     int inner-area = area-of-disk(inner);

     return outer-area - inner-area ;
    }


이건 (C/C++, Java) 계열 이겠군요. 자신이 즐겨 쓰는 언어로 구현 하는 단계를 말합니다. 

   
Tests 단계

   (area-of-ring 5 3) 또는 area-of-ring (5 ,3);
   의 기대값 
   50.24 

크나 작으나 이러한 절차를 따라서 자기도 모르게 만들어가고 있는 것이라고 볼 수가 있습니다. 
프로그램을 오래 짜 왔지만, 주로 시스템 베이스의 프로그램만 만들어 왔습니다. Windows 나 unix 시스템 계열의 코딩만 하다보니, c/c++ 에 너무 익숙해져서 뭔가 허전하더군요. 요즘 대세인 web programming 도 못하고, 그러던 차에 몇년전 부터 후배의 꼬임에 넘어가서 emacs 라는 툴을 다루기 시작하면서 lisp 에 관한 관심이 가더군요. 무지하게 매력적인 lisp 이란 언어에 푹 빠졌습니다.

뭐 사업하면서 코딩을 하고 있을 수는 없고 해서, emacs 라는 툴만 간간히 다루고 마음속에만 담아두고 있었는데, 요즘 회사 일손이 부족하여 저도 본격적으로 코딩을 다시 시작하게 됐습니다. 3년만에 다시 잡는 코딩이라 많이 생소한 느낌에 또 새로운 분야(아이폰 앱스 개발)라 진행도가 느린데 일과는 별도의 간단한 어플리케이션을 짤 일이 생겼습니다. 그래서 다시 lisp 을 찾아보게 됐습니다.

서두가 길었는데요, 리습은 emacs lisp 과 common lisp 그리고 수많은 방계 언어들이 많습니다. emacs lisp 은 익혀두면 모든것이 emacs 안에서 이루어지는 프로그램을 짤 수 있는데요, 유연함은 아무래도 common lisp 에 비해서 떨어집니다. common lisp 은 개발하기 위한 환경만 떨렁 갖춰두고 있었는데요,(slime + sbcl) 개발 환경만 가지고는 뭔가 만들었다는 기분이 들지가 않더군요.

개발하고 있는 것과는 별도로 데이타베이스의 사용자 테이블에 누가 있는지 궁금해서 서버팀에게 종종 물어봤는데, 일 방해하는것 같아서 직접 sql 모드로 들어가서 알아보다가, 반복적인 작업을 계속 하는 것 같아서 결국 프로그램을 짜기로 했습니다.

함수는 2개 입니다. 접속하는 함수 한개, 쿼리 날리는 함수 한개입니다. 이것을 패키징 해보겠습니다.

* db_user.lisp *

(in-package :db_user)
(require 'clsql)

(defun connect-our-db ()
 ....
))

(defun search-person (name)
...
)


뭐 이런식입니다. 함수형 언어니 함수 몇개를 만들면 되겠지요. (더 복잡한 것은 아직 모름..)

package 로 만들기 위해서는 2가지 파일이 더 필요합니다. 관련 파일들의 정보인 package.lisp 파일과 이것들을 묶어서 사용하게 해줄 asdf 파일인 db_user.asd 파일입니다. 그것들을 살펴보기로 하지요.

* package.lisp *

(in-package :cl-user)
(defpackage :db_user (:use :cl :clsql)
            (:export :connect-our-db
                     :search-person))


package 선언 부분이 들어 있는데요. db_user.lisp 에서 선언한 :db_user 를 선언하고 있지요. 거기에 db_user 에서 사용하는 다른 패키지에 관한 정보도 표현되어 있습니다. (예를 들면 clsql  같은 정보) 그리고 외부에서 불러서 써야할 함수들을 export 정보를 통해서 표현하고 있습니다.

* db_user.asd *

(defpackage :db_user (:use :asdf :cl))
(in-package :db_user)

(defsystem db_user
  :name "db_user"
  :depends-on ("clsql")
  :components ((:file "package")
               (:file "db_user" :depends-on ("package")))
  )


거의 비슷하고요. 다만 package.lisp 에서 써줬던 clsql 을 여기서 종속 관계로 명시해줘야 한다는 것입니다.
:depends-on("clsql") <-- 이부분
:components 부분은 연결된 파일을 차례로 써줘가는 것입니다. 처음에 package.lisp 이고, 그것과 연관된 세부 파일들을 세세하게 적어주는 것이지요.

여기 까지 만들어 줬으면 , 이 세개의 파일을 담고 있는 디렉토리를 *central-registry* 에 넣어두고 slime prompt 에서 (sbcl 인 경우)

(require 'db_user)


하면 패키지 로드되고

(db_user:connect-our-db) (db_user:search-person "crazia")


이런식으로 함수를 쓸 수가 있습니다.

다른 훌륭한 패키지들도 이런식으로 하나 하나 추가해 나가는 방식입니다.


저도 산전 수전 다 겪은 프로그래머 출신인지라 , 메모리 관리는 왠만하면 잘 지키는 편인데, object-c 는 자동으로 관리하는 것도 있고, 수동으로 지워줘야 하는 것도 있고, 클래스 함수들도 난립하는 터라 마구 마구 헷갈립니다.

예제로 시작하는 아이폰 개발

의 '부록' 에서 메모리 관리 팁을 설명하고 있어서 조금 정리했습니다. (부록이 참 잘 되어 있습니다..)

1. 블록 내에서는 alloc, retain, copy  의 수와 release, autorelease 의 수가 동일해야 합니다.

2.  factory 함수( 보통 class method 인 경우가 많습니다 )를 사용해 생성한 객체의 경우는 대부분 autorelease 로 반환됩니다. 따로 release 를 다시 할 필요가 없습니다.

3. 클래스의 인스턴스 변수들은 dealloc 메서드에서 다 release 해야 합니다.

+ Recent posts