iPhone 어플리케이션은 Xcode란 개발툴에서 코코아터치 프레임워크와 Objective-C 언어를 사용하여 개발합니다. 코코아 터치는 맥 OS X의 핵심 프레임워크인 코코아를 기본으로 아이폰/터치라는 모바일 기기의 특성에 맞추어진 iPhone 개발의 기본 API입니다.

그렇기 때문에 아이폰 어플리케이션 개발을 위해 기본적으로 알아야할 사항은 통합개발 환경인 Xcode와 인터페이스 빌더라는 툴의 사용법, Cocoa Touch 프레임워크, Objective-C 크게 세가지입니다. 이번에는 간단히 iPhone OS의 계층구조를 중심으로 iPhone 어플리케이션 제작을 위해 접근하는 방법에 대해서 알아 보겠습니다.

1. iPhone OS 계층 구조
아래의 그림은 애플에서 설명하는 아이폰 OS의 기술 계층입니다. 이에 대한 자세한 내용은 아이폰 개발자 센터iPhone OS Technology Overview란 문서에 잘 정리되어 있습니다.


가장 로우레벨의 Core OS부터 가장 상위단계인 코코아 터치까지의 계층구조입니다. 주로 하단은 API가 C로 제공되며 상위로 올라 오면서 Objective-C로 제공됩니다. 많이 사용되는 로우레벨의 API들은 상위단계에서 쉽고 편하게 사용할 수 있는 프레임워크로 제공됩니다. 간 계층의 간단한 설명은 아래와 같습니다.

1) Core OS
메모리/프로세서 관리, 파일 시스템, 네트워크, 각종 하드웨어 드라이버등, 운영체제 하단의 커널 레벨에서 제공하는 커널 API 입니다. 흔히 이야기하는 시스템 프로그래밍에 관련된 C를 기반으로 한 라이브러리를 제공합니다.

2) Core Services
Core Foundation, CFNetwork, SQLite, POSIX threads와 같은 파일입출력, 저수준 데이터 타입, 소켓등에 관련된 서비스입니다. Core OS 레벨에 비교적 쉽게 접근할 수 있는 API를 제공합며 대부분 C로된 프레임워크를 제공됩니다. 여기서 제공하는 많은 기능들은 상단 코코아터치의 Foundation Framework에서 Objective-C 프레임워크로 제공됩니다.

  • Core Foundation - 배열, 스트링, 날짜, URL, 로우레벨 데이터등 아이폰 어플리케이션을 위한 기본적인 C API를 제공합니다.
  • CFNetwork - BSD 소켓 및 HTTP, FTP 프로토콜등 네트워크에 쉽게 접근할 수 있는 API를 제공하는 프레임워크입니다.
  • Core Location - GPS, 주변검색등에 사용할 수 있는 사용자의 현재 위치(위도, 경도) 정보와 관련된 API를 제공하는 프레임워크입니다.
  • SQLite - 아이폰 어플리케이션에서 쉽게 사용할 수 있는 파일기반의 경량 데이터베이스 입니다. SQLite에 관련된 보다 자세한 내용은 이전 포스팅을 참조해 주세요.
  • XML - XML 파싱을 위한 libXML2 라이브러리를 제공합니다.

이외에 보안, 주소록등에 관련된 서비스를 제공합니다.

3) Media
비디오, 오디오, 2D/3D 그래픽, 에니메이션을 구현할 수 있는 API를 제공합니다. Objective-C 또는 C로된 API를 제공합니다.

  • Quartz - OS X의 벡터를 기반으로 한 그래픽 엔진입니다. 선과 도형을 그리고 이미지, 비트맵, PDF를 출력하고 색상, 위치에 관련된 C로된 API를 Core Graphic 프레임워크를 통해 제공합니다.
  • Core Animation - 각종 에니메이션과 시각효과를 제공하는 Objective-C로된 프레임워크입니다.
  • OpenGL ES - 게임등과 같은 고성능의 2D/3D 그래픽 출력을 위한 OpenGL ES 1.1에 기반한 C 프레임워크입니다. OpenGL ES를 사용한 게임은 이를 지원하는 다양한 모바일 플랫폼에서의 포팅을 쉽게 할 수 있습니다. 자세한 내용은 공식 홈페이지를 참조하시기 바랍니다.
  • Core Audio - 마이크를 통해 녹음하고 음악파일을 출력하고 각종 음향효과를 제공하는 C로된 오디오 관련 프레임워크입니다.
  • OpenAL - OpenGL ES와 유사한 개념의 크로스 플랫폼을 지원하는 게임등을 위한 고성능 3D 오디오 라이브러리입니다. 자세한 내용은 공식 홈페이지를 참조 하시기 바랍니다.
  • 동영상 - mov, mp4, 3gp와 같은 각종 동영상 파일을 출력을 지원합니다. 이는 Objective-C를 기반으로 한 Media Player framework를 이용하여 손쉽게 접근할 수 있습니다.

4) Cocoa Touch
iPhone개발의 기본이 되는 계층으로 Objective-C를 기반으로 한 핵심적인 두개의 프레임워크를 가지고 있습니다. 사용자 인터페이스, 이벤트 처리등과 함께 위에서 언급한 로우레벨의 기술들을 보다 쉽게 사용할 수 있는 프레임워크를 제공합니다.

  • Foundation Framework - 배열, 스트링, 날짜 로우레벨 데이터등에 관련된 클래스를 제공하는 기본적인 프레임워크입니다. 위의 Core Foundation에서 제공하는 기본 API들의 Objective-C 레퍼 클래스를 제공합니다.
  • UIKit Framework -각종 컨트롤, 윈도우등의 UI, 이벤트 처리등 iPhone 어플리케이션의 사용자 인터페이스와 관련된 API를 제공하는 프레임워크입니다.


2. Mac or iPhone
Mac의 Cocoa와 iPhone의 Cocoa touch는 이름에서 부터 알 수 있듯이 매우 비슷합니다. 가장 큰 차이점은 역시 인터페이스에 관련된 부분으로 이와 관련하여 Mac에서는 AppKit이란 프레임워크를 iPhone에서는 UIKit이란 프레임워크를 제공합니다.

Foundation Framework는 거의 유사하지만 아이폰의 특성상 제거 또는 변경된 부분이 있습니다. 아이폰은 코코아 바인딩, Objective-C 2.0의 가비지 컬렉션, 애플스크립트, NSUndoManage등은 지원하지 않습니다.

개인적으론 시간이 충분하다면 맥에서 코코아 프로그래밍으로 시작한 후에 코코아 터치로 넘어가도 큰 어려움은 없을 것 같습니다.


3. C or Objective-C
대표적인 코코아 프로그래밍 서적인 Cocoa Programming for MAC OS X (번역본:코코아 프로그래밍)의 저자 아론 힐리가스는 그의 저서에서 아래와 같이 이야기 했습니다.

"C와 Java/C++같은 객체지향 언어를 알고 있다면 두시간이면 Objective-C를 마스터할 수 있다"

OOP와 프로그래밍 언어에 대한 개념만 있다면 Objective-C를 익히는 것은 쉽다라는 의미인 것 같습니다.

사실 어느 언어나 기본지식만 있으면 언어 자체를 익히는데는 그다지 어려움이 없습니다. 하지만 해당 플랫폼에 대한 지식과 핵심 라이브러리나 프레임워크를 배우는데 더욱 많은 시간을 소비해야 합니다. 아이폰도 Xcode, 인터페이스빌더의 사용법, OS X에서의 프로그래밍에대한 이해와 코코아 API를 배우는데 Objective-C 언어 자체를 배우는 것보다 더 많은 시간이 필요합니다.

1) C의 선행학습이 필요한가?
개발경험이 있으신 분들은 그동안의 경험을 바탕으로 어떻게 공부해야 할지 나름대로 방법이 있을 것입니다. 하지만 처음 시작하시는 분들은 Objective-C가 C를 기반으로 하고 있기 때문에, 반드시 C를 공부한 후에 Objective-C를 공부해야하는 지에 대해 질문을 하시는 분들이 있습니다.

이 부분은 아마 많은 분들이 차이가 있을 것 같습니다. 제 생각은 "반드시 필요하지는 않다" 입니다. Objective-C는 C에서 확장된 슈퍼셋이라고 하지만 Objective-C를 공부하기 위해 반드시 C를 먼저 공부해야 할 필요는 없을 것 같습니다.

C를 이해한 후에 Objective-C를 시작하는 것이 이론상으로도 맞고, 정상적인 방법일 것입니다. 하지만 대부분의 Objective-C 서적이나 메뉴얼에는 기본적인 문법에 대한 설명이 있고, 전문 C 서적보다는 범위가 작습니다. Objective-C를 사용할 수 있을 만큼 최소한의 문법만 알고 시작하는 것이 더 접근이 쉬울 것 같습니다.

아래는 iPhone 어플케이션의 샘플 소스중에 한부분입니다. 기존에 C/C++ 개발자들도 이런 Objective-C의 문법을 처음 보게되면, 이것이 C와 관련이 있고 C에서 확장되었다는 사실이 잘 이해가 가지 않을 것입니다.
 

처음 시작하시는 분이라면 차라리 이런 혼란을 피하고 코코아 어플리케이션을 바로 제작할 수 있는 Objective-C로 시작하는 것도 한 방법이라고 생각됩니다.

2) 접근방법
사실 가장 좋은 것은 아래와 같이 가장 로우레벨단계 부터 이해하고 올라 가는 것이 기초도 탄탄하고 가장 좋은 방법일 것입니다.

  1. 메모리/CPU등 컴퓨터 하드웨어에 대한 이해
  2. OS에 대한 이해
  3. 컴파일러에 대한 이해
  4. C언어
  5. 시스템 프로그래밍
  6. 자료구조/알고리즘
  7. Objective-C
  8. OOP, 디자인 패턴
  9. Cocoa API

위의 단계대로 차례로 지식과 실력을 쌓아서 접근하면 좋겠지만, 당장 아이폰 어플리케이션을 만들고 싶은데 지루하고 많은 시간을 필요로 합니다. 흥미를 잃지 않고 접근하는 방법은 위의 순서와 반대로 접근하는 것입니다.

Objective-C에 대한 메뉴얼을 대충(?) 한번 읽어 보고 바로 책, 웹사이트, 동영상등의 간단한 튜토리얼등을 따라해 보면서 실제 실행되는 모습을 보면 계속 흥미를 유지할 수 있습니다.

그후에 어느정도 감이 생기면 직접 만들고 싶은 어플리케이션을 목표로 잡습니다. 첫 목표는 과한 욕심은 버리고 간단하고 쉬운 어플리케이션 부터 시작합니다. 아무리 간단해도 따라해 보며 만들었던 것과는 달리 원하는 기능을 직접 구현하는 것은 매우 어렵습니다. 자료도 많이 찾아 보아야되고, 다른 샘플 소스에서 복사해 와야 하는 경우도 있고, 관련 커뮤니티를 통해 도움을 받을 수도 있습니다. 관련자료들은 이전의 iPhone 어플리케이션 개발을 위한 준비 - 3. 관련 자료 포스팅을 참고하시기 바랍니다.

우여곡절끝에 만들어 내면 부족한면이나 필요한 부분이 무엇인지 조금씩 보이게 될 것 입니다. 필요에 의해 공부를 하다보면 아마 위의 순서를 거슬러 올라 접근하게 되는 경우가 많을 것 같습니다. 그러면 차차 복잡하고 어려운 어플리케이션을 만들 수 있을 것입니다.

막상 처음 시작하게 되면 알아야 할 것은 많고 답답한 마음만 드실 것입니다. 하지만 안타깝게도 왕도는 없습니다. 흥미와 열정을 유지하면서 차근차근 해 나가다 보면 어느새 바라는 어플리케이션을 만들수 있는 날이 눈앞에 와있을 것 입니다.

간혹 주위에 개발경험이 전혀 없는 분들로 부터 "무엇부터 시작해야 아이폰 어플리케이션을 만들 수 있냐?"는 질문을 들으면서 이와 관련해서 간단히 포스팅을 해보아야 겠다고 생각했습니다.

정리는 해보았는데 역시 어렵네요. 제가 봐도 이상한 용어들만 난무하고 설명도 중구난방이고, 개발을 처음 하시는 분들이 보면 잘 이해가 안가실 것 같다는 생각이 듭니다.

* 관련링크
iPhone 어플리케이션 개발을 위한 준비 - 1. 하드웨어
iPhone 어플리케이션 개발을 위한 준비 - 2. 소프트웨어
iPhone 어플리케이션 개발을 위한 준비 - 3. 관련 자료
iPhone 어플리케이션 개발을 위한 준비 - 4. 시작하기

'iOS' 카테고리의 다른 글

UITableView의 메모리 누수 현상  (4) 2009.02.05
UIView에서 텍스트 출력  (10) 2009.01.13
아이폰 SQLite3 샘플  (5) 2008.11.25
재미있는 아이팟 터치용 게임들  (9) 2008.11.23
iPhone SDK 2.2 업데이트  (4) 2008.11.22
AND

3. 관련 자료

* 한글 자료와 영어
아쉽게도 이곳에서 소개하는 대부분의 자료들은 영문으로 되어 있습니다. 아이폰과 관련되어 나온 번역서적은 아직까지는 한권도 없습니다. (준비하고 있다는 이야기는 들었습니다) OS X 코코아 프로그래밍에 관련된 내용인 코코아 프로그래밍이란 책이 유일하게 번역되어 나온 관련 서적입니다.


코코아 프로그래밍
카테고리 컴퓨터/IT
지은이 아론 힐리가스 (인사이트, 2007년)
상세보기

아직까지는 코코아 프로그래밍이 대중화 되지 않아 한글로된 자료가 적지만, 점점 많아질 것으로 보아집니다. 하지만 빠르고 다양한 자료를 얻기 위해서는 영문 자료를 보시는 수 밖에 없습니다. 저도 모자라는 영어로 괴롭지만 자꾸 보다보면서 조금씩 익숙해지고 있습니다. 저같이 영어 공부를 게을리 하신 분들은 따로 영어를 공부하실 필요 없이 영문자료로 공부를 하셔도 좋을 것 같습니다.

* ADC (Apple Developer Connection)
사실 맥/아이폰 프로그래밍에 관한 자료를 구하는 곳은 사실 딱 한 곳, ADC만 알고 있어도 됩니다. 이곳에는 애플에서 제공하는 OS X 개발과 관련된 다양한 문서와 자료, 동영상, 소스코드들이 있습니다. 자료가 방대하지만 이곳을 문지방이 닳도록 드나드시며 근본적인 자료들을 보시는 것이 늦어 보이지만 빠른 길입니다.


1) 관련 사이트
맥/아이폰 개발과 관련된 국내외 사이트들과 블로그들입니다. 국내 사이트들도 아직은 다른 일반적인 개발환경에 비해서 자료와 회원들이 많지는 않습니다. 하지만 비슷한 관심분야를 가진 분들과 교류를 할 수 있고, 궁금하신 점들을 올리고 다른 분들의 의견이나 해답을 들을 수 있습니다. 아무래도 같은 한국어로 이야기하는 것이 명확하고 친근할 것 같습니다.

2.1) 사이트
* 국내

* 국외
  • Apple Developer Connection - 애플에서 제공하는 개발자 지원 서비스입니다.
  • Apple Discutssions - Developer Forum - 애플에서 제공하는 개발자 포럼입니다.
  • iPhone Development Forum - 애플의 아이폰 개발자 포럼이며, 현재는 개발자 프로그램에 등록된 회원만 접근할 수 있습니다.
  • Cocoalab - Become An Xcode란 문서로 유명한 사이트이며 관련 강좌와 소스들을 제공합니다.
  • iPhone Development Centra - 아이폰 개발에 관련된 튜토리얼, 포럼을 제공합니다.
  • iPhoneWebDev - 아이폰용 웹사이트 개발에 관련된 예제, FAQ를 제공합니다.
  • Cocoabuilder.com - Cocoa와 Xcode에 관련된 메일링 리스트를 제공합니다.
  • Apps Amuck - 31개의 아이폰 튜토리얼과 샘플코드를 제공합니다.
  • Cocoa Dev Central - 코코아 튜토리얼과 관련 링크들을 제공합니다.
  • CS 193E - 스텐포드 대학에서 제공하는 코코아 프로그래밍 강좌입니다.
  • CS 193P - 스텐포드 대학에서 제공하는 아이폰 프로그래밍 강좌입니다.

2.2) 블로그

아래는 맥, 아이폰 개발 관련 블로그들입니다. RSS로 등록해 놓으시면 유익하고 재미있는 포스팅들을 편하게 구독하실 수 있습니다.


이외에 Xcode, Cocoa, 맥프로그래밍 관련 국내외 사이트란 포스팅도 참고하세요.

2) 관련 서적
다음은 Cocoa, Objective-C, 맥/아이폰 개발에 관련된 서적들입니다. '코코아 프로그래밍'을 제외하고는 모두 원서이므로 아마존과 같은 외국 사이트 또는 국내의 구매대행 서비스를 하는 온라인 쇼핑몰에서 구입을 하셔야 합니다.

맥에서의 개발이든 아이폰에서의 개발이든 공통적으로 Xcode/인터페이스빌더의 사용법과 Objective-C, 파운데이션 프레임워크에 관해서 알고 있어야 합니다. 그렇기 때문에 당장 시작을 할 수는 없지만, 초반에 기본을 익히는데는 맥 프로그래밍 서적을 보셔도 큰 상관은 없을 것 같습니다.


* 코코아 프로그래밍
- 가격: 22,000원
- 출판: 인사이트
- 역자: 박진형

위에서 언급했던 현재까지 유일한 번역서적입니다. 코코아 프로그래밍에 관한 기본적인 사항을 익히기에 아주 좋은 책이지만, Xcode 2.*를 중심으로 소개하고 있기 때문에, 초보자들이 보기에는 Xcode 3.*을 사용하면서 약간의 차이점 때문에 혼돈되는 부분이 있습니다.


* Cocoa Programming for Mac OS X
가격: $31.49
출판: Addison-Wesley Professional
저자: Aaron Hillegass

아론 힐리가스가 쓴 코코아 프로그래밍의 가장 대표적인 서적입니다. 아이폰이 아닌 맥 프로그래밍에 관한 내용이지만 Xcode, 인터페이스 사용법과 Objective-C등 기본적인 내용을 익히기에 좋은 책입니다. 


* Xcode 3 Unleashed
가격: $26.99
출판 Sams
저자: Fritz Anderson

Step into Xcode의 다음 버젼으로 이전과는 달리 깔끔한 디자인으로 Xcode 3에 맞추어 새로 발간된 책입니다. 위의 아론 책과 유사한 입문서적인데 툴 로서의 Xcode 3 사용법에 치중하였습니다. 칼라로 되어 있기 때문에 보기에 편하고 좋습니다.


* Programming in Objective-C 2.0
가격: $26.69
출판: Addison-Wesley Professional
저자: Stephen G. Kochan

Objective-C 언어에 대한 책입니다. 저는 1.0 버젼을 가지고 있어 정확히 내용은 어떻게 변경되었는지는 모르겠습니다. Objective-C에 대해 체계적으로 공부하기에 좋은 책이며, 책의 절반 가량은 코코아에서 사용하는 파운데이션 프레임워크에 관해 설명하고 있습니다.


* The iPhone Developer's Cookbook
가격: $27.62
출판: Addison-Wesley Professional
저자: Erica Sadun

저도 읽은 적은 없지만 아이폰에서의 개발을 따라 해 볼 수 있는 좋은 책이라고 들었습니다. 쿡북이란 제목처럼 실제로 사용할 수 있는 다양한 소스들을 참조하고 따라해 볼 수 있을 것 같습니다.

(이미지 출처:아마존)

지금은 원서도 많지는 않지만 앞으로는 아이폰 개발에 관련된 책들이 많이 나올 것으로 보입니다. 새로 나온 iPhone, Cocoa 관련 책과 저자들이란 포스팅에서 신간 혹은 새로 나올 관련서적을 확인하실 수 있습니다.

3) 관련 문서
ADC에서는 OS X 개발에 기본이되는 많은 문서들을 제공하고 있습니다. 또한 많은 문서들은 pdf 버젼으로 다운로드 받을 수 있습니다. 자주 참조하는 문서들은 pdf 문서로 다운로드를 받아 편하게 읽어 볼 수 있습니다.

3.1) Getting Started 문서
제목 그대로 애플에서 제공하는 맥/아이폰 개발을 처음 시작하는 입문자를 위한 문서들입니다. 이 문서들은 요점만 제공하기 때문에 내용이 짧아 부담없이 읽어 보실 수 있습니다.

* Mac
ADC의 Gettting Started with Apple Technologies 란 문서를 보시면 맥 OS X 개발을 위한 다양한 자료와 링크를 확인하실 수 있습니다. 또한 세부 항목별로 Getting Started with *와 같은 링크를 제공하기 때문에 관심있는 분야별로 보다 상세하게 확인하실 수 있습니다.

* iPhone
iPhone Dev Center에서는 시작 페이지에서 아래와 같은 문서의 링크를 제공합니다. 각 문서들은 로그인 후에 접근할 수 있습니다.


3.2) Objective-C

3.3) Xcode & Cocoa

  • What's New in Xcode - Xcode 3.0과 3.1에서 업그레이드된 사항들입니다.
  • Xcode Workspace Guide - 툴로서 Xcode 사용법에 관한 메뉴얼입니다.
  • Xcode Overview - Xcode에 대한 간단한 설명과 함께 관련내용에 대한 세부 링크를 제공합니다.
  • Become An Xcoder - 처음 코코아 프로그래밍을 시작하는 사람들을 위한 튜토리얼입니다.
  • Cocoa Fundamentals Guide - 코코아 프레임워크에 관련된 전반적인 내용을 제공합니다.
  • Cocoa Application Tutorial - 간단한 코코아 어플리케이션을 만들어 보는 상세한  튜토리얼을 제공합니다.
  • Foundation Framework Reference - 코코아 프로그래밍을 위한 NS로 시작되는 다양한 파운데이션 프레임워크의 클래스들에 대한 설명 과 기본적인 함수, 데이터타입과 상수들에 대한 문서입니다. Xcode의 도움말에서도 확인하실 수 있으며, 제공하는 클래스들을 둘러 보기에 좋은 문서입니다.
  • Application Kit Framework Reference - AppKit은 위의 파운데이션 프레임워크와 함께 코코아 프로그래밍의 핵심 프레임워크로, 사용자 인터페이스(UI)를 위한 프레임워크입니다. 코코아에서 제공하는 다양한 사용자 UI 클래스들을 볼 수 있는 문서입니다.

3.4) 아이폰
ADC에서 아이폰 개발과 관련된 문서들은 링크는 가능하지만, 로그인 하지 않으면 메인 페이지에서 접근할 수 없는 정책에 따라 링크는 제공하지 않습니다. 하지만 아이폰 관련문서는 많지 않기 때문에 iPhone Dev Center로 로그인 후에 쉽게 찾으실 수 있습니다.

애플의 iPhone DevCenter의 메인화면에서 우측하단을 보면 iPhone Reference Library 색션에서 Required Reading 항목을 보면 아래와 같은 세개의 문서가 있습니다. 애플에서도 권장하는 기본적인 문서인만큼 꼭 읽어 보시는 것이 좋습니다.



이상 맥/아이폰 개발을 위한 자료들과 관련 사이트들을 간단히 알아 보았습니다. 급하게 써내려가서 빠지거나 틀린 내용들이 많이 있을 것 같습니다. 이는 추후에 수정해 나가겠습니다.

다음에는 마지막으로 아이폰 프로그래밍을 시작하는 방법에 대해 주관적인 제 생각을 포스팅 해 보겠습니다.
* 관련링크
iPhone 어플리케이션 개발을 위한 준비 - 1. 하드웨어
iPhone 어플리케이션 개발을 위한 준비 - 2. 소프트웨어
iPhone 어플리케이션 개발을 위한 준비 - 3. 관련 자료
iPhone 어플리케이션 개발을 위한 준비 - 4. 시작하기

AND

OS X 10.5에는 Ruby(v 1.8.6)와 Rails(v 1.2.6)가 설치되어 있습니다. Ruby를 이용하여 Cocoa Application을 만드는 방법을 간단히 알아 보겠습니다. 실제 테스트를 해보니 Ruby와 Cocoa가 이정도까지 잘 결합될 수 있는 것이 신기하네요.

1. 프로젝트 생성
사용자 삽입 이미지
New Project를 클릭하고 프로젝트 템플릿의 Max OS X/Application에서 Cocoa-Ruby Application을 선택합니다. 프로젝트명에 'CocoaRuby' 또는 원하시는 프로젝트명을 입력하고 Save 버튼을 클릭합니다.




2. 기존 코코아 어플리케이션과 차이점
사용자 삽입 이미지
그룹에서 보시면 Objective-C의 main.m과 함게 새로 추가된 Ruby의 rb_main.rb 파일이 생성되어 있습니다.



1) main.m
#import <Cocoa/Cocoa.h>
#import <RubyCocoa/RBRuntime.h>

int main(int argc, const char *argv[])
{
    return RBApplicationMain("rb_main.rb", argc, argv);
}

Ruby를 위한 'RBRuntime.h'가 import되어 있습니다. 그리고 Cocoa에서 일반적으로 사용하는 'return NSApplicationMain(argc,  (const char **) argv);' 대신에 RBApplicationMain("rb_main.rb", argc, argv)로 대체되었습니다. 어플리케이션이 시작되면 'rb_main.rb' 루비 스크립트가 실행됩니다.

2) rb_main.rb
require 'osx/cocoa'

def rb_main_init
  path = OSX::NSBundle.mainBundle.resourcePath.fileSystemRepresentation
  rbfiles = Dir.entries(path).select {|x| /\.rb\z/ =~ x}
  rbfiles -= [ File.basename(__FILE__) ]
  rbfiles.each do |path|
    require( File.basename(path) )
  end
end

if $0 == __FILE__ then
  rb_main_init
  OSX.NSApplicationMain(0, nil)
end

* if $0 == __FILE__ then
현재 파일이 메인일 경우에 실행됩니다. 위의 'RBApplicationMain'에서 'rb_main.rb'을 실행하기 때문에 if내의 명령들이 실행됩니다.

* rb_main_init
Ruby에서 코코아와 기타 프레임워크에 접근을 위해 필요한 파일을 include 합니다.

* OSX.NSApplicationMain(0, nil)
기존의 NSApplicationMain을 실행합니다.


3. Ruby AppController 생성

AppController를 Ruby class로 만들어 보겠습니다. NewFile을 클릭하고 아래와 같이 Ruby 항목에서 'Ruby NSObject subclass'를 선택합니다.

사용자 삽입 이미지

FileName에 'AppController.rb'를 입력하고 Finish버튼을 클릭합니다. 아래와 같이 NSObject의 서브클래스로 AppController Ruby 클래스가 생성되어 있습니다. 푸른색으로 되어 있는 부분을 추가합니다.

require 'osx/cocoa'

class AppController < OSX::NSObject
    ib_outlet :inputText
    ib_outlet :outputText
   
    def buttonClicked(sender)
        OSX.NSLog("Button Clicked");
        @outputText.setStringValue("Hello~ #{@inputText.stringValue}")
    end
   
    ib_action :buttonClicked
end

* ib_outlet, ib_action
IBOutlet과 (IBAction) 대신에 Ruby에서는 ib_outlet과 ib_action을 사용하며 용도는 동일합니다.

* dot(.)
기존 프레임워크들의 클래스 메소드는 '.'를 이용해서 호출하고 함수들은 앞에 'OSX.'를 추가해서 호출합니다.


4. 인터페이스 빌더에서 작업
1) 윈도우
메인 윈도우에 라이브러리에서 Label, TextField, Button을 드래그 해서 아래와 같이 배치합니다.
사용자 삽입 이미지

2) AppController
사용자 삽입 이미지
라이브러리 윈도우에서 'Object'를 드래그해서 MainMenu.nib 윈도우에 드랍합니다.

Object의 Class를 앞에서 만들어 놓은 Ruby의 AppController로 선택합니다.




아래와 같이 inputText는 TextField에 outputText는 Label에 buttonClick는 buttonClicked에 각각 연결합니다.

사용자 삽입 이미지 사용자 삽입 이미지

이제 완료되었습니다. 빌드를 하고 실행을 합니다. 아래와 같이 ruby를 입력하고 버튼을 클릭하면 Label이 변경되는 것을 확인할 수 있습니다.
사용자 삽입 이미지

Ruby 스크립트들은 어플리케이션 번들의 Resource 디렉토리 밑에 위치합니다. Ruby는 인터프리터 방식으로 실행되기 때문에, 배포 후에도 사용자들이 목적에 맞게 Ruby(*.rb) 파일을 수정하여 어플리케이션을 쉽게 변경할 수 있습니다.
 
사용자 삽입 이미지
AND

문득 제 블로그 배너 속의 원숭이를 보다가 퍼뜩 생각 하나가 스쳐갔습니다. 위의 그림은 올해 초등학교 4학년이 된 제 아들이 그려 준 것인데요. 녀석의 깊은 뜻을 오늘에서야 깨달았습니다.

사용자 삽입 이미지
바로 이 원숭이가 저를 상징하는 Code Monkey였습니다.  Code Monkey는 쉬운 코딩만 겨우 해내는 프로그래머를 의미하는데, 제 아들이 애비를 정확히 보았네요. 녀석의 깊은 뜻도 모르고 원숭이가 왜 코코아를 마실까 의문을 가지고 있었던 제가 부끄럽습니다.

코코아를 들고 넋이 나간 저 원숭이가 Xcode를 사용하고 있을 때 제 모습인 것을 이제서야 알게되었습니다.

Code Monkey는 Jonathan Coulton이라는 미국의 젊은 개발자가 직접 만들고 부른 노래로 알고 있는데 작년 이맘 때쯤 많은 블로그에 소개가 되었던 노래 입니다. 아래는 유튜브에 있는 에니메이션 뮤직 비디오 입니다. 다시 들어도 참... ^^;




사용자 삽입 이미지
이런 깊은 뜻을 가지고 좋은 배너를 만들어 준 아들에게 고마움의 표시로 저런 티나 하나 사줘야 겠습니다. 이제 저 원숭이가 단순한 원숭이 그림이 아닌 제 분신처럼 느껴져 많은 애착이 갑니다.

(사진출처: http://www.cafepress.com)

AND

WebKit에 포함된 WebView를 이용하여 초 간단한 웹브라우저를 만들어 보겠습니다.
(프로젝트 생성과 인터페이스 빌더에서 작업은 이전에 많이 다루었기 때문에 여기서는 자세한 설명은 생략하였습니다. 'Cocoa 따라해보기' 분류의 초기 포스트에 관련된 설명이 있습니다.)

0. WebKit
WebKit은 애플의 대표적인 오픈소스 프로젝트 중에 하나 입니다. 대표적인 사파리뿐만 아니라 아도비 에어, 아이폰, 노키아의 폰부라우져, 구글 안드로이드 플랫폼등 여러 프로젝트에서 사용되었습니다. (출처:WikiPedia/WebKit)

Webkit은 KDE의 웹브라우져 퀀커러의 엔진인 KHTML과 KJS에서 시작한 발전해 왔습니다. 주개발자는 Dave Hyatt로 넷스케이프에서 모질라를 개발하다 2002년에 애플에 합류해 현재 사파리, Webkit팀의 책임자로 있는 것으로 보입니다. 초기 파이어폭스, Camino와 탭브라우징, XBL/XUL을 만들고 W3C에도 많은 영향력이 있는 것 같습니다.

Webkit에 대한 상세한 정보는 WebKit 홈페이지에서 확인하실 수 있습니다.

1. WebKit 프레임워크 추가
Xcode의 New Project에서 Cocoa Application으로 프로젝트를 하나 만듭니다. WebKit 프레임워크를 추가하기 위하여 Groups & Files에서 우클릭하여 Add > Existing Frameworks를 선택합니다.

사용자 삽입 이미지

사용자 삽입 이미지
선택창에서 /System/Library/FrameWorks에 있는 WebKit.framework를 선택합니다. 좌측과 같이 WebKit.framework가 포함되어 있는 것을 확인할 수 있습니다.


2. 소스코드 편집

AppController이란 이름으로 Objective-C 클래스를 생성합니다. 아래와 같이 입력합니다.

1) AppController.h
#import <Cocoa/Cocoa.h>

@class WebView;

@interface AppController : NSObject {
    IBOutlet WebView *webView;
    IBOutlet NSTextField *inputUrl;
}

- (IBAction)goURL:(id)sender;

@end

2) AppController.m
#import "AppController.h"
#import <WebKit/WebKit.h>

@implementation AppController

-(void)awakeFromNib
{
    /* 기본 주소 설정 */
     NSURL *url = [NSURL URLWithString:@"http://www.cocoadev.co.kr"];
    [[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:url]];
}

- (IBAction)goURL:(id)sender
{
    /* 사용자 입력 주소 Load */
    [[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:
        [NSURL URLWithString:[sender stringValue]]]];
}
@end

3. 인터페이스 빌더
Xcode의 AppController.h를 MainMenu.nib로 드래그 해서 가져다 놓습니다. Classes 항목의 AppController를 우클릭하여 Instantiate AppController를 선택하여 인스턴스를 생성합니다.

팔레트에서 NSTextField와 CustomView를 드래그해서 아래와 같이 윈도우에 배치합니다.
사용자 삽입 이미지

1) NSTextField 속성 변경
사용자 삽입 이미지
NSTextFiled는 윈도우 크기가 변경되더라도 위치를 유지하면서 가로 크기만 변하도록 설정하겠습니다.

NSTextField의 속성창중 Size메뉴(단축키: Command+3)에서 AutoSizing 속성을 좌측과 같이 변경합니다.


2) CutomView 속성 변경
사용자 삽입 이미지
웹컨텐츠를 보여주는 View는 윈도우의 크기에 맞춰 변환되도록 AutoSizing을 좌측과 같이 설정합니다.

그리고 Custom Class 항목(단축키: Command+5)에서 WebView를 선택합니다.


3) AppController와 연결
이제 AppController의 inputUrl을 NSTextField로 webView를 webView로 연결합니다.  그리고 Action에서 NSTextField는 goURL로 연결합니다.

사용자 삽입 이미지

4. 테스트 or More..
빌드 후에 실행하면 아래와 같습니다. 입력창에 URL을 입력하면 다른 사이트로 연결됩니다.
사용자 삽입 이미지

More...
Developer/Examples/WebKit/MiniBrowser에 히스토리가 저장되고 앞으로, 뒤로 가기, 리로드, 정지, 폰트 확대/축소 등의 기능이 추가된 예제가 있습니다. 그외 ADC의 WebKit과 관련된 아래의 문서들이 도움이 되실 것입니다.
AND

유닉스 계열에서는 실제 파일뿐만 아니라 소켓, 파이프, 장치등 모든 것을 파일로 간주하고 관리합니다. (pipe에서의 사용예는 제 블로그의 pipe를 이용한 간단한 프로세스간의 통신에서 확인하실 수 있습니다.)

NSFileHandle은 코코아 파운데이션 프레임워크에 포함된 저수준의 File Descriptor와  그와 관련된 open, close, read, write등의 관련된 함수들의 래퍼 클래스입니다.

코코아에서는 겍체의 아카이브를 지원하고 xml등 데이터 타입에 따라 파일이나 URL로 부터 편리하게 읽고 쓸 수 있게 하는 클래스들이 있기 때문에, 일반적인 파일에 관련된 작업에서는 NSFileHandle을 사용하여 직접 파일을 제어할 경우는 그다지 많지 않습니다.

NSFileHandle은 데이터를 읽고 쓰는데 1바이트 바이너리로 데이터를 저장하는 NSData를 사용합니다. NSData는 많은 클래스들에서 용도에 맞게 변경하는 초기화 메소드를 제공하므로 필요에 따라 사용하시면 됩니다.

1. 파일에서 텍스트 읽기
기존에 존재하는 test.txt 텍스트 파일을 읽어 출력하는 간단한 예입니다.

* 파일 핸들러 얻기  
fileHandleForReadingAtPath로 읽기전용으로 파일을 오픈합니다. 실패시에는 nil을 반환합니다.

* 파일읽기
readDataToEndOfFile로 파일의 전체를 읽어 오고 readDataOfLength로 특정 크기만큼 읽어 올 수 있습니다.

* 파일닫기
사용을 완료하였을 경우에는 closeFile을 이용해서 열려 있는 파일을 닫습니다.

NSFileHandle *readFile;

readFile = [NSFileHandle fileHandleForReadingAtPath:@"test.txt"];
if(readFile == nil)
{
    NSLog(@"fail to read file");
    return 1;
}
   
NSData *data = [readFile readDataToEndOfFile];
NSString* text = [[NSString alloc] initWithData: data
                                                        encoding: NSUTF8StringEncoding];
NSLog(@"%@", text);

[text release];
[readFile closeFile];

2. 파일에 텍스트 쓰기
new.txt 텍스트 파일을 만들어 "new text..."란 텍스트를 입력하는 예입니다.

* 파일 생성
[[NSFileManager defaultManager] createFileAtPath:@"new.txt"
    contents: data attributes:nil];

두번째 인자인 contents에 data를 지정하여 data의 내용으로 파일이 생성됩니다. 빈 파일 생성시에는 nil로 설정합니다. 생성 후에 [writeFile writeData: data] 메소드를 이용하여 파일에 입력할 수 있습니다. NSFileManager에 관해서는 다음 포스팅에서 자세히 설명하겠습니다.

* NSFileHandle없이 파일 제어
[NSData dataWithContentsOfFile:@"new.txt"]

NSFileHandle을 거치지 않고 NSData에서 바로 파일을 읽어 올 수 있습니다. 반대로 저장도 가능합니다. 위에 언급한 것과 같이 클래스들이 파일에 관련된 메소드를 가지고 있습니다. NString도 NSData를 거치지 않고 아래와 같이 텍스트 파일에서 내용을 바로 읽어 올 수 있습니다.

NSString* readText =[NSString stringWithContentsOfFile:@"new.txt"
            encoding:NSUTF8StringEncoding error:NULL];

NSString* text = @"new text...";
NSFileHandle *writeFile;
NSData *data = [NSData dataWithBytes:[text cString]
    length:[text cStringLength]];   

[[NSFileManager defaultManager] createFileAtPath:@"new.txt"
    contents: data attributes:nil];

writeFile = [NSFileHandle fileHandleForWritingAtPath:@"new.txt"];
if(writeFile == nil)
{
    NSLog(@"fail to open file");
    return 1;
}
   
[writeFile closeFile];

/** 기록된 파일 확인 */
NSData* readData = [NSData dataWithContentsOfFile:@"new.txt"];
NSString* readText = [[NSString alloc] initWithData: readData
    encoding: NSUTF8StringEncoding];

NSLog(@"READ: %@", readText);

3. 기존 파일 변경
파일 포인터(offset) 이동
seekToFileOffset은 C에서 fseek, lseek와 같이 파일의 특정위치로 이동하게 해줍니다. 아래에 사용된 [writeFile seekToFileOffset: 2]는 파일 포인터를 두번째 바이트에 위치시키며 이후로 writeData로 기록할 때는 두번째 바이트 뒤로부터 파일에 쓰여집니다. 이와 유사하게 seekToEndOfFile는 파일의 마지막으로 파일포인터를 이동합니다.

아래는 위에서 생성한 new.txt 파일의 세번째 바이트 위치부터 test.txt 파일의 내용을 추가하는 예입니다.
NSFileHandle *readFile;
NSFileHandle *writeFile;

readFile = [NSFileHandle fileHandleForReadingAtPath:@"test.txt"];
if(readFile == nil)
{
    NSLog(@"fail to read file");
    return 1;
}

writeFile = [NSFileHandle fileHandleForWritingAtPath:@"new.txt"];
if(writeFile == nil)
{
    NSLog(@"fail to open file");
    return 1;
}

NSData *data = [readFile readDataToEndOfFile];

[writeFile seekToFileOffset: 2];
[writeFile writeData: data];

[readFile closeFile];
[writeFile closeFile];
AND

이전 포스트에 이어서 프로그램을 완성해 보겠습니다. 이전의 소스에서 아래의 청색으로 된 부분을 추가합니다.

1. AppController.h 수정

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject {
    NSMutableString *curString;
    NSString* dataArray[3][2];
   
    IBOutlet NSTextField* displayString;
    IBOutlet NSTableView* wordList;
    IBOutlet NSMatrix* subjectMatrix;
    IBOutlet NSComboBox* adverbSelecter;
    IBOutlet NSColorWell* colorWell;
    IBOutlet NSSlider* alignSlider;
    IBOutlet NSButton* notChecker;
    IBOutlet NSProgressIndicator* progressBar;   
}

- (int)numberOfRowsInTableView:(NSTableView *)tableView;
- (id)tableView:(NSTableView *)tableView
    objectValueForTableColumn:(NSTableColumn *)tableColumn
            row:(int)row;

- (IBAction)setDisplayText:(id)sender;
- (IBAction)adverbSelecterChanged:(id)sender;
- (IBAction)alignSliderChanged:(id)sender;
- (IBAction)notCheckerChanged:(id)sender;
- (IBAction)subjectMatrixChanged:(id)sender;
- (IBAction)colorWellChanged:(id)sender;
- (IBAction)wordListChanged:(id)sender;
@end


2. 인터페이스 빌더에서 수정

변경한 소스를 저장한 후에 변경된 사항을 인터페이스 빌더에 적용하기 위해서 다시 AppController.h 파일아이콘을 드래그 하여 인터페이스 빌더의 MainMenu.nib에 놓습니다.

사용자 삽입 이미지
좌측과 같이 윈도우의 TableView중에 목적어 컬럼을 선택합니다. 속성창을 열어 Identifier 항목을 1로 설정합니다.

이와 동일하게 동사 컬럼의 Identifier는 2로 설정합니다.




사용자 삽입 이미지
AppController에서 새로 추가한 메소드인 wordListChanged르 TableView에 연결합니다.

TableView를 선택하고 Control키를 클릭한 채 마우스로 드래그 하여 AppController 인스턴스에 가져다 놓습니다. 좌측의 속성 화면에서 wordListChanged를 선택하고 연결합니다.

(연결이 어려우신 분들은 이전 포스트를 참조하세요.)


3. AppController.m 수정

#import "AppController.h"

@implementation AppController

- (id)init
{
    self = [super init];
   
    curString = [[NSMutableString alloc] init];

    /* 목적어 데이터 설정 */

    dataArray[0][0] = [[NSString alloc] initWithUTF8String:"철수를"];
    dataArray[1][0] = [[NSString alloc] initWithUTF8String:"영희를"];
    dataArray[2][0] = [[NSString alloc] initWithUTF8String:"바둑이를"];
   
    /* 동사 데이터 설정 */
    dataArray[0][1] = [[NSString alloc] initWithUTF8String:"사랑했다"];

    dataArray[1][1] = [[NSString alloc] initWithUTF8String:"싫어했다"];
    dataArray[2][1] = [[NSString alloc] initWithUTF8String:"때렸다"];
   
    return self;
}

- (void)awakeFromNib
{
}

- (void)dealloc
{
    int i, j;
   
    /* 할당된 오브젝트 해제 */
    for(i = 0; i < 3; i++)
    {
        for(j = 0; j < 2; j++)
        {
            [dataArray[i][j] dealloc];
        }
    }
   
    [curString dealloc];
    [super dealloc];
}

- (IBAction)setDisplayText:(id)sender
{
    int pos = [wordList selectedRow]; // 테이블뷰에서 현재 선택된 열
    if(pos == -1) // 없으면 실행하지 않는다
        return;
   
    /** 조건에 따른 문자열 설정 */
    if([[subjectMatrix selectedCell] tag] == 0)
        [curString setString:[NSString stringWithUTF8String:"나는 "]];
    else
        [curString setString:[NSString stringWithUTF8String:"너는 "]];
   
    [curString appendString:dataArray[pos][0]];
    [curString appendString:@" "];
    [curString appendString: [adverbSelecter stringValue]];
       
    if([notChecker state] == NSOnState)
        [curString appendString:[NSString stringWithUTF8String:" 안 "]];
   
    [curString appendString:dataArray[pos][1]];
   
    /* 색상 설정 */
    [displayString setTextColor:[colorWell color]];
   
    /* 정렬 설정 */
    int align = [alignSlider intValue];

    if(align == 0)
        [displayString setAlignment:NSLeftTextAlignment];
    else if(align == 50)
        [displayString setAlignment:NSCenterTextAlignment];
    else
        [displayString setAlignment:NSRightTextAlignment];
   
    /* 텍스트 출력 */
    [displayString setStringValue:curString];
   
    /* 진행 상태바 에니메이션 중지 */
    [progressBar stopAnimation:self];

}

- (IBAction)adverbSelecterChanged:(id)sender
{
    /* 변경시 진행 상태바 에니메이션 시작 */
    [progressBar startAnimation:self];
}

- (IBAction)alignSliderChanged:(id)sender
{
    [progressBar startAnimation:self];
}

- (IBAction)notCheckerChanged:(id)sender
{
    [progressBar startAnimation:self];
}

- (IBAction)subjectMatrixChanged:(id)sender
{
    [progressBar startAnimation:self];
}

- (IBAction)colorWellChanged:(id)sender
{
    [progressBar startAnimation:self];
}

- (IBAction)wordListChanged:(id)sender
{
    [progressBar startAnimation:self];
}

- (int)numberOfRowsInTableView:(NSTableView *)tableView
{
    /* 테이블 열 갯수 3 반환 */
    return 3;

}

- (id)tableView:(NSTableView *)tableView
    objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
{
    int pos;

    /** 현재 설정되어야 할 TableView의 컬럼을 반환한다. */
    if([[tableColumn identifier] characterAtIndex:0] == '1')
        pos = 0;
    else
        pos = 1;
   
    return dataArray[row][pos];
}

@end


사용자 삽입 이미지
이제 빌드를 하고 실행 하시면 좌측과 같이 어플리케이션이 실행됩니다. 각각의 옵션을 선택하고 [적용] 버튼을 클릭하면 선택된 옵션에 맞추어 상단에 텍스트가 출력됩니다.

옵션에 변동이 일어나면 하단의 바가 에니메이션되며 [적용] 버튼을 클릭하여 적용이 완료되면 진행 상태바의 에니메이션이 중지 됩니다.

전체 프로젝트 소스파일은 아래를 클릭해서 다운로드 받이스면 됩니다.







AND

사용자 삽입 이미지
흔히 사용되는 컨트롤들의 간단한 사용법을 알아 보겠습니다.
만들려는 예제의 모습은 좌측과 같습니다. 각 컨트롤들의 설정에 따라서 상단 텍스트 필드의 내용을 변경하여 보여주는 샘플을 작성해 보겠습니다.

이 예제를 따라 해보기 위해서는 인터페이스 빌더의 사용법과  인스턴스 생성, 인스턴스와의 연결 등에 관한 기본 내용을 알고 있어야 합니다. 잘 이해가지 않는 분들은 이전 포스트를 확인해 보신 후에 다시 보시기 바랍니다.



1. AppController 생성

XCode에서 Cocoa Application으로 새로운 프로젝트를 생성합니다. 다시 New File에서 Objective-C Class를 선택한 후에 AppController란 이름으로 새로운 클래스를 생성합니다.

1) AppController.h 변경
이번 장에서 사용할 컨트롤들은 아래와 같습니다. 아래의 컨트롤들을 인터페이스 빌더에서 연결하기 위해 IBOutlet 변수들을 생성합니다.
NSTextField, NSTableView, NSMatrix, NSComboBox, NSColorWell, NSSlider, NSButton, NSProgressIndicator  

사용자의 조작으로 컨트롤의 상태가 변하는 알림 메시지를 받을 메소드를 생성합니다. 각 메소드는 아래와 같습니다.

- (IBAction)setDisplayText:(id)sender;
- (IBAction)adverbSelecterChanged:(id)sender;
- (IBAction)alignSliderChanged:(id)sender;
- (IBAction)notCheckerChanged:(id)sender;
- (IBAction)subjectMatrixChanged:(id)sender;
- (IBAction)colorWellChanged:(id)sender;

AppController.h
#import <Cocoa/Cocoa.h>

@interface AppController : NSObject {
    IBOutlet NSTextField* displayString;
    IBOutlet NSTableView* wordList;
    IBOutlet NSMatrix* subjectMatrix;
    IBOutlet NSComboBox* adverbSelecter;
    IBOutlet NSColorWell* colorWell;
    IBOutlet NSSlider* alignSlider;
    IBOutlet NSButton* notChecker;
    IBOutlet NSProgressIndicator* progressBar;   
}

- (int)numberOfRowsInTableView:(NSTableView *)tableView;
- (id)tableView:(NSTableView *)tableView
    objectValueForTableColumn:(NSTableColumn *)tableColumn
            row:(int)row;

- (IBAction)setDisplayText:(id)sender;
- (IBAction)adverbSelecterChanged:(id)sender;
- (IBAction)alignSliderChanged:(id)sender;
- (IBAction)notCheckerChanged:(id)sender;
- (IBAction)subjectMatrixChanged:(id)sender;
- (IBAction)colorWellChanged:(id)sender;

@end


2) AppController.m 변경

사용자 삽입 이미지
TableView는 좌측과 같이 테이블 형식으로 목록을 보여주는 컨트롤입니다. 각 셀에 데이터를 입력하기 위해서는 데이터를 등록하여 주는 dataSource를 지정해야 합니다.

여기서는  나중에 인터페이스 빌더에서 AppController를 dataSource로 등록하겠습니다. tableView의 dataSource가 되면 데이터를 요구하는 메시지에 응답하기 위해서 아래와 같은 두개의 메소드를 구현해야 합니다.

- (int)numberOfRowsInTableView:(NStableView *)tableView;
목록의 갯수를 반환합니다.

- (id)tableView:(NSTableView *)tableView
    objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row

각 셀의 데이터를 반환합니다. 이 반환된 값들로 각 셀들이 채워집니다. 셀에 데이터를 등록하는 것은 다음 포스팅에서 구현해 보겠습니다.

메시지를 처리하는 각 메소드들은 실제 기능을 구현하기 전에 정확히 동작하는지 확인하기 위해서 NSLog를 추가합니다. 소스파일 전체는 아래와 같습니다.

AppController.m
#import "AppController.h"

@implementation AppController

- (void)awakeFromNib
{
}

- (void)dealloc
{
    [super dealloc];
}

- (IBAction)setDisplayText:(id)sender
{
    NSLog(@"Button colicked");
}

- (IBAction)adverbSelecterChanged:(id)sender
{
    NSLog(@"adverbSelecter changed");
}

- (IBAction)alignSliderChanged:(id)sender
{
    NSLog(@"alignSlider Changed");
}

- (IBAction)notCheckerChanged:(id)sender
{
    NSLog(@"notChecker changed");
}

- (IBAction)subjectMatrixChanged:(id)sender
{
    NSLog(@"subject matix");
}

- (IBAction)colorWellChanged:(id)sender
{
    NSLog(@"colorWell changed");
}

- (int)numberOfRowsInTableView:(NSTableView *)tableView
{
    NSLog(@"tableView getRowCount");
    return 1;
}

- (id)tableView:(NSTableView *)tableView
    objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
{
    NSLog(@"tableView getColumnValue");
    return nil;
}

@end


2. 인터페이스 빌더에서 작업
 
1) 컨트롤 배치

윈도우에 각 컨트롤들이 배치되어야 할 모습은 아래와 같습니다.

사용자 삽입 이미지

사용자 삽입 이미지
팔레트에서 마우스 포인터를 해당 컨트롤 위에 놓으면 좌측의 빨간 화살표가 가르키는 것과 같이 컨트롤의 종류를 확인할 수 있습니다.

위의 이미지를 참조하여 팔레트에서 각각의 콘트롤들을 드래그로 가지고 와서 배치 합니다.





2) 속성 설정
각각의 콘트롤들의 속성을 변경합니다.

NSTextField
사용자 삽입 이미지
텍스트필드의 옵션을 좌측과 같이 설정합니다. 사용자의 입력은 받지 않기 때문에 Editable, Enabled를 해제합니다.


NSTableView
사용자 삽입 이미지
Attributes에서 Columns를 2로 설정합니다.
NSTableView를 더블클릭하면 좌측과 같이 푸른 테두리가  나타납니다. 이 상태에서 컬럼을 더블클릭하여 입력 모드로 변경되면 이름을 변경할 수 있습니다.

각각 목적어, 동사로 변경합니다.

NSMatrix
Rows는 1 Cols는 2로 설정하고 Mode가 'Radio'로 되어 있음을 확인합니다.

NSComboBox
사용자 삽입 이미지
좌측과 같이 Visible Items를 4로 입력하고 4개의 항목을 추가합니다.

추가는 하단의 입력필드에서 이름을 입력 후에 [+] 버튼을 클릭합니다. 삭제는 아이템을 선택한 후에 [-]버튼을 클릭합니다.



NSColorWell
변경없이 기본값을 유지합니다.

NSSlider
사용자 삽입 이미지
좌측과 같이 최소값을 0.0, 최대값을 100.0, 기본값을 50.0으로 설정합니다. 세단계만 선택되게 하기 위해 Number Of Markers를 3으로  Stop on ticks marks only를 체크 합니다.


NSButton
Type이  'CheckBox'로 되어 있음을 확인하고 Title에 '부정어로 만듭니다.'로 입력합니다.

NSProgressIndicator  
MinumRange는 0.0, MaximumRange는 100.0으로 설정하고 Indeterminate를 체크 합니다.

2) AppControll 인스턴스 생성
AppController.h를 인터페이스 빌더의 MainMenu.nib 윈도우로 드래그 해 놓습니다. Classes 항목에서 AppController를 우클릭하여 Instantiate AppController를 선택하여 인스턴스를 생성합니다.

이제 아래의 연결된 모습을 참조하여 윈도우의 각 항목들을 AppController의 IBOutlet 변수와 IBAction 메소드로 연결합니다.
사용자 삽입 이미지

각 컨트롤들은 아래의 AppController의 IBAction 메소드와 연결됩니다.
  • adverbSelecterChanged <- NSComboBox
  • alignSliderChanged <- NSSlider
  • notCheckerChanged <-NSButton (Check Box)
  • subjectMatrixChanged <- NSMatrix
  • colorWellChanged <- NSColorWell
  • setDisplayText <- NSButton (적용 Button)
 
사용자 삽입 이미지
마지막으로 위에 설명한 대로 좌측과 같이  TableView의 dataSource를 AppControll로 설정합니다.








이제 컴파일 하고 실행한 후에 각각의 콘트롤의 값과 상태를 변경하여 봅니다. 그리고 RunLog 창에서 아래와 같이 메시지를 보내고 받는지 확인합니다.

잘 동작하지 않는 분들은 아래의 샘플 파일을 다운로드 받으신 후에 비교해 보시기 바랍니다.
사용자 삽입 이미지

AND

기타 2007. 11. 30. 14:31
Xcode란 이름을 처음 들었을 때는 별다른 느낌이 없었습니다. 한 단어로 자주 사용되는 X가 주는 여러 의미도 있고, OS X의 X도 있고 code와 합쳐져 그냥 Xcode인가 보다 하고 생각했습니다.

하지만 cocoa는 약간 흥미가 가는 이름이었습니다. 처음에는 코코아란 차를 생각하고 Java와 뭔가 비슷한 이유로 이름을 지은 것이 아닐까 생각했습니다. cocoa 개발자들이 개발중에 코코아 차를 자주 마셔서 지은 이름이 아닐까? 별로 중요한 내용은 아니지만 cocoa를 시작도 하기전에 이 죽일 놈의 호기심이 또 발동해서 구글 등에서 이름의 내역을 대충 알게되었습니다.

이제 다시 당시에 알아낸 내용과 위키피디아를 통해서 cocoa 이름의 유래에 관해서 정리해 볼려고 합니다.


사용자 삽입 이미지
우선 코코아가 정확히 뭔지 알아 보겠습니다. 저는 카카오, 코코아, 코코넛 세개의 정확한 의미를 몰라 혼돈이 있었습니다. 코코넛은 코코스 야자 나무의 열매이고, 카카오는 카카오 나무의 열매입니다. 이 카카오를 가공하여 분말로 만든 것을 코코아라고 부릅니다.(저만 헤깔리는 것이 겠죠.)


일단 코코아가 무엇인지는 알았고, Xcode의 cocoa에 이름이 붙여진 이유에 대해서 알아 보겠습니다. 1991년도 경 애플에서는 KidSim이란 프로젝트가 진행되고 있었습니다. 이 프로젝트는  ATG(Advanced Technology Group)에서 이루어졌습니다. KidSim은 Kid+Simulation의 조합과 같은 이름에서 암시하듯이 프로그래밍 언어를 배우지 않고도 어린이들에게 쉽게 애니메이션, 시뮬레이션, 게임등을 만들게 해주는 툴입니다.


보시는 바와 같이 어디선가 본듯한 모습인데 앨런케이옹의 스퀵이 떠 오릅니다. 아래는 스퀵과 관련한 제 이전 포스트 입니다.

아무튼 이 당시 앨런케이옹도 애플의 ATG에서 비슷한 연구를 한 것으로 보입니다.

잠시 ATG에 대해서 살펴 보면 ATG는 1986에 만들어져 1997까지 유지된 애플의 새로운 기술을 개발하는 연구소입니다. 구성원은 컴퓨터 발전과 객체지향에 지대한 영향을 끼친 앨런케이, 제록스에서 레이저 프린팅 시스템을 처음 개발한 게리 스타크웨더, 초기 애플 GUI 소프트웨어 개발의 주축이었던 레리 테슬러(ATG 를 만들었습니다.)등 초호화 맴버입니다.

드림팀 답게 이들은 이곳에서 애플 기술의 초석이 되는 많은 결과물들을 만들어 내었습니다. 위키피디아에서 가져온 이들이 개발한 주요 내용들 입니다.

QuickDraw, QuickTime, QuickTime VR, QuickDraw 3D, 3DMF the 3D metafile graphics format, ColorSync, HyperCard, Apple events, AppleScript, Apple's PlainTalk speech recognition software, Apple Data Detectors, the V-Twin software for indexing, storing, and searching text documents, Macintalk Pro Speech Synthesis, the Newton handwriting recognizer, the component software technology leading to OpenDoc, MCF and HotSauce, Squeak Smalltalk, Cocoa

대충봐도 눈에 익은 것들이 많습니다. 하지만 1996년 앨런케이도 애플을 떠나고, 1997년 스티브잡스가 애플로 복귀하면서 비생산적인 부서를 정리하는 구조조정(?)을 감행하면서 ATG도 역사속으로 사라지게 됩니다.

다시 KidSim으로 돌아와 1994년 이 프로젝트는 Cocoa로 이름을 변경합니다. 이후 1996년 이 프로젝트는 공식적으로 세간에 발표됩니다. 하지만 ATG가 없어 지면서 위의 래리 테슬러와 기존 cocoa의 개발자들은 Stagecast 소프트라는 회사를 만들어 떠납니다.

이때 스티브 잡스는 NeXTSTEP을 이용해 새로운 애플의 OS를 개발하게 됩니다. 애플은 Xcode의 시조인  NeXTSTEP의 개발환경을 새로운 OS로 가져 오게 됩니다. 그런데 새로 트레이드마크를 등록하는데 드는 시간을 줄이기 위해, Stagecast의 동의하에 이미 등록되어 있는 Cocoa란 이름을 사용합니다.

알고보니 허무합니다. 사실인지 아닌지 확인할 길은 없지만, 시간이 없어서 그냥 가지고 왔답니다. 끝.

아무튼 ATG에 당대의 개발자들이 모여 시간과 돈에 얽매이지 않고 자유롭게 연구한 UI, 그래픽/네트워크 기술, 교육용 소프트웨어 등 많은 결과물이 오늘날 애플 제품들의 초석이 되지 않았나 생각됩니다.


'기타' 카테고리의 다른 글

블로그 RSS 읽어오는 C++ class  (7) 2007.12.14
OS X용 정규 표현식 테스트 어플리케이션 - Reggy  (1) 2007.12.12
맥용게임 Doukutsu  (3) 2007.11.21
미국의 맥개발자 구인 내용  (8) 2007.10.20
맥용 게임 Thumblebugs  (0) 2007.10.19
AND

파이프(pipe)란 그 이름과 같이 한 프로세스의 표준 출력을 다른 프로세스의 표준입력으로 연결하여 주는 프로세스간의 통신을 위한 방법입니다. 파이프는 아래와 같이 많이 사용됩니다.

사용자 삽입 이미지

ps로 현재 프로세스를 출력하는데 결과가 파이프(|)를 통해 grep의 입력으로 전달됩니다. 그래서 출력 내은 grep으로 전달되고, 현재 프로세스중 iTerm의 문자열이 있는 프로세스만 출력해줍니다.

이번장에선 파이프를 이용하여 command line, GUI의 두 어플리케이션간에 통신을 하고, 프로젝트를 또 다른 프로젝트에 임포트 시키는 프로그램을 간단히 만들어 보겠습니다.


1. getDouble 커멘트라인 툴

먼저 커메드라인 툴을 만들어 보겠습니다.

Xcode의 New Project 메뉴를 클릭하시고, 프로젝트 타입에서 Command Line Utility/Starndard Tool 을 선택합니다. 전 getDouble이란 이름으로 프로젝트를 생성하였습니다. main.c 소스파일을 열고 아래의 내용을 추가합니다.

#include <stdio.h>

int main (int argc, const char * argv[]) {
    // insert code here...
    int num;
   
    scanf("%d", &num);
    printf("%d", num * 2);
   
    return 0;
}

보시는 바와 같이 사용자로 부터 한 수를 입력 받아 2를 곱해서 출력해주는 간단한 프로그램입니다. 테스트를 위해 빌드 후에 실행 합니다.
사용자 삽입 이미지
위와 같이 Run Log 창에서 3[return]을 입력하고, 결과는 두배인 6이 출력되었습니다. 이제 프로젝트를 닫습니다.


2. testPipe GUI 어플리케이션

다시 New Project를 클릭하고 Application/cocoa application를 선택합니다. 저는 이름을 위와 같이 testPipe로 하였습니다.

New File에서 Objective-C class를 선택하시고 AppController란 이름으로 새로운 클래스를 생성합니다. 아래의 내용을 추가합니다.

1) AppController.h 파일

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject {
    NSTask* task;
   
    /*
사용자로 부터 숫자를 입력받는 텍스트필드 입니다. */
    IBOutlet NSTextField* inputText;
   
    /*
입력받은 수를 처리한 결과를 보여 줄 텍스트필드 입니다. */
    IBOutlet NSTextField* outputText;
}

/* 버튼이 클릭되었을 경우에 입력 받은 값을 getDouble 을 실행시켜 넘겨 줍니다. */
- (IBAction) buttonClicked:(id) sender;

/*
getDouble로 부터 받은 결과값을 outputText에 출력합니다. */
- (void) setData: (NSNotification *) noti;

@end


2) AppController.m 파일

#import "AppController.h"

@implementation AppController

- (IBAction) buttonClicked:(id) sender
{
     /* getDouble을 실행하기 위해 NSTask를 사용합니다. NSTask는 한 어플리케이션에서 다른 어플리케이션을 서브프로세스로 실행시키고 관리할 수 있습니다. 리소스 폴더에 있는 getDouble을 실행합니다.

    */
    task = [[NSTask alloc] init];
    NSBundle* aabundle = [NSBundle mainBundle];
    NSString* appPath = [aabundle pathForResource: @"getDouble" ofType: @""];
   
    [task setLaunchPath: appPath];
   
    /*  입출력 파이프를 준비합니다. 유닉스에서는 파이프도 파일로 취급하기 때문에 NSFileHandle 오브젝트로 관리합니다.
    */
    NSPipe* inPipe = [[NSPipe alloc] init];
    NSPipe* outPipe = [[NSPipe alloc] init];
   
    NSFileHandle* inputFile = [inPipe fileHandleForWriting];
    NSFileHandle* outputFile = [outPipe fileHandleForReading];
   
    [task setStandardInput:inPipe];
    [task setStandardOutput:outPipe];
   
    [inPipe release];
    [outPipe release];
   
    /* getDouble로 부터의 출력이 종료되었을 때 호출된 함수(setData)를 설정하도록 옵저버로 등록합니다.
    */

    [[NSNotificationCenter defaultCenter]
        addObserver: self
           selector: @selector(setData:)
               name: NSFileHandleReadToEndOfFileCompletionNotification
             object: outputFile];
    
    /* 백그라운드로 파일을 읽고 종료시에 
읽은 데이터를 NSDicionary 타입의 userInfo에 NSFileHandleNotificationDataItem 키로 읽은 데이터를 넘겨줍니다.
     */

    [outputFile readToEndOfFileInBackgroundAndNotify];
   
    /* getDouble을 실행합니다. */
    [task launch];
   
    /* 사용자가 입력한 값을 파이프(inputFile)를 통해 getDouble 프로세스에 넘겨 줍니다. */
    NSString* str = [inputText stringValue];
    NSData* data = [str dataUsingEncoding:NSASCIIStringEncoding];
    [inputFile writeData: data];
   
    [inputFile closeFile];

}

- (void) setData: (NSNotification *) noti
{
    /* getDouble에서 printf로 출력한 값을 텍스트필드에 출력합니다. */
    NSDictionary* dic = [noti userInfo];
    NSData* data = [dic objectForKey: NSFileHandleNotificationDataItem];
   
    NSString* str = [[NSString alloc] initWithData: data encoding:NSASCIIStringEncoding];

    [outputText setStringValue: str];
   
    [str release];
    [task release];
}

@end


3) 인터페이스 빌더에서 작업

아래의 작업이 이해가 가시지 않는 분은 이전 포스트를 참조해주세요.

사용자 삽입 이미지
위의 소스파일을 저장하고 인터페이스빌더를 실행하고 Window에 텍스트필드 2개와 버튼하나를 좌측과 같이 위치시킵니다.




Xcode의 Groups & Files에서 AppController.h을 드래그하여 열려진 MainMenu.nib의 Instances 판넬에 놓습니다. 그리고 Classes에서 AppController를 우클릭하여 Instantiate AppController를 실행하여 인스턴스를 생성합니다.

AppController를 컨트롤키와 함께 드래그하여 좌측의 텍스트 필드를 inputText 아울렛에 우측의 텍스트 필드를 outputText 아울렛에 연결합니다. 반대로 버튼 컨트롤을 컨트롤키와 함께 AppConrtoller 인스턴스로 드래그하여  buttonClicked 액션에 연결합니다.

각각의 인스펙터에서 아래와 같이 연결되었음을 확인합니다.
사용자 삽입 이미지

 
3. getDouble 프로젝트 추가

실행 또는 배포 시에는 번들의 리소스 디렉토리에 getDouble 실행 파일을복사해 놓으시면 되지만 여기서는 getDouble 프로젝트를 testPipe 프로젝트로 임포트 시켜보겠습니다.

사용자 삽입 이미지

위와 같이 getDouble 프로젝트의 디렉토리에서 getDouble.xcodeproj 파일을 드래그 하여 Xcode(testPipe) 좌측의 Groups & Files에 Resources  하위에 놓습니다.

이제 getDouble이 변경되었을 때에도 testPipe에서 반영될 수 있도록 Dependencies에 추가합니다.
사용자 삽입 이미지
Groups & Files의 Targets 서브의 testPipe를 더블클릭하여 좌측의 info 창을 오픈합니다. 좌측 하단의 [+] 버튼을 클릭하여 getDouble을 선택하시고 좌측과 같이 추가된 것은 확인합니다.



이제 모든 작업이 완료되었습니다. getDouble이 정확히 포함되었는지 아래 이미지와 같이 세개의 붉은색 화살표 부분을 확인합니다.

사용자 삽입 이미지

testPipe를 빌드 후에 터미널에서 아래와 같이 리소스 디렉토리에 getDouble 실행파일을 확인합니다.
사용자 삽입 이미지


4. 실행 및 확인

사용자 삽입 이미지

빌드 후에 testPipe를 실행합니다. 위와 같이 3을 입력한 후에 [X 2] 버튼을 클릭하면 getDouble을 실행하고 파이프를  통해 3을 전달합니다. getDouble은 scanf를 통하여 파이프로 들어온 값에 2를 곱하고 출력합니다. 이 출력값은 다시 설정된 파이프를 통해 testPipe에서 받은 후 출력합니다.

이번 장에선 간단히 파이프에 대해서 알아 보았습니다. 파이프는 소켓처럼 네트워크를 통해서 프로세스간에 통신을 할 수는 없지만 로컬에서 사용할 수 있는 간단한 방법입니다. 직접 구현을 하지 않고도 존재하는 실행파일 또는 프로세스에서 간편한게 정보를 가져오거나 가공할 수 있는 편리한 방법을 제공합니다.

유닉스에서 간단하고 직관적으로 pipe를 사용하던 방법과 비교해 보면 cocoa의 특징을 잘 보여주는 것 같습니다.

'Xcode 2 > Tip' 카테고리의 다른 글

Xcode에서 Flex 개발하기  (2) 2007.12.08
Xcode에서 파이어폭스 플러그인 컴파일 하기  (0) 2007.12.04
Cocoa에서 MySQL 라이브러리 사용하기  (6) 2007.11.13
Xcode에 Subversion 적용하기  (6) 2007.11.10
Xcode 단축키  (2) 2007.10.26
AND

코아에서 MySQL C Library를 이용하는 예입니다. wrapper 클래스를 작성하여 MySQL
서버에 접속해서 데이터를 가져오는 간단한 샘플 코드를 작성해 보겠습니다.

OS X에서 MySQL 설치 및 설정은 이전 포스트를 참고해 하세요.


1. MySQL에서 작업

1) MySQL 서버 확인
테스트를 위하여 테이블을 생성하고 데이터를 넣어 보겠습니다. 우선 시스템 환경설정의 MySQL 항목에서 아래와 같이 서버가 기동중인지 확인 합니다. 서버가 실행되지 않고 있으면 Start 버튼을 클릭하여 아래와 같이 실행 상태로 만듭니다.
사용자 삽입 이미지

2) 테이블 생성 및 데이터 입력
그 다음 아래와 같이 터미널에서  MySQL에 로그인 후에 member 테이블을 생성하고 데이터를 넣습니다. 저는 이전에 test/1111로 계정을 만들고 cocoadev란 데이터베이스를 만들어 두었기에 아래와 같이 접속하였습니다.
사용자 삽입 이미지

id, name 두개의 필드를 가진 member란 테이블을 생성하고, 테스트를 위해 1, cocoa 값으로 데이터를 입력합니다.

2. 소스코드 작성
Xcode를 실행하고 프로젝트를 생성합니다. 저는 최대한 간단하게 만들기 위해 프로젝트 타입을 Command Line Utility의 Foundation Tool로 선택하고 프로젝트를 생성하였습니다.

Wrapper 클래스 작성을 위하여 새 파일에서 Objective C class 타입으로 MySqlDB 클랙스를 생성합니다. 이제 MySqlDB.h 파일과 MySqlDB.m 파일을 작성합니다.

테스트를 위하여 연결 및 쿼리등 필요한 메소드만 작성해 보겠습니다. mysql.h 파일을 참고 하시면 MySQL 라이브러리에서 제공하는 많은 함수들을 확인하실 수 있습니다.

1) MySqlDB.h 편집
#import <Cocoa/Cocoa.h>
#import "mysql.h"

@interface MySqlDB : NSObject {

    bool isConnected;
    int rowCount;
   
    MYSQL mySQL;
    MYSQL *pMySQL;
   
    MYSQL_RES* pRes;
    MYSQL_ROW Rows;
}

-(id) initWithServer: (const char*) host
             loginID: (const char*) userid
         loginPasswd: (const char*) passwd
               setDB: (const char*) database;

-(bool) connect : (const char*) host
         loginID: (const char*) userid
     loginPasswd: (const char*) passwd
           setDB: (const char*) database;

-(bool) query :(const char*) str;
-(bool) getRows;
-(bool) getStringData: (int)idx toBuffer: (char *) buff;

-(bool) isConnected;
-(int) rowCount;

@end

2) MySqlDB.m 파일 편집
#import "MySqlDB.h"


@implementation MySqlDB

-(id) initWithServer: (const char*) host
             loginID: (const char*) userid
         loginPasswd: (const char*) passwd
               setDB: (const char*) database
{   
    self = [super init];
   
    [self connect: host
          loginID: userid
      loginPasswd: passwd
            setDB: database];
               
    return self;
}

- (void)dealloc
{
    if(pMySQL)
        mysql_close(pMySQL);
   
    [super dealloc];
}

-(bool) connect : (const char*) host
         loginID: (const char*) userid
     loginPasswd: (const char*) passwd
           setDB: (const char*) database
{
    isConnected = FALSE;
   
    pMySQL = mysql_init(&mySQL);
    if(pMySQL == NULL)
    {
        return FALSE;
    }
   
    pMySQL = mysql_real_connect(&mySQL, host, userid, passwd, database, 0, 0, 0);
    if(pMySQL == NULL)
    {
        return FALSE;
    }
   
    isConnected = TRUE;
   
    return TRUE;
}

-(bool) query: (const char*) str
{
    if(!pMySQL)
        return FALSE;
   
    int ret = mysql_query(pMySQL, str);
    if(ret != 0)
    {
        return FALSE;
    }
   
    pRes = mysql_store_result(pMySQL);
    rowCount = mysql_num_rows(pRes);
   
    NSLog(@"rows:%d", rowCount);
    return TRUE;
}

-(bool) getStringData: (int)idx toBuffer: (char *) buff;
{
    if(Rows[idx] == NULL)
        return FALSE;
   
    strcpy(buff, Rows[idx]);
    return TRUE;
}

-(bool) getRows
{
    Rows = mysql_fetch_row(pRes);
   
    if(Rows == NULL)
        return FALSE;

    return TRUE;
}

-(bool) isConnected
{
    return isConnected;
}

-(int) rowCount
{
    return rowCount;
}

@end

3) MyTest.m 편집
이제 테스트를 위하여 위에 작성된 클래스를 사용하여 MySQL서버에 접속하여 데이터를 가져오도록  MyTest.m 파일에 소스를 추가해 보겠습니다. 연결시 로그인 정보와 데이터 베이스, 쿼리 내용은 자신의 환경에 맞게 변경해 줍니다.

#import <Foundation/Foundation.h>
#import "MySqlDB.h"

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // insert code here...
    MySqlDB* myDB = [[MySqlDB alloc] initWithServer:"localhost"
                                            loginID:"test"
                                        loginPasswd:"1111"
                                              setDB:"cocoadev"];
   
    if([myDB isConnected] == true)
    {
        if([myDB query :"SELECT * FROM member"])
        {
            char buff1[128];
            char buff2[128];
           
            [myDB getRows];
            [myDB getStringData: 0 toBuffer: buff1];
            [myDB getStringData: 1 toBuffer: buff2];
           
            NSLog(@"DATA: id=%s, name=%s", buff1, buff2);
        }
        else
            NSLog(@"Fail to query");
    }
    else
        NSLog(@"Fail to connect.");
   
    [myDB release]
    [pool release];
    return 0;
}


3. 빌드옵션 설정 및 라이브러리 등록

MySQL 라이브러리를 사용하기 위해서는 링크시에 라이브러리를 추가하도록 등록하고, 헤더파일을 인크루드하기 위해 위치를 지정해 주어야 합니다.

GCC 옵션에서 -I(인클루드 패스 지정), -L(라이브러리 패스 지정), -l(라이브러리 추가) 옵션을 생각하시면 됩니다.

1) include 패스 설정
빌드시 헤더파일을 찾을 수 있도록 링크를 설정합니다. 프로젝트 정보창을 오픈합니다. Build 항목에서 "User Header Search Path"에 mysql의 include 패스를 입력합니다. 대부분 /usr/local/mysql에 설치되는데 다른 곳에 설치하신 분들은 그 곳의 패스를 입력합니다.
사용자 삽입 이미지

2. libmysqlclient.a 라이브러리 링크
사용자 삽입 이미지
파인더에서 해당 디렉토리에 접근하기 힘들기 때문에 터미널에서
 > open /usr/local/mysql/lib 로 파인더를 오픈합니다.

libmysqlclient.a 파일을 드래그 하여 좌측과 같이 Xcode의 Frameworks 그룹으로 가지고 옵니다.




이제 모든 준비가 완료되었습니다. 빌드 후 실행하여 아래와 같은 결과를 확인합니다.
사용자 삽입 이미지

코코아에서 MySQL 라이브러리를 사용하여 MySQL에 연결하는 방법을 간단히 알아 보았습니다. PostgreSQL이나 다른  C 라이브러리들도 위와 같은 방법으로 코코아에서 사용하실 수 있습니다.

티스토리 블로그 알리미를 작성한 적이 있는데, 설치형 블로그들은 위와 같이 DB에 직접 쿼리하는 방법으로 더욱 쉽고, 다용하고, 정확한 정보를 가져오는 툴을 작성할 수 있을 것 같습니다.

'Xcode 2 > Tip' 카테고리의 다른 글

Xcode에서 파이어폭스 플러그인 컴파일 하기  (0) 2007.12.04
pipe를 이용한 간단한 프로세스간의 통신  (6) 2007.11.28
Xcode에 Subversion 적용하기  (6) 2007.11.10
Xcode 단축키  (2) 2007.10.26
#pragma mark  (2) 2007.08.04
AND

사용자 삽입 이미지
이번 장에서는 적기들을 출력하고 동작하게 하는 작업을 해보겠습니다.

적기는 좌측과 같이 세 종류로 상단에 위치해 있으며 인베이더 게임과 같이 좌우로 움직이며 아래로 내려 옵니다.

마지막 적기가 화면에서 사라지면 다음 스테이지로 넘어가며, 스테이지가 진행될 수록 적기의 움직임이 빨라 집니다.




1.8.5 소스 파일 추가

1) global.h

게임에서 사용되는 여러 속성들의 공유를 위해 global.h 헤더파일을 작성합니다. XCode의 메뉴에서 File/New File을 클릭한 후, BSD/Header File을 선택하고 [Next] 버튼을 클릭합니다. 파일명을 global.h로 입력하고  [Finish] 버튼을 클릭합니다.

이제 에디터에서 global.h를 열어서 아래와 같이 내용을 입력합니다.
#define SCREEN_WIDTH        300        /* 화면 너비 */
#define SCREEN_HEIGHT       320        /* 화면 길이 */

#define HERO_SPEED          3       /* 우주선 속도 */
#define MISSILE_SPEED       4       /* 총알발사 속도 */

#define MAX_ENEMY1            5        /* 적기1 갯수 */
#define MAX_ENEMY2            3        /* 적기2 갯수 */
#define MAX_ENEMY3            1        /* 적기3 갯수 */

#define ENEMY_DOWN            20        /* 적기 하강 픽셀 */

이전 StageView.m에 있던 SCREEN_WIDTH및 위와 중복되는 define된 부분 (검은색)을StageView.m에서 삭제 합니다.  그리고 StageView.m 상단에 아래와 같이 global.h를 임포트 합니다.
 
#import "global.h"
#import "StageView.h"


2) Enemy 오브젝트 추가

최대한 간단하게 만들기 위해 class 추가 없이 StageView.m에서 다 해결 할라고 했는데, 적기 때문에 너무 복잡해 질 것 같아 Enemy 클래스를 추가하기로 하였습니다.
 
XCode의 메뉴에서 File/New File을 클릭한 후, Cocoa/Objective-C class를 선택하고 [Next] 버튼을 클릭합니다. 파일명을 Enemy.m으로 입력하고  [Finish] 버튼을 클릭합니다.

설명은 소스에 간단한 주석으로 대치 합니다.

Enemy.h
#import <Cocoa/Cocoa.h>

@interface Enemy : NSObject {
    int type;
    int energy;
    int state;
   
    NSImage* image;
    NSPoint position;
}

- (id)initWithImage:(NSImage*)img
               type:(int)t;

- (int)state;
- (void)setState:(int)s;
- (void)setPosition:(NSPoint)pos;

- (void)down;
- (BOOL)moveAndDisplay:(int)dir;

@end
type과 energy는 아직 사용하지 않습니다.

Enemy.m
#import "global.h"
#import "Enemy.h"

@implementation Enemy

/** 적기 타입과 이미지를 설정 */
- (id)initWithImage:(NSImage*)img
           type:(int)t
{
    [super init];
   
    type = t;
    image = img;

    return self;
}

- (void)setPosition:(NSPoint)pos
{
    position = pos;
}

- (int)state
{
    return state;
}

- (void)setState:(int)s
{
    state = s;
}

/** 적기를 한단계 아래로 옮기고, 화면에 안 나올 경우에는 상태를 0으로 변경한다. */
- (void)down
{
    position.y -= ENEMY_DOWN;
    if(position.y + [image size].height < 0)
        state = 0;
}

- (BOOL)moveAndDisplay:(int)dir
{
    BOOL isChange = false;
    NSRect drawRect;
    NSRect imgRect;
       
    drawRect.size = [image size];
   
    /** 적기를 이동하고 좌우 화면의 경계를 넘었을 경우에는 아래로 이동하도록 isChange를 1로 세팅 해서 반환. */
    position.x += dir;
    if(position.x < 0)
    {
        isChange = true;
    }   
    if(position.x >= SCREEN_WIDTH - drawRect.size.width)
    {
        isChange = true;
    }
   
    /** 적기를 출력한다. */
    drawRect.origin.x = position.x;
    drawRect.origin.y = position.y;
   
    imgRect.origin = NSZeroPoint;
    imgRect.size = [image size];
   
    [image drawInRect:drawRect
                       fromRect:imgRect
                      operation:NSCompositeSourceOver
                       fraction:1.0];   
   
    return isChange;
}

@end


1.8.6 StageView 변경
다음은 스테이지를 설정하고, 적기를 출력 하기 위해 Stageview 클래스를 변경해 보겠습니다. 스테이지가 증가할 수록 적기의 이동속도가 빨라 집니다.

StageView.h
/* StageView */

#import <Cocoa/Cocoa.h>

@class Enemy;

@interface StageView : NSView
{
    NSTimer *timer;
    NSImage *backgroundImage;
   
    NSRect heroRect;
    NSImage *heroImage;

    BOOL isFire;
    NSRect missileRect;
    NSImage *missileImage;
   
    int curStage;
    int enemyDir;

    NSImage *enemyImage1;
    NSImage *enemyImage2;
    NSImage *enemyImage3;
   
    Enemy *Enemy1[MAX_ENEMY1];
    Enemy *Enemy2[MAX_ENEMY2];
    Enemy *Enemy3[MAX_ENEMY3];
}

- (void)setStage;
- (void)processGame;
- (void)fireMissile;
- (void)keyDown:(NSEvent *)event;

@end

StageView.m
#import "global.h"
#import "StageView.h"
#import "Enemy.h"

@implementation StageView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) != nil) {
        /* 배경 이미지 로드 */
        NSString* imageName = [[NSBundle mainBundle] pathForResource:@"background" ofType:@"png"];
        backgroundImage = [[NSImage alloc] initWithContentsOfFile:imageName];
       
        /* 우주선 이미지 로드 & 초기 좌표 설정 */
        imageName = [[NSBundle mainBundle] pathForResource:@"hero" ofType:@"png"];
        heroImage = [[NSImage alloc] initWithContentsOfFile:imageName];
        heroRect.size = [heroImage size];
        heroRect.origin = NSMakePoint((SCREEN_WIDTH - heroRect.size.width)/2, 0);
       
        /* 미사일 이미지 로드 & 초기 좌표 설정 */
        imageName = [[NSBundle mainBundle] pathForResource:@"missile" ofType:@"png"];
        missileImage = [[NSImage alloc] initWithContentsOfFile:imageName];
        missileRect.size = [missileImage size];
        missileRect.origin = NSMakePoint(heroRect.origin.x + heroRect.size.width/2 - missileRect.size.width/2, heroRect.size.height);
       
        /* 적기1 이미지 로드 */
        imageName = [[NSBundle mainBundle] pathForResource:@"enemy1" ofType:@"png"];
        enemyImage1 = [[NSImage alloc] initWithContentsOfFile:imageName];
       
        /* 적기2 이미지 로드 */
        imageName = [[NSBundle mainBundle] pathForResource:@"enemy2" ofType:@"png"];
        enemyImage2 = [[NSImage alloc] initWithContentsOfFile:imageName];
       
        /* 적기3 이미지 로드 */
        imageName = [[NSBundle mainBundle] pathForResource:@"enemy3" ofType:@"png"];
        enemyImage3 = [[NSImage alloc] initWithContentsOfFile:imageName];
               
        int i;
       
        /** 적기 오브젝트 생성 */
        for(i = 0; i < MAX_ENEMY1; i++)
        {
            Enemy1[i] = [[Enemy alloc] initWithImage:enemyImage1
                                                type:1];
        }
        for(i = 0; i < MAX_ENEMY2; i++)
        {
            Enemy2[i] = [[Enemy alloc] initWithImage:enemyImage2
                                                type:2];
        }
        for(i = 0; i < MAX_ENEMY3; i++)
        {
            Enemy3[i] = [[Enemy alloc] initWithImage:enemyImage3
                                                type:3];
        }
       
        /** 1 스테이지 설정 */
        curStage = 0;
        [self setStage];
       
        /* 30프레임으로 타이머 설정 */
        timer = [[NSTimer scheduledTimerWithTimeInterval: (1.0f / 30.0f)
                                                  target: self
                                                selector:@selector(processGame)
                                                userInfo:self
                                                 repeats:true] retain];       
    }
    return self;
}

/** 할당된 메모리 반환 */
- (void) dealloc
{
    int i;
   
    [backgroundImage release];
    [heroImage release];
    [missileImage release];
   
    [enemyImage1 release];
    [enemyImage2 release];
    [enemyImage3 release];

    for(i = 0; i < MAX_ENEMY1; i++)
    {
        [Enemy1[i] release];
    }
   
    for(i = 0; i < MAX_ENEMY2; i++)
    {
        [Enemy2[i] release];
    }
   
    for(i = 0; i < MAX_ENEMY3; i++)
    {
        [Enemy3[i] release];
    }
   
    [super dealloc];
}

- (void) setStage
{
    int i;
    NSPoint pos;
   
    /* 현재 스테이지를 1 증가 시킨다. */
    curStage++;
       
    /* 적기의 좌/우 움직이는 속도와 방향 설정. */
    enemyDir = curStage;

    isFire = NO;
   
    /** 적기들의 초기 위치와 상태를 설정한다. */
    pos.x = 45;
    pos.y = 190;
    for(i = 0; i < MAX_ENEMY1; i++)
    {
        [Enemy1[i] setPosition:pos];
        [Enemy1[i] setState:1];
   
        pos.x += 45;
    }

    pos.x = 78;
    pos.y = 225;
    for(i = 0; i < MAX_ENEMY2; i++)
    {
        [Enemy2[i] setPosition:pos];
        [Enemy2[i] setState:1];
   
        pos.x += 55;
    }

    pos.x = 122;
    pos.y = 265;
    for(i = 0; i < MAX_ENEMY3; i++)
    {
        [Enemy3[i] setPosition:pos];
        [Enemy3[i] setState:1];
   
        pos.x += 80;
    }
}

/** 키입력을 받기 위한 설정 */
- (BOOL)acceptsFirstResponder
{
    return YES;
}

- (void)keyDown:(NSEvent *)event
{
    int keyCode;
   
    /* 현재 눌려진 키값을 얻어 온다. */
    keyCode = [event keyCode];
       
    /* 좌측으로 이동 */
    if(keyCode == 123)
    {
        heroRect.origin.x -= HERO_SPEED;
        if(heroRect.origin.x < 0)
            heroRect.origin.x = 0;
    }
   
    /* 우측으로 이동 */
    if(keyCode == 124)
    {   
        heroRect.origin.x += HERO_SPEED;
       
        if(heroRect.origin.x >= SCREEN_WIDTH - heroRect.size.width)
            heroRect.origin.x = SCREEN_WIDTH - heroRect.size.width;
    }
   
    /* 발사(스페이스) */
    if(keyCode == 49)
    {
        [self fireMissile];
    }
}

- (void)fireMissile
{
    if(isFire == NO)
    {
        /* 미사일이 처음 발사되었을 경우에 초기위치를 우주선 좌표로 설정 */
        missileRect.origin = NSMakePoint(heroRect.origin.x + heroRect.size.width/2 - missileRect.size.width/2, heroRect.size.height);
    }
    isFire = YES;
}

- (void)processGame
{
    if(isFire == YES)
    {
        /* 미사일이 발사중이면 y좌표를 이동 */
        missileRect.origin.y += MISSILE_SPEED;
        if(missileRect.origin.y >= SCREEN_HEIGHT)
        {
            /* 화면상단에 위치했을 경우에는 미사일을 출력하지 않는다 */
            isFire = NO;
        }
    }
   
    [self setNeedsDisplay:YES];
}

- (void)drawRect:(NSRect)rect
{
    NSRect imgRect;
    NSRect drawRect;
   
    /* 배경 이미지 출력 */
    imgRect.origin = NSZeroPoint;
    imgRect.size = [backgroundImage size];
   
    drawRect = [self bounds];
   
    [backgroundImage drawInRect:drawRect
                       fromRect:imgRect
                      operation:NSCompositeSourceOver
                       fraction:1.0];
   
    /* 우주선 출력 */
    imgRect.origin = NSZeroPoint;
    imgRect.size = [heroImage size];
   
    [heroImage drawInRect:heroRect
                 fromRect:imgRect
                operation:NSCompositeSourceOver
                 fraction:1.0];   
   
   
    int i;
    int isChange = 0;
    int enemyCount = 0;
   
    /* 적기들을 출력하고 좌우 경계선을 넘어간 적기가 있을 경우를 체크한다. */
    for(i = 0; i < MAX_ENEMY1; i++)
    {
        if([Enemy1[i] state])
        {
            enemyCount++;
            isChange |= [Enemy1[i] moveAndDisplay:enemyDir];
        }   
    }

    for(i = 0; i < MAX_ENEMY2; i++)
    {
        if([Enemy2[i] state])
        {
            enemyCount++;
            isChange |= [Enemy2[i] moveAndDisplay:enemyDir];
        }   
    }
   
    for(i = 0; i < MAX_ENEMY3; i++)
    {
        if([Enemy3[i] state])
        {
            enemyCount++;
            isChange |= [Enemy3[i] moveAndDisplay:enemyDir];
        }
    }
   
    /** 남은 적기가 없을 경우에는 다음 스테이지로 넘어 간다. */
    if(enemyCount == 0)
    {
        [self setStage];
        return;   
    }
       
    if(isFire == YES)
    {
        /* 미사일 출력 */
        imgRect.origin = NSZeroPoint;
        imgRect.size = [missileImage size];
       
        [missileImage drawInRect:missileRect
                        fromRect:imgRect
                       operation:NSCompositeSourceOver
                        fraction:1.0];   
    }
   
    /** 적기가 좌우측의 경계를 넘었을 경우에는 아래로 이동한다. */
    if(isChange)
    {
        enemyDir *= -1;
        for(i = 0; i < MAX_ENEMY1; i++)
        {
            if([Enemy1[i] state])
                [Enemy1[i] down];
        }
       
        for(i = 0; i < MAX_ENEMY2; i++)
        {
            if([Enemy2[i] state])
                [Enemy2[i] down];
        }
       
        for(i = 0; i < MAX_ENEMY3; i++)
        {
            if([Enemy3[i] state])
                [Enemy3[i] down];
        }
    }
}
@end

이번장에서 사용된 이미지와 전체 프로젝트 파일은 아래에서 다운 받으실 수 있습니다.

다음장에서는 충돌 검사, 스테이지/점수/에너지 표시등을 넣고 몇 가지 사항을 수정하여 초 간단한 게임을  완료해 보겠습니다.

AND

오늘 책 주문할 것이 있어 강컴에 갔다가 메인페이지에서 정말 놀랍고 반가운 소식을 보았습니다. 눈에 띄는 예약도서에 아론 힐리가스의 Cocoa programming form mac OS X가 번역본이 있는 것 입니다.
사용자 삽입 이미지
강컴yes24에서 9월 15까지 예약 주문을 받고 9월 16일 부터 발송한다고 합니다. 가격은 19,800원 입니다. 원서가 있지만 당장 신청해야겠습니다.

다소 걱정되었던 부분은 번역서는 역자도 중요한데, 박진형이라는 분으로 Osxdev의 공동회장을 맡고 계시고 블로그를 가보니 모르는 제가 보기에도 실력이 굉장한 분 같아 마음이 놓입니다.

출판사는 요새 좋은 책들을 많이 내고 있는 인사이트 입니다. 사이트를 가보 았는데 블로그만 운영하고 있더군요. 아무리 블로그가 중요한 시대지만 좀 아니라고 봅니다.  강컴에는 예약판매가 메인페이지에 있는데 막상 출판사 홈페이지에서는  쉽게 찾을 수가 없더군요.

사용자 삽입 이미지
cocoa로 검색하니 이전에 나온 내용이 있어 보고 있는데, 낯익은 아이디를 볼 수 있었습니다.  제 블로그에 간혹 댓글을 남겨 주셨던 프린이님이네요. ^^ 그곳에서 보니 반가웠습니다.

저는 영어가 짧은 관계로 앞으로 더욱 많은 한국어 관련서적을 볼 수 있었으면 합니다.
AND

1.7.5 소스 수정

1) AppController.h 수정 및 MyWindow 연결

AppController.h를 오픈하여 아래와 같이 추가합니다. "/* */" 또는 "//"로 주석 처리된 부분은 생략 하셔도 됩니다.

#import <Cocoa/Cocoa.h>

/* 해당 버튼의 Tag 값 */
#define BTN_PLUS        100
#define BTN_MINUS        101
#define BTN_MULTIPLE    102
#define BTN_DIVISION    103
#define BTN_RESULT        104
#define BTN_CLEAR        105

@class MyWindow;

@interface AppController : NSObject {
    int prevOperation;       /* 사용자가 이전에 클릭한 연산 값 */
    int isClear;                   /* 연산을 클릭 후, 다시 값을 입력할 때 지원야 함을 알리는 플래그 */
   
    int totalValue;              /* 현재 계산 총 합 */
   
    IBOutlet MyWindow *myWindow;          /* 사용자 윈도우 */
    IBOutlet NSTextField *txtValue;             /* 현재/결과 값 표시 창 */
    IBOutlet NSTextField *txtOperation;      /* 현재 연산 모드 결과 표시 창 */
    IBOutlet NSTextField *txtHistory;          /* 연산 내역 표시 창 */
}

- (void)awakeFromNib;
- (void)processCalcul:(int)val;
- (void)processInput:(int)val;

- (IBAction) processCommand:(id)sender;
@end

저장 후에 Xcode의 MainMenu.nib를 더블클릭하여 인터페이스 빌더를 실행합니다. Xcode 좌측에서   AppController.h를 드래그 하여 MainMenu.nib 윈도우로 드래그해서 놓습니다.

이 작업은 이전 장에서 하였지만 변경된 AppController를 인터페이스 빌더에서 작업하기 위해 다시 한번 반복합니다. 아래와 같이 AppController의 myWindow 아울렛을 Window에 연결합니다.
사용자 삽입 이미지

MainMenu.nib를 저장 후, 인터페이스 빌더를 닫습니다.

2) MyWindow 소스파일 변경

MyWindow.h를 오픈하여 아래와 같이 추가합니다.
#import <Cocoa/Cocoa.h>

@class AppController;

@interface MyWindow : NSWindow
{
    AppController *app;
}

- (void)setAppController:(AppController *)a;
- (void)keyDown:(NSEvent *)event;
@end

MyWindow.m을 오픈하여 아래와 같이 추가 합니다. 사용자의 키입력을 처리하고 AppController에 알려줄 수 있도록 합니다.
#import "MyWindow.h"
#import "AppController.h"

@implementation MyWindow
/* AppControllerd에서 호출 */
- (void)setAppController:(AppController *)a
{
    app = a;
}

/* 키가 눌려질 경우, 자동으로 호출된다. */
- (void)keyDown:(NSEvent *)event
{
    int keyCode;
    int val = -1;
 
    /* 현재 눌려진 키값을 얻어 온다. */
    keyCode = [event keyCode];


    NSLog(@"KEY: %d", keyCode);
   
    /* 키값을 AppController에서 처리할 수 있도록 변경해 준다.
        각각의 키값은 좌측 숫자키 보드에서 누른 키값들이며, 위의 NSLog에서 확인할 수 있다. */
    if(keyCode >= 82 && keyCode <= 90) // 0~7
        val = keyCode - 82;
    else if(keyCode ==91) // 8
        val = 8;
    else if(keyCode ==92) // 9
        val = 9;
    else if(keyCode == 67) // *
        val = BTN_MULTIPLE;   
    else if(keyCode == 75) // /
        val = BTN_DIVISION;
    else if(keyCode == 69) // +
        val = BTN_PLUS;
    else if(keyCode == 78) // -
        val = BTN_MINUS;
    else if(keyCode == 71) // clear
        val = BTN_CLEAR;
    else if(keyCode == 81) // =
        val = BTN_RESULT;
   
    /* val이 세팅되어 처리해야 될 값일 경우에, AppController의 processInput에 값을 넘겨 준다. */
    if(val != -1)
        [app processInput:val];
   
    [self interpretKeyEvents:[NSArray arrayWithObject:event]];
}
@end

3) AppController.m 소스파일 변경

#import "AppController.h"
#import "MyWindow.h"

@implementation AppController
/* awakeFromNib는 nib파일이 로드된 후, 오브젝트들에게 보내지는 메세지 입니다.
    어플리케이션 실행 후, 각종 초기화 작업을 할 수 있습니다.
*/
- (void)awakeFromNib
{
    isClear = 0;
    prevOperation = 0;
    totalValue = 0;

    /* myWindow의  AppController 변수를 세팅합니다.
        myWindow는 키보드 입력시 이 AppController로 전달합니다.
    */
    [myWindow setAppController:self];
}

/* 연산자 (+, -, X, /, = )가 입력되었을 경우, 처리 합니다. */
- (void)processCalcul:(int)val
{
    /* 이전에 설정된 연산자를 처리한다. */
    if(prevOperation == BTN_PLUS)
        totalValue += [txtValue intValue];
    else if(prevOperation == BTN_MINUS)
        totalValue -= [txtValue intValue];
    else if(prevOperation == BTN_MULTIPLE)
        totalValue *= [txtValue intValue];
    else if(prevOperation == BTN_DIVISION)
        totalValue /= [txtValue intValue];
   
    /* 연산명령이 처음 수행되었을 경우에는, 결과값을 현재 입력된 값으로 설정 합니다. */
    if([[txtHistory stringValue] length] < 1)
        totalValue = [txtValue intValue];
   
    char operation;
    NSString *strTemp;
  
    /* 입력된 키에 따라 출력될 문자를 설정 합니다. */
    if(val == BTN_PLUS)
        operation = '+';
    else if(val == BTN_MINUS)
        operation = '-';
    else if(val == BTN_MULTIPLE)
        operation = '*';
    else if(val == BTN_DIVISION)
        operation = '/';
    else
        operation = '=';

   
/* 계산 History에 출력될 값으로 = 일 경우에는 결과값을 출력합니다. */
    if(val == BTN_RESULT)
        strTemp = [[NSString alloc] initWithFormat:@"= %d\n", totalValue];
    else
        strTemp = [[NSString alloc] initWithFormat:@"%c", operation];
   
   
/* clear 모드를 1로 설정해 이후 다시 숫자를 입력 하면, 이전 결과 값이 지워진 후,
        새 입력값이 나올 수 있도록 합니다.
        prevOperation에 다음 연산 작업을 위해 입력된 연산값을 저장합니다.
    */
    isClear = 1;
    prevOperation = val;
   
    /* 연산 창에 현재 연산모드를 출력합니다. */
    [txtOperation setStringValue:[NSString stringWithFormat:@"%c", operation]];

    /* 연산내역 창에 현재 연산 내역을 추가 합니다. */
    [txtHistory setStringValue:[NSString stringWithFormat:@"%@ %@ %@",
        [txtHistory stringValue],
        [txtValue stringValue],
        strTemp]];
   
   
/* 결과 창에 현재까지 계산된 결과를 보여 줍니다. */
    [txtValue setFloatValue:totalValue];
    [strTemp release];
}

/* 버튼 클릭과 사용자의 키보드 입력을 처리 합니다. */
- (void)processInput:(int)val
{
    NSString *strTemp;
    
    if(val >= 0 && val <= 9)
    {
      
/* 숫자가 입력되었을 경우, 처리 합니다. */
        if(isClear == 1)
        {
             /* 연산을 클릭하고, 처음 숫자가 입력되었을 경우 결과창을 초기화 합니다. */
            [txtValue setStringValue:@""];
            isClear = 0;
        }   
       
        /* 결과 창에 현재 입력된 값을 추가하여 출력 합니다. */ 
        strTemp = [NSString stringWithFormat:@"%@%d", [txtValue stringValue], val];
        [txtValue setStringValue:strTemp];
    }   
    else if(val == BTN_CLEAR)
    {
       /* clear 키를 입력하였을 경우, 데이터와 출력을 초기화 합니다. */
        isClear = 0;
        prevOperation = 0;
       
        [txtValue setStringValue:@""];
        [txtHistory setStringValue:@""];
        [txtOperation setStringValue:@""];
    }
    else if(val >= BTN_PLUS && val <= BTN_RESULT)
    {
       /* 연산이 입력되었을 경우에 현재 값이 있는지 확인 후 처리 합니다. */
        if([[txtValue stringValue] length] > 0)
            [self processCalcul:val];
    }
}

/* 버튼이 클릭되었을 경우에, processInput에 알려 줍니다. */
- (IBAction) processCommand:(id)sender
{
    /* 현재 선택(클릭)된 버튼의 tag값을 얻어 옵니다. */
    int val = [[sender selectedCell] tag];
   
    [self processInput:val];
}
@end

이제 모든 작업이 완료되었습니다. 빌드하고 실행시켜 계산기 프로그램을 테스트 해 봅니다.

아직 처리하지 않는 부분이 많이 있습니다. 이 부분과 몇 가지 기능들을 다음 튜토리얼을 통해 계속 수정/추가해 나가겠습니다. 이번 장부터는 소스코드를 압축하여 올립니다. 참고하실 분은 다운 받아 압축을 풀고, Xcode에서 확인하시고, 이 소스코드에는 주석이 되어 있지 않습니다.

AND

1.7.1 프로젝트 개요 및 생성

1) 개요

사용자 삽입 이미지
이전에도 간단한 계산기를 만들어 보았지만, 이번에는 연속계산이 가능하고, 키보드 입력과  몇 가지 출력을 추가한 업그레이드 된 계산기를 만들어 보겠습니다.

좌측과 같은 계산기인데 첫번째  232라고 되어있는 창에 입력한 값과 계산된 값이 출력 됩니다. 그 옆의 작은 창에 현재 계산 모드가 출력 됩니다. 그 아래의 큰 창에는 계산했었던 내용들이 보여 집니다. 입력은 숫자 키패드와 버튼으로 받습니다.

이번 튜토리알에서는 새로운 내용인 키입력 처리와 NSMatrix에 대해서 알아 보겠습니다.


2) 프로젝트 생성

Xcode를 실행하고, MyCalculator로 cocoa aplication 프로젝트를 새로 만듭니다. 메뉴에서 File/New File을 선택하여 Objective-C 클래스를 선택하고 AppController 클래스를 만듭니다.

위의 과정에 대한 상세한 설명은 이전 포스트에서 설명하였으니, 앞으로는 자세한 과정은 생략하도록 하겠습니다.

1.7.2 AppController 생성 및 변경
Xcode에서 AppController.h을 열어 아래와 같이 추가하고, 저장합니다. 소스에 대한 설명은 다음 장에서 하겠습니다.

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject {
    int prevOperation;
    int isClear;
   
    int totalValue;
   
    IBOutlet NSTextField *txtValue;
    IBOutlet NSTextField *txtOperation;
    IBOutlet NSTextField *txtHistory;
}

- (IBAction) processCommand:(id)sender;
@end

1.7.3 사용자 인터페이스 편집

1) AppController 인스턴스 생성

Xcode의 MainMenu.nib를 더블클릭하여 인터페이스 빌더를 실행합니다. Xcode 좌측에서   AppController.h를 드래그 하여 MainMenu.nib 윈도우로 드래그해서 놓습니다.

사용자 삽입 이미지
Classes에서 AppController를 우클릭 한 후, 메뉴에서 Instantiate AppController를 클릭하여 인스턴스를 만듭니다.

좌측과 같이 인스턴스가 만들져 있는 것을 확인합니다.





2)  Window 컨트롤 배치 및 속성 변경

위의 Intances 창에 보이는 Window를 더블 클릭하여 윈도우를 엽니다.

사용자 삽입 이미지
팔레트에서 텍스트 필드 세개와 버튼 하나를 윈도우에 가져다 놓고 좌측과 같이 배치합니다.

처음 긴 텍스트 필드는 계산 결과 및 입력값, 우측 작은 텍스트 필드는 현재 연산자, 아래의 넓은 택스트 필드는 히스토리를 보여줍니다.

버튼 모양은 인스펙터에서 아래와 같이 설정하였습니다. 원하시는 모양을 선택하시면 됩니다.
사용자 삽입 이미지






사용자 삽입 이미지
첫번째 텍스트필드를 선택하고 인스펙터를 엽니다. 좌측과 같이 정렬을 우측으로 설정합니다. 인스펙터는 해당 오브젝트를 클릭 후에 메뉴 에서 선택 또는 shitf + commad(사과 키) + i를 동시에 누릅니다.

두번째 연산모드 텍스트 필드의 정렬을  가운데로 설정합니다.




사용자 삽입 이미지
위의 세 텍스트필드 모두, 옵션을 좌측과 같이 모두 체크를 해제 합니다. 키보드 입력을 윈도우가 모두 받기 위함입니다.


3) MSMatrix 생성

위의 작업들은 이전 튜토리얼에서 해 본 작업 들입니다. 이해가 가지 않는 분들은 이전 포스트를 확인해 주세요. 이제 NSMatrix를 사용하기 위해 새로운 작업을 해보겠습니다.

사용자 삽입 이미지
버튼을 클릭한 후, 쉬프트 + 옵션 + 마우스를 클릭한 상태에서, 버튼 우측 하단의 원을 클릭하면 좌측과 같이 하나의 원만 남습니다.

위의 상태에서 이 원을 드래그 하여 크기를 늘리면 버튼이 늘어 나는데, 4X4개의 버튼이 되게 합니다. 그리고 텍스트필드와 윈도우를 적당한 크기로 조절합니다.
 
사용자 삽입 이미지

위의 작업이 완료가 되면 좌측과 같은 모습을 보실 수 있습니다. 버튼을 더블클릭하면 텍스트를 수정 할 수 있습니다.

버튼을 더블클릭하여 아래와 같이 모든 버튼들의 텍스트를 변경합니다. 곱하기는 대문자 x를 사용합니다.
사용자 삽입 이미지







4) 버튼셀 tag 변경

이 작업은 매우 중요합니다. 아래와 같이 Tag 값을 정확히 설정하여야, 정확한 계산 결과 값을 보실 수 있습니다.

아래와 같이 0 버튼을 클릭하고 인스펙터에서 Tag값을 0으로 세팅합니다. 0~9까지의 버튼들을 모두 텍스트와 같은 숫자(1->1, 2->2, ...)로 변경합니다.
사용자 삽입 이미지

사용자 삽입 이미지
숫자를 제외한 +, -, X, /, =, C 기호들은 아래와 같이 Tag 값을 입력해 줍니다.

+->100, -->101, X->102, /->103, =->104, C->105


5) 텍스트 필드 Connection 설정

아래와 같이 텍스트필드들을 AppController의 아울렛들과 연결해 줍니다.
012

사용자 삽입 이미지
좌측과 같이 NSMatrix를 AppController의 processCommand에 연결 시킵니다.












6) 윈도우 서브클래스 생성 및 Window와 연결

이제 윈도우를 위한 서브클래스와 소스파일을 생성합니다.
사용자 삽입 이미지
MainMenu.nib파일의 Classes 윈도우에서 NSWindow의 서브클래스를 생성합니다.

NSWindow는 NSObject > NSResponder 밑에 있습니다.






사용자 삽입 이미지
NSWindow의 서브클래스 MyWindow를 우클릭하여 나오는 메뉴에서  Create Files for MyWindow를 선택하여, MyWindow.h와 MyWindow.m 파일을 생성합니다.





사용자 삽입 이미지
Window의 인스펙터를 오픈한 후, Custome Class에서 위에서 만든 MyWindow를 선택합니다.





1.7.4 윈도우, 메뉴, 아이콘 설정

1) 윈도우 설정

사용자 삽입 이미지
왼쪽과 같이 윈도우 타이틀을 MyCalculator로 변경합니다.

Title bar controls에서 Zoom (and resize)를 해제 하여 사용자가 윈도우의 크기를 변경 할 수 없게하며, 우측 하단에 나오는 사이즈 변경 탭이 사라집니다.



2) 메뉴 설정

사용자 삽입 이미지
MainMenu를 클릭하여 좌측과 같이 메뉴 부분에서 New Application으로 되어 있는 부분을
MyCalculator로 변경합니다.










3) 아이콘 설정

이전 튜토리얼 SimpleViewer 이미지뷰어 (2) 1.6.4 어플리케이션 다듬기에서 4) 아이콘 변경을 참조하여, 원하는 아이콘으로 변경합니다.

저는 계산기 어플리케이션 자체를 아이콘으로 만들었습니다. shift + command + 4를 동시에 누르면 캡쳐할 범위를 선택할 수 잇는 십자모양의 커서가 나옵니다. 이 커서를 계산기로 이동하여 스페이스를 누르면 해당 윈도우만 캡쳐할 수 있습니다.

위의 튜토리얼에서 3) About SimpleViewer 판넬 변경을 참조하여, 판넬부분도 변경하여 줍니다. 빌드 후 어플리케이션을 실행시킵니다. 메뉴에서 About MyCalculator를 클릭하여 아래와 같이 변경사항을 확인합니다.
사용자 삽입 이미지

이제 계산기 어플리케이션의 겉모습이 완성되어습니다. 다음 장에서는 실제로 계산기가 동작하도록 소스코드를 작성해 보겠습니다.

개인적으로 일이 있어 오랫만에 포스트를 올립니다. 저도 오랫만에 Xcode를 실행해 보니 왠지 낯서네요. 꾸준히 올릴 수 있도록 하겠습니다.


AND

1.6.4 어플리케이션 다듬기
이번에는 이전에 만들어 놓은 SimpleViewer에 스크롤을 추가하고, 아이콘을 변경하고, 몇 가지 세부사항을 변경하는 작업을 해보겠습니다.

1) 스크롤 추가

인터페이스 빌더에서 윈도우를  선택하고 메뉴에서 아래와 같이  Layout/Make Subviews of/ScrollView를 클릭합니다.
사용자 삽입 이미지

사용자 삽입 이미지
위와 같이 하면 ImgView 우측과 하단에 좌측과 같이 여백이 생기는 것을 확인하실 수 있습니다.










ImgView와 NSScrollView의  인스펙트를 오픈 해  Size의 Autosizing 항목을 각각 아래와 같이 변경해 줍니다. NSScrollView는 우측 하단 모서리 부분을 클릭하면 선택됩니다. 인스펙터 창의 상단 타이틀에서 현재 선택된 항목을 확인 합니다.
사용자 삽입 이미지 사용자 삽입 이미지

2) 메뉴 타이틀 변경

사용자 삽입 이미지
SimpleViewer를 실행 시키고 메뉴를 보면 좌측과 같이
About NewApplication
Hide NewApplication
Quit NewApplication
과 같이 어플리케이션 이름이 아니라 인스펙트 빌더의 기본값인 NewApplication으로 되어 있습니다.



사용자 삽입 이미지
인스펙터 빌더에서 MainMenu.nib에서 각각의 항목을 더블클릭 해 좌측과 같이 모든 NewApplication을 SimpleViwer로 변경합니다.

Nib 파일은 실행시 로드되기 때문에 컴파일 없이 인스펙터 빌더에서 저장하고 어플리케이션을 실해 하여 확인해 봅니다.





3) About SimpleViewer 판넬 변경

사용자 삽입 이미지
어플리케이션을 실행 시키고, 메뉴에서 About SimpleViewer를 클릭하면 좌측과 같은 판넬을 볼 수 있습니다.

__MyCompanyName__으로 되어 있는 부분과 버젼을 변경해 보겠습니다.





사용자 삽입 이미지
Xcode에서 좌측과 같이 Resources 그룹에서 info.plist를 에디터에서 오픈합니다.

하단을 보면 아래와 같이 CFBundelVersion 키에서 string으로 되어 있는 부분에 1.0으로 되어 있는 값을 원하는 버젼으로 수정합니다.

    <key>CFBundleVersion</key>
    <string>0.9</string>



이제는 그 밑의 infoPlist.strings를 열어 NSHumanReadableCopyright 부분을 원하는 저작권자 명으로 변경하여 줍니다.

NSHumanReadableCopyright = "© cocoadev.tistory.com, 2007";

이제 다시 프로그램을 실행시키고 메뉴에서 About을 클릭하면 아래와 같이 변경된 사항을 확인할 수 있습니다.
사용자 삽입 이미지


4) 아이콘 변경

사용자 삽입 이미지
프로그램을 실행해서 하단의 독을 보거나,  폴더에서 실행파일의 아이콘을 보면 좌측과 같이 맥의 기본 응용프로그램 아이콘으로 되어 있습니다.

이번에는 이 아이콘을 변경해 보겠습니다.

/Developer/Applications/Utilities에서 Icon Composer를 실행합니다.
사용자 삽입 이미지
좌측과 같이 Image RGB/Alpha의 각각의 사각형 영역에  원하는 이미지 파일을 드래그해서 가져다 놓습니다.

저는 128X128 JPG 이미지를 하나 만들어 Thumbnail(128X128) 영역에 드래그 해 놓고, 그 부분을 위로 계속 드래그 시켜 항목을 채웠습니다.

이제 메뉴에서 아이콘을 적당한 이름으로 저장합니다.





사용자 삽입 이미지
위에서 저장한 아이콘 파일(*.icns)을 Xcode 좌측 메뉴에서 Resources 그룹에 드래그 해서 가져다 놓으면 좌측과 같은 창이 나옵니다.

상단의  Copy items into destination group's folder(if needed) 항목의 체크를 확인하고 add 버튼을 클릭합니다.





이제 이전에 열었던 info.plist를 열어 CFBundleIconFile 항목을 아래와 같이 설정합니다.
    <key>CFBundleIconFile</key>
    <string>simpleviewer</string>
저는 아이콘 파일명을 simpleviewer.icns로 만들었습니다. 이전에 저장한 icns를 제외한 파일명을 입력해 줍니다.

이제 어플리케이션을 실행하여 About 메뉴를 클릭하면,  아래와 같이 변경된 내역을 확인할 수 있습니다.
사용자 삽입 이미지




AND

Objective-C는 1980년대 Stepstone사의 Brad Cox와 Tom Love에 의해 기존의 C에 SmallTalk의 객체지향 장점을 추가하여 만들어진 멋진 언어라고 합니다. 하지만 주위에선 별로 사용자를 볼 수가 없습니다.

개발환경이 특정 하드웨어나 OS로 한정되어 있고, 가장 많은 사용자를 가진 윈도우즈용 어플리케이션을 제작할 수 없어서(제가 아는 한에서 이며, 확실치 않습니다.) 인 것 같습니다.

보기에도 언뜻 Objective-C 소스를 보면 C와는 상관없는 전혀 별개의 언어로 보여, C/C++ 사용자가 접근이 힘들어 보입니다. 이유는 C에다 클래스와 메시지 전달 방식의 메소드 등 추가된 문법 때문입니다.

하지만 이 부분은 쉽게 배울 수 있으며, 어디선가 C를 알고 객체지향 프로그램에 대한 이해가 있다면 2시간 이면 Object-C를 배울 수 있다는 내용을 본적이 있습니다. 저같이 C/C++을 잘하지 못하는 사람도 대충 배워 가는 것을 보면, 틀린 얘기는 아니라고 생각됩니다.

그래서 C/C++, 또는 윈도우즈에서 VC++을 사용해 보시고, Objective-C 경험이 전혀 없으신 분들을 위해 이 블로그의 제목처럼 맛만 보실 수 있도록 부실한 내용을 시작 하겠습니다.
 
우선 Xcode에서 cocoa application 프로젝트를 만들면, Xcode에서 자동으로 생성해주는 main.m를 확인해 보겠습니다. (만드는 방법은 튜토리얼 분류쪽의 포스트에 있습니다.)

우선 Objective-C에서는 헤더파일은 .h로 같은 이름을 사용하지만, 소스파일명에는 .c 대신에 .m 확장자를 사용함을 알 수 있습니다. 소스를 보겠습니다.

#import <Cocoa/Cocoa.h>

int main(int argc, char *argv[])
{
    return NSApplicationMain(argc,  (const char **) argv);
}

MS처럼 윈도우로 넘어 오면서 C/C++ 고유의 main이란 이름을 가만히 놔두지 않는 것에 비하여, 전형적이고 친숙한 C의 main이 보입니다.

import만 생소하고 모두 C와 동일합니다. import는 Objective-C에서 추가된 전처리 명령어로 include와 유사하지만, 중복될 경우에는 한번만 include합니다. import대신 include의 사용도 가능합니다. 하지만 중복되어 인클루드될 경우를 대비해 헤더파일에
#ifndef _HEADER_NAME_H
#define _HEADER_NAME_H
.........
#endif  //_HEADER_NAME_H
과 같은 처리가 필요하지만 import를 사용하게 되면, 중복 오류를 신경 쓸 필요가 없습니다.

우선 테스트를 위해 아래와 같이 C코드를 추가하고 컴파일을 해봅니다.
void copy_str(char* des, char* src)
{
    strcpy(des, src);
}

int main(int argc, char *argv[])
{
    char buffer[12];
    char* ptr;
    
    copy_str(buffer, "Hellow!");
    ptr = buffer;
    
    printf("str: %s\n", ptr);
    
    return NSApplicationMain(argc,  (const char **) argv);
}

main을 봐도 알수 있듯이 C문법이 오류없이 컴파일 되며, 실행을 하게 되면 빈 윈도우와 함께 로그 윈도우에 str: Hello!를 출력합니다.

이 예를 들은 이유는 Objective-C가 C와 전혀 별개의 언어가 아닌 C언어의 확장으로 봐도 무방할 것 같습니다. 위에 언급한 바와 같이 C언어 또는 C++ 사용이 가능하면, 작은 노력으로도 쉽게 Objective-C를 사용할 수 있습니다.

이제 다른 소스를 확인해 보겠습니다. 아래는 Xcode에서 제공되는 샘플소스의 헤더 파일과 소스파일의 일부분입니다.

@interface DotView : NSView {
    float radius;
}
// Standard view create/free methods
- (id)initWithFrame:(NSRect)frame;
- (void)dealloc;
@end

#import <Cocoa/Cocoa.h>
#import "DotView.h"

@implementation DotView

- (void)mouseUp:(NSEvent *)event {
    NSPoint eventLocation = [event locationInWindow];
    center = [self convertPoint:eventLocation fromView:nil];
    [self setNeedsDisplay:YES];
}
@end

위에 테스트로 작성된 소스와는 다르게 객체를 사용하기 때문에, 전혀 C와는 관련 없는 소스로 보입니다.

C++이나 Java등을 사용하신 분들은 class의 선언과 구현인지 대충 짐작이 가시겠지만, C 문법이라고 보기엔 import, -, @, [], : 등 많은 부분이 눈에 거슬립니다. 아래에서 확연히 다른 점을 간단히 다루어 보겠습니다.

1) "@" 예약어

우선 "@"으로 시작되는 것은 Objective-C에서 추가된 예약어 입니다.

위에서 예를 보면 클래스의 선언부분은  @interface, @end로 구현부분은 @implementation, @end의 사이에 위치합니다.

또한 "hello"는 char* 형의 문자열을 의미하지만, @"hello"는 NSString에서 사용하는 문자열(참고로 @""는 아스키 코드만 가능하며, 한글은 UTF8로 처리해야 합니다.)을 의미하는 것과 같이 기존 C의 문법과 구별이 필요할 때에 사용한다고 보시면 됩니다..

2) 함수 선언

헤더파일을 보면 클래스의 메소드 선언 시 C++/Java와는 달리 클래스 선언 구역({})의 외부에서 선언 됩니다.

위의 - (id)initWithFrame:(NSRect)frame; 선언을 예로 들어 보겠습니다.

함수 앞에는 "-" 표시가 있는데 이는 인스턴스 메소드를 나타내며, "+"일 경우에는 클래스 메소드를 나타냅니다. "+"는 C++/Java의 static 맴버함수와 유사하여 인스턴스의 생성 없이 바로 사용할 수 있는 메소드입니다.

(id) 는 반환될 타입입니다. ()로 처리된다는 것만 제외하고 C와 동일합니다. 참고로 id는 모든 오브젝트를 가리키는 포인터입니다.

":"는 다음에 인자를 의미하며 인자가 ":(타입)이름"과 같이 나온다는 것을 의미합니다. 인자가 2개 이상일  경우에는 스페이스로 구분하며 아래와 같이 선언 합니다.

- (NSPoint)convertPoint:(NSPoint)aPoint fromView:(NSView *)aView;
보면 fromView라는 것이 혼돈을 주는데 인자의 별칭(alias)이라고 생각하시면 되고, 실제 호출 시에는 아래와 같이 사용합니다.

[self convertPoint:eventLocation fromView:nil];
이렇게 함으로써 소스코드는 길어 지지만, 메소드와 인자의 용도를 명확하게 합니다.

여기에는 없지만 변수앞에 사용하는 IBOutlet과 메소드에 사용하는 IBAction란 예약어가 있습니다. 이는 인터페이스 빌더에서 참조를 위한 것으로 변수와 메소드 타입에 영향을 주지 않습니다.  VC의 AFX_ 류로 생각하시면 됩니다.


3) 메소드 호출

center = [self convertPoint:eventLocation fromView:nil];
소스파일에 있는 위의 코드를 C++로 변경하면 아래와 같습니다.

center = this->convertPoint(eventLocation, NULL);

Objective-C에서는 메소드 호출(정확히는 메세지 전달)시 에는 [object method]의 형태로 사용됩니다. [object method:[object method]]와 같이 중첩해서 사용이 가능하며, 초기에는 혼돈이 오지만 자주 보면 object->method(object->method)와 같이 친숙해 집니다.


4) 프레임워크

라이브러리와 유사한 의미로 프레임워크라는 용어를 사용합니다. cocoa 프로젝트를 생성하면 Xcode 좌측의 Groups & Files에서 FrameWorks란 폴더를 찾을 수 있습니다.

 하단의 Other Frameworks를 보면 AppKit.framework와 Foundation.framework를 확인하실 수 있습니다. 이는 VC에서 MFC 클래스 라이브러리와 유사합니다.

Foundation은 NSObject, NSString, NSArray등의 기본적인 클래스들로 구성이 되어 있으며, AppKit은 NSWindow, NSButton, NSImage등 사용자 UI에 관련된 클래스들로 구성되어 있습니다.
 
사용자 삽입 이미지
좌측과 같이 이 두 framework 아래의 headers를 클릭해 보시면 cocoa 개발을 위한 기본적인 클래스 목록들을 확인하실 수 있습니다.






5) 기타

- Objective-C는 C++/Java와는 달리 class에 생성자/소멸자가 없지만, 이를 대치해서 사용할 수 있는 메소드와 이벤트가 있습니다.

- retain이라는 사용 카운터를 사용하여 오브젝트가 메모리에서 삭제되는 시기를 결정합니다. 인스턴스가 추가(alloc)되면 retain이 증가되고, 사용이 완료되면 release라는 메소드로 retain을 감소 합니다. retain이 0이 되면 삭제됩니다.

- Nib 파일 - NeXT Interface Builder의 약자로 오브젝트, 클래스, 리소스, 컨넥션, UI등의 정보와 파일을 가지고 있는 cocoa 어플리케이션에서는 매우 중요한 역활과 의미를 가지고 있는  파일 입니다.

이상으로 마치며... 프로그래밍 언어를 한번 정도 다루어 보신 분들, 특히 C++, Java라면 Objective-C는 쉽게 접근할 수 있는 언어입니다. C에 객체지향을 더했다는 측면에서, C++과 만들어진 이유와 나온 시기도 비슷하여 서로 비교해 보는 것도 재밌습니다. Cocoa와 MFC, VC++과 Xcode도 그렇고요.

앞으로 맥사용자들이 많이 늘어, C++ 사용자 처럼 Objective-C 사용자들이 많이 늘었으면 하는 바램입니다.
 

'Xcode 2 > Objective-C' 카테고리의 다른 글

Objective-C 코딩 스타일  (6) 2008.09.25
Objective-C class의 특징  (2) 2008.03.17
AND

1.5.0  프로젝트 개요
이번에는 NSView를 이용하여 도형들과 텍스트를 출력하는 예제를 만들어 보겠습니다. 아래와 같이 각각의 버튼들을 클릭하면 각각의 내용들을 출력하는 간단한 프로그램 입니다.
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지






1.5.1  프로젝트 생성

이전 포스트에서 경험 해 본 코코아 프로젝트와 소스파일을 생성하고, 인터페이스 빌더와 연결 할 수 있다는 전제 하에 설명하겠습니다. 이 부분에 이해가 안되시면 1.2와 1.3 포스트를 해보시고 오시기 바랍니다.

Xcode를 실행하고 아래와 같이 SimpleDraw 코코아 프로젝트를 생성합니다.
  1. 메뉴바에서 File/New Project...를 선택합니다.
  2. Application/Cocoa Application을 선택합니다.
  3. roject Name에 SimpleDraw를 입력하고 finish 버튼을 클릭합니다.

1.5.2 인터페이스 빌더에서 작업
1) NSView 서브클래스 만들기

사용자 삽입 이미지
Xcode에서 MainMenu.nib를 더블클릭하여 인터페이스 빌더를 실행합니다. 좌측과 같이 Classes 에서 NSObject > NSResponder > NSview를 우클릭(또는 control + 클릭) 하여 메뉴에서 Subclass NSView를 선택 합니다.


사용자 삽입 이미지
MyView라는 서브 클랙스가 생성됩니다. 여기서는 MyView라는 이름을 사용하지만, 용도에 맞는 이름으로 변경하셔도 됩니다.







사용자 삽입 이미지
다음은 MyView에 그리기 명려을 처리할 메소드를 추가합니다. 좌측과 같이 MyView를 우클릭 하여 Add Action to MyView를 선택 합니다.




 
사용자 삽입 이미지
좌측과 같이 기본으로 만들어진 myAction을 drawItem으로 변경합니다.







사용자 삽입 이미지
이제 소스파일을 생성하기 위해 MyView 클래스를 다시 우클릭하여 Create Files for MyView를 선택합니다.

[Choose] 버튼을 클릭하여 MyView.h와 MyView.m 파일을 생성합니다.



2) NSView 속성 설정
 
이제는 NSView와 버튼들을 배치하고 속성들을 설정해 보겠습니다. 아래와 같이 윈도우를 열고 팔레트에서 여섯번째 메뉴인 Containers를 선택하고, 윈도우로 CustomView를 드래그 하여 가지고 옵니다.
사용자 삽입 이미지

사용자 삽입 이미지
윈도우에서 CustomView가 선택된 상태에서 [shift + command + i]를 눌러 인스펙터를 오픈한 후, 상단에서 Custom Class를 선택합니다.

또는 [command + 5]를 눌러서 바로 Custom Class를 오픈해도 됩니다.

Class 항목에서 이전에 만들어 놓은 MyView를 선택합니다.


사용자 삽입 이미지
완료되면 CustomView가 MyView로 변경됩니다. MyView의 사이즈를 좌측과 같이 적절하게 조절 합니다.

상단의 Window에서 SimpleDraw로의 변경은 이전 장에서 해본 것과 같이 윈도우의 인스펙터를 불러내어 Window Title 항목을 변경합니다.






3) 버튼 생성 및 속성 설정

이제 버튼을 배치합니다. 아래와 같이 팔레트에서 버튼을 불러와 버튼을 복사/붙여넣기 하여 네개의 버튼을 만든 후 MyView 하단에 배치 합니다.
사용자 삽입 이미지

사용자 삽입 이미지
좌측과 같이 네개의 버튼의 제목을 각각 사각형, 원, 선, 문자로 변경합니다.

자세히 보면 버튼의 모양이 기본 버튼과 다른데, 이는 아래에서 설명하겠습니다.









사용자 삽입 이미지
사각형 버튼에서 인스펙터를 열어 보면, Tag에 1로 되어 있습니다. 이는 각각의 버튼이 클릭되었을 때, MyView의  DrawItem 메소드에서 구별하기 위해 설정 합니다.

사각형은 1, 원은 2, 선은 3, 문자는 4로 Tag 값을 각각 설정 합니다.

위의 버튼 모양은 Type 항목에서 Round Textured Button을 선택한 모양 입니다.


이제 버튼 클릭시 동작을 연결합니다. 이전 장과 같이 사각형 버튼을 control키를 누른 상태에서 드래그 하여 MyView에 가져다 놓습니다. 아래와 같이 인스펙터 창이 나타나면 Target/Actiond에서 drawItem을 선택하고 하단의 Connect 버튼을 클릭합니다.
사용자 삽입 이미지
위의 작업을 원, 선, 문자 버튼에서도 똑같이 drawItem에 연결 하여 줍니다.


1.5.3 소스코드 작성

1) 헤더파일 수정

이제 Xcode로 돌아 와서 소스코드를 수정합니다. 우선 MyView.h를 에디터에서 오픈합니다.

#import <Cocoa/Cocoa.h>

@interface MyView : NSView
{
    int itemType;
   
    NSString *txt_message;
    NSMutableDictionary *txt_attributes;
}
- (IBAction)drawItem:(id)sender;
@end

헤더파일에는 아래와 같이 세가지 속성들이 추가되었습니다.

int itemType;
어떤 방식으로 그릴지 설정을 저장할 변수 입니다. 위에서 버튼들의 인스펙터에서 설정 한 tag값(1, 2, 3, 4)들이 저장됩니다.

NSString *txt_message;
출력할 문자를 저장합니다.

NSMutableDictionary *txt_attributes;
폰트, 색상 등 문작의 출력 속성을 저장 합니다.

2) 소스 파일 변경 사항

이제 MyView.m을 에디터에서 오픈합니다. 이번에는 소스가 조금 길어 졌습니다.

#import "MyView.h"

@implementation MyView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) != nil) {
        // Add initialization code here
        itemType = 0;
        
        txt_message = [[NSString alloc] initWithString:@"Hello!!!"];
        txt_attributes = [[NSMutableDictionary alloc] init];
        [txt_attributes setObject:[NSColor blueColor]
                                   forKey:NSForegroundColorAttributeName];
    }
    return self;
}

- (void)dealloc {
    [txt_message release];
    [txt_attributes release];
    [super dealloc];
}

- (void)drawRect:(NSRect)rect
{
    const int ITEM_WIDTH = 50;
    const int ITEM_HEIGHT = 50;
   
    NSRect itemRect;
    NSBezierPath *path;
        
    itemRect.origin.x = (rect.size.width/2) - (ITEM_WIDTH/2);
    itemRect.origin.y = (rect.size.height/2) - (ITEM_HEIGHT/2);
   
    itemRect.size.width = ITEM_WIDTH;
    itemRect.size.height = ITEM_HEIGHT;

    switch (itemType) {
    case 1:
        path = [NSBezierPath bezierPathWithRect:itemRect];
        [path fill];
        break;
    case 2:
        path = [NSBezierPath bezierPathWithOvalInRect:itemRect];
        [path fill];
        break;
    case 3:
        path = [NSBezierPath bezierPath];
        [path moveToPoint:itemRect.origin];
        [path lineToPoint:(NSPoint) { itemRect.origin.x + itemRect.size.width, itemRect.origin.y + itemRect.size.height }];
        [path setLineCapStyle: NSButtLineCapStyle];
        [path setLineWidth: 3];
        [path stroke];
        break;
    case 4:
        [txt_message drawAtPoint:(NSPoint) { 20, 20 } withAttributes:txt_attributes];
        break;
    default:
        break;
    }
}

- (IBAction)drawItem:(id)sender
{
    NSLog(@"%d", [sender tag]);
   
    itemType = [sender tag];        
    [self setNeedsDisplay:YES];
}

@end

3) 오브젝트 생성과 해제

자동으로 생성된 initWithFrame 메소드는 이름에서 짐작하듯이 NSView가 처음 초기화 될 때 자동으로 호출됩니다. 주석 설명(// Add initialization code here)과 같이 초기화할 내용들을 아래와 같이 입력합니다.

itemType = 0;

초기에 아무것도 그려지지 않도록 0으로 넣습니다.
      
txt_message = [[NSString alloc] initWithString:@"Hello!!!"];
출력할 텍스트 오브젝트를 생성하고 "Hello!!!"란 문구로 초기화 합니다.

txt_attributes = [[NSMutableDictionary alloc] init];
[txt_attributes setObject:[NSColor blueColor]
                            forKey:NSForegroundColorAttributeName];
텍스트 속성 오브젝트를 생성하고, 폰트칼라만 파란색([NSColor blueColor])으로 설정합니다.

- (void)dealloc {
    [txt_message release];
    [txt_attributes release];
    [super dealloc];
}
dealloc은 오브젝트 사용이 끝나고 메모리에서 해제될 때 호출됩니다. alloc으로 메모리를 할당한 오브젝트들(txt_message, txt_attributes)을, release로 메모리를 해제해 줍니다.

4) 그리기

drawRect는 NSView가 그려져야 할 때, 자동으로 호출됩니다. 그리는 작업을 이곳에서 하면 됩니다.

const int ITEM_WIDTH = 50;
const int ITEM_HEIGHT = 50;
그려질 도형들의 크기를 설정합니다.
  

NSRect itemRect;
NSBezierPath *path;
        
itemRect.origin.x = (rect.size.width/2) - (ITEM_WIDTH/2);
itemRect.origin.y = (rect.size.height/2) - (ITEM_HEIGHT/2);
itemRect.size.width = ITEM_WIDTH;
itemRect.size.height = ITEM_HEIGHT;
그려질 도형들의 위치와 크기를 설정합니다. 인자로 넘어온 rect는 그려져야 할 영역인데 NSView의 크기로 보시면 됩니다. NSView의 중앙에 오도록 계산하여 x, y를 설정 합니다.
 

switch (itemType) {
case 1:
    path = [NSBezierPath bezierPathWithRect:itemRect];
    [path fill];
    break;
case 2:
    path = [NSBezierPath bezierPathWithOvalInRect:itemRect];
    [path fill];
    break;
case 3:
    path = [NSBezierPath bezierPath];
    [path moveToPoint:itemRect.origin];
    [path lineToPoint:(NSPoint) { itemRect.origin.x + itemRect.size.width, itemRect.origin.y + itemRect.size.height }];
    [path setLineCapStyle: NSButtLineCapStyle];
    [path setLineWidth: 3];
    [path stroke];
    break;
case 4:
    [txt_message drawAtPoint:(NSPoint) { 20, 20 } withAttributes:txt_attributes];
    break;
default:
    break;
} 
itemType에 따라 NSBezierPath를 사용하여 사각형, 원, 직선과 문자를 출력합니다. 상세한 설명은  튜토리알 이 후에 다시 하겠습니다. 용도는 오브젝트, 변수, 메소드 명을 보시면 대충 짐작하실 수 있습니다.

다만 [txt_message drawAtPoint:(NSPoint) { 20, 20 } withAttributes:txt_attributes];
를 보시면 x, y 20, 20으로 출력하는데 생각과는 달리 좌측 상단에 나오지 않고 좌측 하단에 출력 됩니다. 이는 기본 좌표 체계가 좌측 하단을 기준으로 시작하기 때문입니다.

5) 사용자 입력 처리

- (IBAction)drawItem:(id)sender
{
    NSLog(@"%d", [sender tag]);
   
    itemType = [sender tag];        
    [self setNeedsDisplay:YES];
}
버튼이 클릭될 때, 호출되는 메소드 입니다. 네개의 버튼이 연결되어 잇지만 [sender tag]로 현재 어느 버튼이 클릭되었는지 알 수 있습니다.

[self setNeedsDisplay:YES];
NSView가 다시 그려져야 됨을 알립니다. drawRect가 호출됩니다.

6) 마무리

이제 빌드 후 실행하고 테스트 해 봅니다. 오류가 나거나 정상적으로 동작하지 않으면 위의 내용을 확인해 보시기 바랍니다.

완전한 이해를 위해서는 Object-c의 문법과 메소드 기타 상세한 설명이 필요 하지만, 단순히 따라 해보는 튜토리알에 의미를 두었습니다. 추후 튜토리알과 다른 항목에서 설명하도록 할려고 합니다.

또한 결정적인 이유는 송구스럽게도 저도 대충 이해하고 구현에 중점을 두고 해보기 때문입니다.
 

AND

사용자 삽입 이미지

cocoa의 사촌인 GNUstep에서 개발에 관한 간단한 데모 동영상(플래쉬)을 보여주는 곳 입니다. 그런데 샘플 프로그램 이름이 SimpleCalc로 제가 이 블로그에 올린 포스트 중에서 샘플로 작성한 프로젝트명이랑 똑 같네요.

 NeXTSTEP이란 뿌리를 같이 하기 때문에 동영상을 참고해가며 cocoa에서 따라해 보는 것도 가능할 것 같습니다.

  
AND

1.4.1  프로젝트 생성
이전 포스트에서 경험 해 본 코코아 프로젝트와 소스파일을 생성하고, 인터페이스 빌더와 연결 할 수 있다는 전제 하에 설명하겠습니다. 이 부분에 이해가 안되시면 1.2와 1.3 포스트를 해보시고 오시기 바랍니다.

Xcode를 실행하고 아래와 같이 SimpleCalc  코코아 프로젝트를 생성합니다.
  1. 메뉴바에서 File/New Project...를 선택합니다.
  2. Application/Cocoa Application을 선택합니다.
  3. roject Name에 SimpleCalc를 입력하고 finish 버튼을 클릭합니다.

1.4.2  AppController class 생성 및 변경
이전 장과 똑같은 방법으로 AppController.h와 AppController.m 파일을 생성합니다.

1) AppController.h 파일 변경

Xcode에서 생성된 AppController.h 파일을 편집창에서 열어 아래와 같이 파란색 라인을 추가합니다.
#import <Cocoa/Cocoa.h>

@interface AppController : NSObject {
    IBOutlet NSTextField *txtResult;
    IBOutlet NSTextField *txtValue;
}

- (IBAction)plusClicked:(id)sender;
- (IBAction)minusClicked:(id)sender;
- (IBAction)muntiplyClicked:(id)sender;
- (IBAction)devideClicked:(id)sender;

@end

IBOutlet NSTextField *txtResult;
IBOutlet NSTextField *txtValue;
위와 같이 계산된 결과값을 표시해 줄 txtResult와 사용자가 입력하는 숫자를 입력 받을 txtValue를 선언 합니다.

- (IBAction)plusClicked:(id)sender;
- (IBAction)minusClicked:(id)sender;
- (IBAction)muntiplyClicked:(id)sender;
- (IBAction)devideClicked:(id)sender;
위와 같이 더하기/빼기/곱하기/나누기 연산을 처리 할 메소드를 선언합니다.

2)  AppController.m 파일 변경

다음은 AppController.m 파일을 편집창에서 열어 아래와 같이 파란색 라인을 추가합니다.
#import "AppController.h"


@implementation AppController
- (IBAction)plusClicked:(id)sender
{
    float curResult = [txtResult floatValue];
    float curValue = [txtValue floatValue];
   
    curResult += curValue;
    [txtResult setFloatValue:curResult];
    [txtValue setStringValue:@""];
}

- (IBAction)minusClicked:(id)sender
{
    float curResult = [txtResult floatValue];
    float curValue = [txtValue floatValue];
   
    curResult -= curValue;
    [txtResult setFloatValue:curResult];
    [txtValue setStringValue:@""];
}

- (IBAction)muntiplyClicked:(id)sender
{
    float curResult = [txtResult floatValue];
    float curValue = [txtValue floatValue];

    if(curValue == 0.0f)
        return;

    curResult *= curValue;
    [txtResult setFloatValue:curResult];
    [txtValue setStringValue:@""];
}

- (IBAction)devideClicked:(id)sender
{
    float curResult = [txtResult floatValue];
    float curValue = [txtValue floatValue];

    if(curValue == 0.0f)
        return;
   
    curResult /= curValue;
    [txtResult setFloatValue:curResult];
    [txtValue setStringValue:@""];
}

@end

나중에 인터페이스 빌더에서 만들어질 +, -, *, / 버튼이 클릭되었을 때, 행동을 지정합니다. 모두 같은 기능을 하니 plucClicked만 예를 들겠습니다.

float curResult = [txtResult floatValue];
결과 창의 현재 값을 가지고 옵니다. 소수점을 지원하기 위해 float으로 선언합니다.

float curValue = [txtValue floatValue];
현재 사용자가 입력한 값을 가지고 옵니다.

curResult += curValue;
사용자가 입력한 값을 현재 값에 더합니다. curResult = curResult + curValue; 과 같은 의미입니다. 더하기(+)와 마이너스(-)는 일반 사용하는 기호와 같지만 곱하기는 "*", 나누기는 "/"를 사용합니다.

[txtResult setFloatValue:curResult];
계산된 값을 텍스트 필드에 저장 합니다. 이 명령으로 사용자는 최종 계산된 값을 볼 수 있습니다.

[txtValue setStringValue:@""];
다음 입력을 위하여 현재 입력창의 내용을 지웁니다. 쌍따움표 앞에 @는 아스키 코드를 NSString 형태로 바꾸어 줍니다. @""로는 한글은 표현할 수 없습니다.
 
곱하기와 나누기에는 아래의 내용이 더 추가되어 있습니다.
if(curValue == 0.0f)
        return;
사용자가 미입력시나 0을 입력하였을 경우, 0으로 곱하거나 나누어지는 것을 방지합니다.

3) Nib에 인스턴스 생성

이제 소스파일에서 변경된 사항을 저장하고 Xcode의 AppController.h를 마우스로 드래그 하여 인터페이스 빌더의 윈도우로 가져다 놓습니다. classes 윈도우에서 AppController를 우클릭 하여, Instantiate AppController를 클릭하여 아래와 같이 인스턴스를 생성합니다.
사용자 삽입 이미지

1.4.3  인터페이스 빌더에서 윈도우 생성 및 연결

1) 컨트롤 추가

윈도우를 틀릭하여 아래와 같이 컨트롤들을 추가하고 배치 합니다.
사용자 삽입 이미지

텍스트 필드 두개와  버튼 네개 (버튼은 하나를 가져 온 후, 복사/붙여 놓기로 사용하셔도 됩니다.)를 가져다 놓습니다. 상하 구분선은 팔레트의 두번째 Controls 항목에 있으며, 아래의 화살표를 참조 하여 마우스로 드래그 해 오시면 됩니다.
사용자 삽입 이미지

텍스트 필드는 숫자 형식으로 보여지기 위해, 아래의 우측에 보이는 팔레트에서 1.99$로 표시되어 있는 숫자형식을 지정하는 콘트롤을 드래그 해서 윈도우의 텍스트 필드에 가져다 놓습니다. 그 하단의 텍스트 필드에도 위와 같이 숫자형식 콘트롤을 드래그 해 놓습니다.
사용자 삽입 이미지

윈도우의 텍스트 필드를 클릭하고 [command + shift + i]를 클릭하여,  인스펙터를  오픈합니다. 숫자를 표현하기 위해 좌측 Attributes 항목에서 Alignment를 세번째 우측 정렬로 선택합니다. 위의 숫자형식 콘트롤을 추가하였기 때문에 오른쪽 이미지와 같이 마지막 속성에 Formmatter란 숫자형식을 지정할 수 있는 속성이 추가되었습니다. 첫번째 항목을 선택 합니다. 위의 작업 역시 하단의 텍스트 필드에도 똑같이 해줍니다.

가장 위의 결과창(모서리가 동그란 입력창)은 보여주기만 하므로 Options에서 Editable 클릭을 해제합니다.
사용자 삽입 이미지 사용자 삽입 이미지

2) AppCrontroller와 연결

이제는 AppConroller의 속성과 메소드를 연결합니다. AppController를 controll키와 함께 드래그하여 상단의 텍스트 필드와 txtResult 아울렛에 연결합니다. 마찬가지로 하단 왼쪽의 텍스트 필드와 txtValue와 연결합니다.

사용자 삽입 이미지

각각의 버튼들 controll키와 함께 AppController로 드래그 하여 + -> plusClicked, - -> minusClicked, * -> multiflyClicked, / -> devideClicked 로 연결(Connect)합니다.
사용자 삽입 이미지

위의 인터페이스 빌더의 일련의 작업들이 이해가 안가시는 분은 이전 포스트를 참조해 주세요.

1.4.4 테스트 및 추가사항
이제 모든 작업이 완료되었습니다. Build And Go를 클릭합니다. 빌드가 완료되면 아래와 같이
윈도우가 나타납니다. 좌측 아래 사각 입력창에 숫자를 입력하고 버튼들을 차례로 누르면서 결과값을 확인해 봅니다.
사용자 삽입 이미지

정말로 간단한 계산기가 완료 되었습니다. 다음번엔 여기에 기능들을 추가하고 조금은 어플리케이션 다운모습으로 만들어 볼려고 합니다.

저도 공부하면서 올리는 중이니 용어 사용이나,  구현에 있어 적절치 못한 부분이 많이 있을 것으로 생각됩니다. 지적과 조언은 언제나 감사하게 받아 들이겠습니다.



AND