2010년 11월 28일 일요일

한글완성형(EUC-KR)을 iPhone/Mac에서 사용할 수 있는 언어셋으로 변환하기



Windows PC로부터 받아온 문자열 데이터를 사용하기 위해 NSString으로 변환하니 문자가 깨져버렸다. 이유를 찾아보니, Encoding이 맞지 않아서였다.

그래서 위해 개발자 문서에 나온 모든 Encoding 타입을 적용하였지만, 원하는 결과가 나오지 않았다. 

Apple에서 iPhone에는 제공하지 않는 것일까?

그래서 MAC Application을 간단히 만들어 테스트 해보았다.
MAC OS에서는 [NSString stringWithFormat:@"%s", anyString];
위와 같이 처리하면 자동으로 변환이 되었다. 하지만 유독 iPhone에서만 안되는 것일까?

처리 방법은 아래 레퍼런스를 참고하면 된다.
한글, 중국어, 일본어 등 여러 2bytes 문자들에 대해서 정의해놓은 encoding이다.
  
한글의 경우, 0x80000003으로 정의되어 있는 것을 알 수 있다. 즉, 
[NSString stringWithCString:anyString encoding:0x80000003]; 
과 같이 처리하면 원하는 결과를 얻을 수 있다.

실제 접했던 문제에 대한 예를 들어 설명해보자.
 Windows PC에서 수신 데이터의 메모리 값이 다음과 같았다.
0xBF 0xC0 0xC8 0xC4
이 데이터는 한글로 '오후'이다.
해당 데이터를 잘라낸 다음 버퍼에 넣고, 그 뒤로 0x00 널문자를 2바이트 추가 한다.
그리고 위의 방법과 같이 0x80000003 Encoding을 사용하면 원하는 결과를 얻을 수 있다.
 <출처 : OSXDEV Forum>

NSString의 인코딩

NSString은 내부적으로 모두 유니코드로 저장된다. 하지만, NSString에 문자열을 넣거나 뺄때 유니코드가 아닌 다른 인코딩을 사용할 수 있다.
NSString이 기본적으로 지원하는 인코딩은 NSString.h에 다음과 같이 선언되어 있다.
typedef unsigned NSStringEncoding;

enum {
    NSASCIIStringEncoding = 1,  /* 0..127 only */
    NSNEXTSTEPStringEncoding = 2,
    NSJapaneseEUCStringEncoding = 3,
    NSUTF8StringEncoding = 4,
    NSISOLatin1StringEncoding = 5,
    NSSymbolStringEncoding = 6,
    NSNonLossyASCIIStringEncoding = 7,
    NSShiftJISStringEncoding = 8,
    NSISOLatin2StringEncoding = 9,
    NSUnicodeStringEncoding = 10,
    NSWindowsCP1251StringEncoding = 11,    /* Cyrillic; same as AdobeStandardCyrillic */
    NSWindowsCP1252StringEncoding = 12,    /* WinLatin1 */
    NSWindowsCP1253StringEncoding = 13,    /* Greek */
    NSWindowsCP1254StringEncoding = 14,    /* Turkish */
    NSWindowsCP1250StringEncoding = 15,    /* WinLatin2 */
    NSISO2022JPStringEncoding = 21,         /* ISO 2022 Japanese encoding for e-mail */
    NSMacOSRomanStringEncoding = 30,

    NSProprietaryStringEncoding = 65536    /* Installation-specific encoding */
};
하지만, NSString은 실제로 Mac OS에서 사용할 수 있는 모든 인코딩을 지원한다.

다음은 NSString클래스의 인코딩관련 메쏘드들이다.
/* 사용자 기본 인코딩을 구한다 */
+ (NSStringEncoding)defaultCStringEncoding;

/* NSString에서 사용가능한 모든 인코딩을 구한다 */
+ (const NSStringEncoding *)availableStringEncodings;

/* 인코딩의 이름을 구한다 */
+ (NSString *)localizedNameOfStringEncoding:(NSStringEncoding)encoding;

/* 현재 스트링을 변환시 가장 빠르거나 메모리가 적게 드는 인코딩을 구한다 */
- (NSStringEncoding)fastestEncoding;
- (NSStringEncoding)smallestEncoding;

/* 데이터의 손실없이 해당 인코딩으로 변환 가능한지 여부를 알아본다 */
- (BOOL)canBeConvertedToEncoding:(NSStringEncoding)encoding;

/* 해당 인코딩의 데이터로부터 NSString을 생성한다 */
- (id)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding;

/* 현재 스트링을 해당 인코딩의 데이터로 변환하여 얻는다 */
- (NSData *)dataUsingEncoding:(NSStringEncoding)encoding allowLossyConversion:(BOOL)lossy;
- (NSData *)dataUsingEncoding:(NSStringEncoding)encoding; /* lossy=NO */
다음의 코드를 사용해서 NSString에서 지원하는 모든 인코딩의 이름과 그 값을 알 수 있다.
NSStringEncoding *encoding = [NSString availableStringEncodings];
while (*encoding) {
    NSLog(@"%@ %x", [NSString localizedNameOfStringEncoding:*encoding], *encoding);
    encoding++;
}
다음은 NSString에서 지원하는 인코딩들이다.
Western (Mac OS Roman)0x1e3030
Japanese (Mac OS)0x800000012147483649-2147483647
Traditional Chinese (Mac OS)0x800000022147483650-2147483646
Korean (Mac OS)0x800000032147483651-2147483645
Arabic (Mac OS)0x800000042147483652-2147483644
Hebrew (Mac OS)0x800000052147483653-2147483643
Greek (Mac OS)0x800000062147483654-2147483642
Cyrillic (Mac OS)0x800000072147483655-2147483641
Devanagari (Mac OS)0x800000092147483657-2147483639
Gurmukhi (Mac OS)0x8000000a2147483658-2147483638
Gujarati (Mac OS)0x8000000b2147483659-2147483637
Thai (Mac OS)0x800000152147483669-2147483627
Simplified Chinese (Mac OS)0x800000192147483673-2147483623
Tibetan (Mac OS)0x8000001a2147483674-2147483622
Central European (Mac OS)0x8000001d2147483677-2147483619
Symbol (Mac OS)0x666
Dingbats (Mac OS)0x800000222147483682-2147483614
Turkish (Mac OS)0x800000232147483683-2147483613
Croatian (Mac OS)0x800000242147483684-2147483612
Icelandic (Mac OS)0x800000252147483685-2147483611
Romanian (Mac OS)0x800000262147483686-2147483610
Keyboard Symbols (Mac OS)0x800000292147483689-2147483607
Farsi (Mac OS)0x8000008c2147483788-2147483508
Cyrillic (Mac OS Ukrainian)0x800000982147483800-2147483496
Western (Mac VT100)0x800000fc2147483900-2147483396
Unicode™ (UTF-16)0xa1010
Unicode™ (UTF-8)0x444
Western (ISO Latin 1)0x555
Central European (ISO Latin 2)0x999
Western (ISO Latin 3)0x800002032147484163-2147483133
Central European (ISO Latin 4)0x800002042147484164-2147483132
Cyrillic (ISO 8859-5)0x800002052147484165-2147483131
Arabic (ISO 8859-6)0x800002062147484166-2147483130
Greek (ISO 8859-7)0x800002072147484167-2147483129
Hebrew (ISO 8859-8)0x800002082147484168-2147483128
Turkish (ISO Latin 5)0x800002092147484169-2147483127
Nordic (ISO Latin 6)0x8000020a2147484170-2147483126
Thai (ISO 8859-11)0x8000020b2147484171-2147483125
Baltic Rim (ISO Latin 7)0x8000020d2147484173-2147483123
Celtic (ISO Latin 8)0x8000020e2147484174-2147483122
Western (ISO Latin 9)0x8000020f2147484175-2147483121
Latin-US (DOS)0x800004002147484672-2147482624
Greek (DOS)0x800004052147484677-2147482619
Baltic Rim (DOS)0x800004062147484678-2147482618
Western (DOS Latin 1)0x800004102147484688-2147482608
Central European (DOS Latin 2)0x800004122147484690-2147482606
Turkish (DOS)0x800004142147484692-2147482604
Icelandic (DOS)0x800004162147484694-2147482602
Arabic (DOS)0x800004192147484697-2147482599
Cyrillic (DOS)0x8000041b2147484699-2147482597
Thai (Windows, DOS)0x8000041d2147484701-2147482595
Japanese (Windows, DOS)0x888
Simplified Chinese (Windows, DOS)0x800004212147484705-2147482591
Korean (Windows, DOS)0x800004222147484706-2147482590
Traditional Chinese (Windows, DOS)0x800004232147484707-2147482589
Western (Windows Latin 1)0xc1212
Central European (Windows Latin 2)0xf1515
Cyrillic (Windows)0xb1111
Greek (Windows)0xd1313
Turkish (Windows Latin 5)0xe1414
Hebrew (Windows)0x800005052147484933-2147482363
Arabic (Windows)0x800005062147484934-2147482362
Baltic Rim (Windows)0x800005072147484935-2147482361
Vietnamese (Windows)0x800005082147484936-2147482360
Western (ASCII)0x111
Japanese (Shift JIS X0213)0x800006282147485224-2147482072
Chinese (GBK)0x800006312147485233-2147482063
Chinese (GB 18030)0x800006322147485234-2147482062
Japanese (ISO 2022-JP)0x152121
Korean (ISO 2022-KR)0x800008402147485760-2147481536
Japanese (EUC)0x333
Simplified Chinese (EUC)0x800009302147486000-2147481296
Traditional Chinese (EUC)0x800009312147486001-2147481295
Korean (EUC)0x800009402147486016-2147481280
Japanese (Shift JIS)0x80000a012147486209-2147481087
Cyrillic (KOI8-R)0x80000a022147486210-2147481086
Traditional Chinese (Big 5)0x80000a032147486211-2147481085
Western (Mac Mail)0x80000a042147486212-2147481084
Traditional Chinese (Big 5 HKSCS)0x80000a062147486214-2147481082
Western (NextStep)0x222
Non-lossy ASCII0x777
Western (EBCDIC US)0x80000c022147486722-2147480574
표에서 알 수 있듯이 NSString.h에서 선언되어 있지 않은 인코딩은 CFString의 인코딩 번호에 0x80000000를 더한 값이다.

2010년 10월 15일 금요일

Customisable UIPageControl


Customisable UIPageControl


PageControl Examples
PageControl Examples

The UIPageControl has no way of changing the colors of the dots. Kind of annoying right? Well I knocked up a simple solution to customising the look of the UIPageControl.
This class allows you to replace the dots with your own UIImages, with the following 2 properties:
@property (nonatomic, readwrite, retain) UIImage* imageNormal;
@property (nonatomic, readwrite, retain) UIImage* imageCurrent;
It works by replacing the UIImage on the UIImageView containing the original dot.
One word of warning, if Apple change the way the UIPageControl works, this code will most likely break (especially if they change the dots so they are no longer UIImageViews!). However I’d say the only reason this would happen, is if they add their own functionality in to customise the look of it, so it’s probably OK.
Here’s the full code, in all it’s glory. Feel free to use how you see fit (I’m releasing this with no restrictions). It’s not very complicated, but I figured it might be useful to someone!
#import <UIKit/UIKit.h>
 
@interface OMPageControl : UIPageControl {
  UIImage* mImageNormal;
  UIImage* mImageCurrent;
}
 
@property (nonatomic, readwrite, retain) UIImage* imageNormal;
@property (nonatomic, readwrite, retain) UIImage* imageCurrent;
 
@end
#import "OMPageControl.h"
 
@interface OMPageControl (Private)
- (void) updateDots;
@end
 
 
@implementation OMPageControl
 
@synthesize imageNormal = mImageNormal;
@synthesize imageCurrent = mImageCurrent;
 
- (void) dealloc
{
  [mImageNormal release], mImageNormal = nil;
  [mImageCurrent release], mImageCurrent = nil;
 
 [super dealloc];
}
 
 
/** override to update dots */
- (void) setCurrentPage:(NSInteger)currentPage
{
  [super setCurrentPage:currentPage];
 
  // update dot views
  [self updateDots];
}
 
/** override to update dots */
- (void) updateCurrentPageDisplay
{
  [super updateCurrentPageDisplay];
 
  // update dot views
  [self updateDots];
}
 
/** Override setImageNormal */
- (void) setImageNormal:(UIImage*)image
{
  [mImageNormal release];
  mImageNormal = [image retain];
 
  // update dot views
  [self updateDots];
}
 
/** Override setImageCurrent */
- (void) setImageCurrent:(UIImage*)image
{
  [mImageCurrent release];
  mImageCurrent = [image retain];
 
  // update dot views
  [self updateDots];
}
 
/** Override to fix when dots are directly clicked */
- (void) endTrackingWithTouch:(UITouch*)touch withEvent:(UIEvent*)event 
{
  [super endTrackingWithTouch:touch withEvent:event];
 
  [self updateDots];
}
 
#pragma mark - (Private)
 
- (void) updateDots
{
  if(mImageCurrent || mImageNormal)
  {
    // Get subviews
    NSArray* dotViews = self.subviews;
    for(int i = 0; i < dotViews.count; ++i)
    {
      UIImageView* dot = [dotViews objectAtIndex:i];
      // Set image
      dot.image = (i == self.currentPage) ? mImageCurrent : mImageNormal;
    }
  }
}
 
@end

2010년 2월 10일 수요일

iphone Programming : Objective-C

3. Objective-C 쾌속 유람기
이번 장에서는 Xcode 와 Interface Builder 를 잠시 떠나서 Objective-C 프로그래밍 언어에 대해 살펴보도록 하겠다. 아무리 Dummy 를 위한 문서라지만, iPhone Programming 을 하는데 있어서 Objective-C 언어의 기본에 대해 전혀 살펴보지 않는다는 것이 무리가 있어 보인다. 어차피 iPhone 이건 Mac OS X 이건 간에 Apple 의 세상에서 프로그래밍을 하기로 마음먹은 이상, Objective-C 언어와 친해지지 않을 수 없는 운명이라고 보면 된다. Objective-C 가 마음에 들지 않는다면, iPhone SDK 프로그래밍은 포기하고 사파리에서 동작하는 웹 프로그래밍에 집중하는 편이 훨씬 올바른 선택이다.
매킨토시와 iPhone Application 프로그래밍에 흥미가 있거나 꼭 익혀야 겠다고 생각한다면, 다른 생각 하지 말고 Objective-C 와 친해질 궁리를 하자.

3 장에서는 간단한 iPhone SDK 프로그래밍에 필요한 만큼만 Objective-C 언어에 대해 압축해서 훓어보고 지나가도록 하겠다. 따라서 조금이라도 상세한 내용이나 자세한 설명은 생략되어 있다. 하지만 이렇게 요약된 내용이 처음 접하는 사람들에게는 오히려 더 도움이 될 수도 있다. 처음부터 상세한 내용을 파고 들면서 익히기에는 시간도 부족하고, 그렇게 해서는 금방 지치기 때문이다.


3.1. 들어가면서


다음과 같이 Objective-C 언어의 특징을 두서없이 요약해보았다.

C 언어에 OOP 확장 기능을 추가했다.
MVC(Model-View-Controller) 패턴을 주로 사용한다.
메시지 전송(send) / 수신(receive) 개념이 기본이 된다.
상당히 동적인(Dynamic) 언어다.
클래스 디자인에 Get/Put 개념이 많이 사용된다.


이외에도 더 여러가지 특징을 나열할 수 있다. Objective-C 언어는 SmallTalk 계열의 OOP 언어로 분류된다. 거의 모든 기본 개념이 SmallTalk 을 기본으로 C 언어를 확장한 것이다. 또한 현재는 존재하지도 않는 클래스에 대해서도 프로그래밍이 가능하여, 진정한 모듈러 프로그래밍이 가능한 언어다.

Dynamic 한 특성이 많아서 상당히 많은 동작이 Run-time 에 이루어지게 된다. 이것은 과거 CPU 성능이 지금과 같이 않을때 실행 속도면에서 약점으로 작용했다. 그러나 이제 이런것은 고려 대상이 아니다. iPhone 같은 작은 디바이스에서 Mac OS 가 동작하는 세상인 것이다. 앞으로는 Objective-C 와 같이 진정한 OOP 프로그래밍 언어가 환영받는 시대가 오지 않을까 생각해본다.

3.2. Object 와 id

한마디로 말해서 Objective-C 는 이름처럼 객체(Object)로 C 프로그램을 작성하는 언어다. Object 는 무엇인가? 사실 이건 완전히 선문답이다. 객체가 무엇이냐는 질문으로 시작할 수 밖에 없지만, 반대로 객체가 무엇인지 알면 거의 전부 다 아는것과 같다.

따라서 지금은 단순하게 짚고 넘어갈 수 밖에 없다. 객체(Object)는 어떤 자료(data)와 그 자료를 처리하는데에 관련된 특정 동작들이 함께 묶여있는 단위다. 프로그래밍 언어마다 용어는 조금씩 차이가 있는데, 객체의 자료는 보통 인스턴스 변수(
Instance Variable)가 되고, 동작은 메소드(method)가 된다. C++ 혹은 Java 와 같은 프로그래밍 언어가 일반화 되었기 때문에, 많은 프로그래머들에게 이런 개념은 익숙할 것이다.

Objective-C 에서는, 객체를 구분하는데에 사용하는 구분자가 있다. 이것은 별도의 형(type)으로 선언되어 있으며, 이 type의 이름이 
id 이다. 어떤 객체로의 포인터 처럼 사용하면 된다. 실제로는 객체가 가지고 있는 어떤 고유값이 저장된 변수로의 포인터이다.

따라서 id 는 NULL 값을 가질 수 있다. id 가 가지는 NULL 값은 별도로 
nil 이라고 하며, 따로 정의되어 있다. C 언어에서 포인터에 NULL 을 사용하듯이, id 변수에 nil 을 사용하면 된다.

id 만으로는 객체가 어떤 종류의 객체인지 알 수는 없다. id 에 객체의 형태에 대한 어떤 정보도 담겨있지 않으며, 단지 객체라는 것을 알려줄 뿐이다. 이것은 컴파일 시에 객체에 대한 상세한 정보가 아무것도 없다는 뜻이다.
따라서 모든 객체는 실행될 때(Runtime) 자기 자신의 매소드, 인스턴스 변수 등의 정보를 제공할 수 있어야 한다. 이런 특성으로 객체는 동적으로 형변환 된다. (
Dynamic Typing)

3.3. 메시지? Message!

어떤 객체에게 무엇인가 지시하려면, '메시지'를 보내면 된다. '함수 호출' 이라는 것은 완전히 잊어버리고, 지금부터는 모두 메시지 전달로 생각하자. 다음과 같이 대괄호를 사용한 문법을 사용한다.

[ receiver message ]

우리는 앞에서 MyHello 프로그램을 작성하면서, 이렇게 생긴 문법의 소스 코드를 이미 많이 봤다. 다시 간단하게 정리해보자면, myRect 라는 이름의 객체에게 display 라는 메시지를 전달하고자 한다면,

[ myRect display ];

이렇게 된다. 조금 생각해보면, myRect 객체에는 display 라는 이름의 메소드를 가지고 있다. 따라서 이것은 그냥 
"myRect 객체에 있는 display 메소드를 호출한다" 라고 말해도 마찬가지라고 생각된다. 그렇지 않은가?
Objective-C 를 처음 접해보면 그런 생각이 드는것이 당연하다. 하지만 이것은 엄연히 함수 호출과는 다르다. 예를 들어서 myRect 안에 display 라는 이름의 메소드가 없어도 컴파일 시에 에러가 발생하지도 않으며, 실행시에도 즉각적인 오동작을 일으키지 않는다. 단지 myRect 객체는 자기가 받아들이지 못하는 메시지를 무시할 뿐이다.

조금 더 메시지 형식을 살펴보자. 함수 호출과 마찬가지로 메시지 전달시에도 하나 이상의 파라메터를 전달하는 것이 가능하다. 그러나 그 형식은 C 언어에서 함수 호출하는 것과는 좀 다르다. setWidth 라는 메시지로 값을 설정하는 경우를 예로 보자.

[ myRect setWidth:20.0 ];

보는 것 처럼 콜론 문자를 사용하여 넘겨주는 값을 지정한다. 만일 하나 이상의 파라메터를 전달하는 경우는 어떻게 될까? 사각형의 좌표와 크기를 설정하는 메시지를 가정해 보자.

[ myRect setLeftX:10.0 leftY:5.0 width:20.0 height:15.0 ];

네 개의 파라메터를 전달하고 있는데, 각 파라메터는 전부 이름을 명시하고 있다. 이것은 생략할 수 있는 선택사항이 아니다. 이것을 굳이 C 언어 함수 호출 형태로 변경해보자면 다음과 같을지도 모르겠다.

my_rect_set(10.0, 5.0, 20.0, 15.0);

Objective-C 의 메시지 전달 형식이 복잡해 보이지만, 조금만 사용해 보면 C 언어보다 훨씬 실수가 적고 소스 코드의 이해도가 높다는 것을 알게 된다.
당연히, 메시지를 받은 객체는 리턴 값을 반환할 수 있다.

rectColor = [myRect getColor];

메시지에 대해서는 Apple 문서에서 
Dynamic Binding 에 대한 내용을 함께 참고하기 바란다. (Polymophism 에 대한 부분도 참고하라) 그러면 메시지 전달 메카니즘에 의해 어떻게 Objective-C 가 동적인 특징을 가지게 되는지 이해할 수 있다.

3.4. Class


일반적으로 많이 알고 있는 C++ 혹은 Java 처럼, Objective-C 역시 Class 선언을 사용한다. Class 이름은 프로그램 소스 코드에서 type 과 마찬가지로 사용하면 된다. 따라서 다음과 같은 sizeof 연산자 사용도 가능하다.

int i = sizeof(MyNewClass);

또한, 인스턴스 객체로의 포인터를 다음과 같이 정의할 수도 있다.

MyNewClass   *myClass;

앞에서 이미 클래스로의 포인터는 id 라는 형으로 다룬다고 말했었다. 이렇게 특정 클래스 이름으로 포인터를 지정하는 것을 Static Typing 이라고 하는데, 사실 두 가지 방법은 근본적으로 차이가 없다. 단지 이렇게 Static Typing 을 사용하여 코드를 작성하면 컴파일러에게 관련 코드에서 검사해서 경고 메시지를 출력할 것을 요청하는 것이라고 보면 된다. 자세한 사항은 Static Typing 에 대한 자료를 참조하라.

프로그램을 작성하다보면 수 많은 클래스를 사용하고, 스스로도 많은 클래스를 만들어서 사용하게 된다. 그런데 모든 객체는 반드시 공통적으로 가져야 하는 기능이 상당히 많이 있다. 인스턴스 생성을 위해서 메모리를 할당하거나, 초기화 하는 과정, 그리고 메모리를 반환하는 것 부터 시작해서, 상속 관계 정보를 알려주는 각종 메소드 등이 필수적으로 있어야 한다.
이렇게 많은 기능을 공유하기 위해서, 당연히 객체 지향 프로그래밍에서는 공통의 Root 클래스를 사용한다. Mac OS 에서는 가장 기본적인 프레임워크에 
NSObject 라는 클래스가 있다. 이것은 모든 클래스의 Super 클래스이며, 원조 대왕마마 클래스다. 단도직입적으로 모든 클래스는 NSObject 로 부터 시작된다.

NSObject 에서 기본적인 기능들을 모두 제공하기 때문에, 클래스는 다음과 같이 인스턴스를 생성할 수 있다.

id myRect;
myRect = [Rectangle alloc];

혹은, init 메시지 전달로 초기화 과정까지 함께 하는 방법을 많이 사용한다.

myRect = [[Rectangle alloc] init];

클래스를 선언하면, 하나의 헤더 파일로 인터페이스를 만들고, 같은 이름으로 클래스 소스 코드 파일을 만드는 것이 기본이다. 인터페이스의 형태는 아래와 같다.

@interface 클래스이름 : 수퍼클래스이름{
    인스턴스 변수 선언
}
메소드 선언
@end

중괄호 {} 안에는 클래스의 인스턴스 변수 선언이 있는데, 메소드는 중괄호 밖에 명시한다는 점을 기억하자.
메 소드 선언에서 구분해야 할 것은, 인스턴스 메소드인가 클래스 메소드인가 하는 것이다. 메소드 선언 맨 앞에 있던 정체 불명의 마이너스 기호(-) 가 이것에 대한 것이다. 인스턴스 메소드(instance method)인 경우 메소드 앞에는 마이너스 기호를 붙인다.

- (void)setXPos:(float)x yPos:(float)y;

클래스 메소드(class method)라면, - 대신 + 기호를 붙여야 한다. 클래스 메소드는 코드 중복을 막을 수 있는 좋은 방법이다. 나중에 클래스메소드가 필요하다고 생각되는 경우가 생긴다면, 여러 가지 사항을 세심하게 고려해서 작성해야 한다.

이렇게 작상된 interface 헤더 파일은 #import 를 사용해서 #include 를 쓰는 것과 같이 소스 코드에 사용할 수 있다. 만일 #import 를 쓰는 대신 일부 클래스 이름만 명시해서 컴파일이 진행되도록 하고자 한다면, 다음과 같이 할 수도 있다.

@class  Rectangle, myClass;

이렇게 하면, 컴파일러는 해당 이름의 기호가 단순히 어떤 객체가 될 것임을 알고서 컴파일을 진행하게 된다. 이 방법은 아직 작성되지도 않은 객체에 대한 작업이 가능하게 한다. C 언어에서 extern 선언을 하도 그 실체가 없다면 Link 에러가 나는 것에 반해, Objective-C 는 보다 더 추상화 되어 있는 셈이다.

interface 파일과 짝을 이루는 클래스의 실제 내용은 implementation 파일이라고 부른다. 클래스의 implementation 의 형식도 interface 와 유사한 구조를 가진다.

@implementation 클래스이름 : 수퍼클래스이름{
    인스턴스 변수 선언
}
메소드 선언
@end


메소드를 선언하면서 @implementation 안에서 메소드의 실제 구현 코드가 있어야 한다. 당연히 맨 마지막 열에 있던 세미콜론 문자 대신 중괄호 {} 안에 구현 코드를 작성하면 된다.

3.5. Property


property 는 예전 Objective-C 에서는 없던 내용으로, 언어 규약이 새로 확장되면 추가된 내용이다. 새로 등장한 것이면서도 워낙 많이 등장하기 때문에 꼭 살펴보고 넘어가야 한다.

기본적으로, 객체가 가지고 있는 하나의 인스턴스 변수에 읽고 쓰는 접근을 하기 위해서, 객체 외부에서 변수 값을 읽거나 쓸 수 있는 메소드를 통하는 형식을 취해야 한다. 이런 목적의 메소드를 
accessor 메소드라고 부르고, 각각 getter / setter 메소드라고 칭한다.

Property 의 목적은 이 accessor 메소드를 쉽게 사용할 수 있도록 도와주는 것이다.

예를 들면, value 라는 인스턴스 변수를 위한 accessor 메소드를 모두 만든다면 interface 코드와  implementation 코드는 아래와 비슷해진다.

@interface MyClass : NSObject
{
    int value;
}
-(int)value;
-(void)setValue:(int)newValue;
@end


@implementation MyClass

- (int)value {
    return value;
}

- (void)setValue: (int)newValue {
    value = newValue;
}
@end


이런 식으로 된 코드를 property 를 사용하면 다음과 유사한 형태가 된다.

@interface MyClass : NSObject
{
    int value;
}
@property (copy, readwrite) int value;
@end


@implementation MyClass
@synthesize value;
@end

property 와 관련된 지시자(directive) 키워드는 @property, @synthesize, @dynamic 등이 있고, @property 를 선언할 때 괄호 안에 속성을 지정할 수 있다. property 를 사용해서 객체 지향 프로그래밍의 기본 질서를 잘 지키면서 보다 편리하게 프로그래밍을 할 수 있다.

한가지 더 기억할 점이 있는데, 다음과 같이 전통적인 dot 문법을 사용하는 경우다. 물론 이렇게 사용해도 틀리지 않다.

myClassInstance.value = 30;
NSLog(@"my value: %@", myClassInstance.value );

그런데, 이것은 value 라는 인스턴스 변수에 직접 접근한 것이 아니다. 컴파일러가 이런 dot 표현식을 만나면, 이것은 accessor 를 사용한 표현식과 동일하게 취급된다. 즉, 위의 예제는 아래 코드와 완전히 같다.

[myClassInstance setValue:30];
NSLog(@"my value:%@", [myClassInstance value] );

C 와 C++, Java 등 기존의 대중적인 프로그래밍 언어에 친숙한 사람들을 위한 배려라고 할 수도 있고, C 언어에서 확장된 문법 형태를 가지면서 자연스럽게 일관성이 유지된 것이라고 생각할 수도 있다. 문법적으로 동일한 의미의 코드라는 점은 꼭 기억해 두어야 하겠다.
3.6. Fast Enumeration

Objective-C 2.0 에서 부터는 프로그래머의 편의를 위한 기능이 추가된 것이 있다. enum 을 보다 편리하게 사용할 수 있도록 해주는 기능이라고 보면된다. 이것을 Fast Enumeration 이라고 부른다.

예를 들면, 다음과 같다.

NSArray *array = [NSArray arrayWithObjects: @"one", @"two", @"three", nil ];for( NSString *emt in array ){
    NSLog(@"element: %@", emt );
}

여기서는 NSArray 를 사용하고 있지만, NSDictionary 를 사용하는 것도 상당히 자주 사용되는 방법이다. 또한, Fast Enumeration 을 위해서 NSEnumerator 객체를 사용할 수도 있다. (각 객체에 대한 사항은 레퍼런스 문서를 참조하라)
NSEnumeration 객체는 참조 순서를 변경하기 위해 주로 사용된다. 다음의 코드를 살펴보면 NSEnumeartion 객체를 사용하는 경우를 상상할 수 있다.

NSArray *array = [NSArray arrayWithObjects: @"One", @"Two", @"Three", @"Four", nil];

NSEnumerator *enumerator = [array reverseObjectEnumerator];
for( NSString *element in enumerator) {
    if( [element isEqualToString:@"Three"] )
        break;
}
NSString *next = [enumerator nextObject];

보통 가독성 높은 프로그램 코드를 작성하기 위해 C 프로그래머들도 enum 을 많이 사용하는 것을 볼 수 있는데, Objective-C 를 사용한다면 Fast Enumeration 을 사용해서 손쉽게 보기 좋은 코드를 작성할 수 있을 것이다.
3.7. Protocol

프로토콜은, Java 로 치자면 인터페이스와 같다. Objective-C 역시 클래스의 다중 상속은 불가능하다. 따라서 프로토콜을 통해서 다중 상속과 유사한 구현을 해야 한다.

이미 본 것과 같이 @interface 선언에서 꺽쇠괄호로 지정해 주는 것이 프로토콜의 이름이다.

@interface 클래스명 : 수퍼클래스명 <프로토콜(, 프로토콜,...)>
{
    // ...
}
// ...
@end

프로토콜은, @protocol 지시문으로 정의된다. 프로토콜도 클래스처럼 다른 프로토콜을 계승해서 정의하는 것도 가능하다. 프로토콜의 내용은 당연히 하나 이상의 메소드들의 집합이다.
예를 들면, 다음과 같은 방식으로 정의될 수 있다.

@protocol MyXMLSupport
- (NSXMLElement *)XMLRepresentation;
- initFromXMLRepresentation:(NSXMLElement *)XMLElement;
@end

프로토콜을 사용할 경우 #import 문으로 프로토콜이 정의된 헤더 파일을 포함할 수도 있고, @protocol 지시문을 사용해도 된다. 즉;

#import 프로토콜 파일명
혹은
@protocol 프로토콜명;

프로토콜로 만들어진 객체는 Protocol 이라는 이름의 클래스다. 또한 @protocol(이름) 형식을 사용해서 해당 프로토콜의 클래스를 얻을 수 있다.

Protocol *myXMLSupportProtocol = @protocol(MyXMLSupport);

프로토콜이라는 것은 아주 단순하게 생각하면 메소드 선언일 뿐이다. 결국, 다른 클래스에 있는 메소드를 선언하는것 그 자체가 프로토콜인 셈이다.
그래서, @protocol 지시문을 사용하는 경우는 
Formal Protocol 이라고 말하고, 반대로 단순하게 메소드 선언만 사용하는 것을 Informal Protocol 이라고 한다. 우리 말로 생각하면 "약식 프로토콜" 이랄까? "간이 프로토콜" 이라고 부르는 곳도 있다.
약식 프로토콜은 메소드가 선언만 되어 있을 뿐 그 구현은 없는 것이기 때문에, 해당 메소드를 구현할 클래스는 스스로의 인터페이스에 해당 메소드를 선언한 후 구현파일에 메소드의 동작 내용을 정의한다.

각자 프로토콜에 대해서 Objective-C 문서를 참조하여 보다 자세한 내용을 살펴보도록 하자.

3.8. 마무리

쓰다보니 Objective-C 언어에 대해 간략하게 다루더라도 체계적으로 정리한다는 것이 상당히 시간이 오래 걸리는 일이 될 것이라는 것을 알게 되었다. 그래서 이번 문서는 아주 짧은 메모 요약본 정도의 수준밖에 되지 않은 것 같다. 여기서 언급하지도 않은 내용도 상당히 많기 때문에, 오히려 도움이 되지 않을지도 모르겠다. 그래도 예제 프로그램을 살펴보고 작성하는데에 도움이 될 수 있을 정도로 눈에 익히는 것을 목표로 하였다.
Objective-C 2.0 언어에 대해서는 따로 KLDP Wiki 페이지에서 상세한 정리 문서를 작서하도록 하겠다. 부디 3장에서 살펴본 내용이 Objective-C 언어에 대한 감이라도 대충 잡는데 도움이 되었으면 하고 희망해본다.

iphone programming : Controller 작성

2.8. Controller 작성
앞에서 우리는 Interface Builder 를 완벽히 사용해서 화면 구성을 모두 끝냈고, Outlet 과 Action 이 모두 준비된 Controller 클래스도 생성했다.

프로그램을 완성하기 위해 남은 일은 단 하나, 바로 코드를 작성하는 일이다. 다시 말해서, 이제 정말로 Objective-C 프로그래밍 만이 남은 것이다.
Objective-C 언어에 대해서는 The Objective-C 2.0 Programming Language 와 같은 문서를 먼저 보는 것이 좋다. 이 문서는 그리 길지 않게 주요 사항들이 잘 요약되어 있다. 이 글이 작성되는 시점에서 2008 년 6월 9일 문서가 나와있다.
객 체 지향 개념에 대해서 보다 더 도움이 필요하거나, Objective-C 에서 어떻게 OOP 프로그래밍 개념을 구현하고 있는지에 대해서 정리하고 싶다면 Object-Oriented Programming with Objective-C 문서를 참조하면 큰 도움이 된다. A4 용지 분량으로 40페이지 문서지만, 개념적인 내용으로 이루어져 있고 당연히 영문 문서이기 때문에 OOP 경험이 없는 사람이라면 쉽게 읽혀지지는 않을 것이다.

다 시 언급하지만, Xcode 에서 제공하는 문서 보기 프로그램을 이용해서 여러가지 문서들을 쉽게 찾을 수 있으므로, 항상 애용하도록 하자. 문서 보기 프로그램의 개념은 마치 iTunes 에서 Pod Cast 를 이용하는 것과 비슷하며, 웹 문서로의 링크로 존재하는 부분들도 많이 있다.

이 문서에서는 상세하게 Objective-C 언어에 대해서 살펴보지는 않는다. 단지 간단한 프로그램을 작성하는데에 필요한 부분들만 빠르게 살펴보고, 직접 코드 작성에 이용해 보도록 하겠다. 이렇게 단순하게 몇가지를 살펴보면서 프로그램을 완성하면 전체적인 윤곽을 잡는데에 도움이 될 것이다. 또한, 반드시 각자 Objective-C 언어에 대해서 별도의 문서를 살펴보기 바란다. 다행히, Objective-C 언어는 그렇게 복잡한 문법을 가진 언어가 아니다.

이제 Xcode 의 Workspace 에서 MyHelloViewController.m 파일을 에디터 창에서 열고 살펴보자.
아마도 다음과 같은 형태로 코드가 작성되어 있을 것이다.


#import "MyHelloViewController.h"

@implementation MyHelloViewController
- (IBAction)changeGreeting:(id)sender {
    
}
@end


C언어 비슷하지만 상당히 낮선 형태의 코드가 보인다. #import 구문은 눈치를 보아하니 #include 와 같은 것으로 보이고, 실제로 그렇다.
다 시 한번 말하는데, Objective-C 는 C 언어의 모든 구문을 사용할 수 있다. C 언어 위에 객체지향 관련 사항만 추가되어 확장된 형태다. #include 역시 사용 가능하지만, Objective-C 에서는 #import 를 주로 사용한다. 그 이유는, 중복되는 include 를 컴파일러가 알아서 막아주기 때문이다. 따라서 헤더 파일에 쓸데 없는 중복 회피용 전처리기 구문을 넣지 않아도 된다.

또 한가지 요상한 것이 보이는데, 이른바 골뱅이 문자(@)의 사용이다. @ 문자는 Objective-C 에서만 사용되는 지시문을 사용할 때 함께 사용되는데, 위에서는 @implementation 과 @end 가 보인다.

제일 헷갈리는 것이 
changeGreeting: 이 선언되어 있는 모습이다. 여기서 이것을 자세하게 말하지는 않겠지만, C 언어의 함수 정의로 해석해 보자면
  IBAction changeGreeting( id sender ) { ... }

와 같다고 보면 된다. 왜 이렇게 이상한 형태로 되어있는지, Objective-C 언어 문법을 공부하다 보면 자연스럽게 알게 될 것이다. 우리는 일단 넘어가도록 하자.

C 언어 식으로 말하자면, 버튼을 클릭하면 changeGreeting: 함수가 호출되도록 되어 있다. 그러면, 일단 간단하게 버튼을 클릭하면 화면에 어떤 변화가 일어나도록 코드를 작성해 보자.

우리가 처음 하려던 것은, 버튼을 클릭하면 화면 중간에 있는 라벨의 문구가 변경되도록 만드는 것이었다. 일단 시험을 해보기 위해, 버튼 클릭시 라벨의 문구가 영구적으로 변경되도록 다음과 같이 작성해보자.

- (IBAction)changeGreeting:(id)sender {
    label.text = @"잘가요!";
}

label 의 text 속성 값을 문자열 대입에 의해 변경한다는 것으로 이해한다면 쉽게 읽을 수 있을 것이다. 사실 이것은 Objective-C 방식의 해석으로는 틀린 것이지만, C 언어 프로그래머가 보기에는 마찬가지다.
한 가지 이상한 것은 문자열 따옴표 앞에 @ 문자가 또 다시 나타난다는 점이다. @ 문자가 앞에 있는 문자열은 C 언어의 문자열과는 다르다. 이것은 Objectiv-C 언어에서 사용하는, 문자열 객체라는것 정도로만 이해하고 넘어가도록 하자.

그리고, 다음과 같이 새로운 메소드를 하나 추가하자. 이것은 프로그램이 종료될 때 메모리를 정리하기 위한 구문이다. release 의 자세한 의미는 나중에 살펴보기로 하자.

- (void)dealloc {
    [textField release];
    [label release];
    [super dealloc];
}

현재까지 작성된 MyHelloViewController.m 소스의 전체 코드는 다음과 같다.

#import "MyHelloViewController.h"


@implementation MyHelloViewController

- (IBAction)changeGreeting:(id)sender {
    label.text = @"잘가요!";
}

- (void)dealloc {
    [textField release];
    [label release];
    [super dealloc];
}
@end

소스 코드를 저장하고서 Build & Run 으로 실행해 보자. 만일 소스코드에 오류가 있다면 오류가 있는 위치에 에러 표시가 나타나서 쉽게 수정할 수 있다. 의도적으로 소스 코드에 에러를 만들어서 Workspace 가 어떤 식으로 에러 표시를 하는지 보는것도 좋다.


프로그램을 실행한 후, 버튼을 클릭하면 라벨의 문구가 소스 코드에서 지정한 대로 변경된다. 물론, 원래 상태로 돌아오도록 하는 기능은 없기 때문에, 다시 초기상태로 돌아가는 방법은 현재로서는 프로그램을 종료하고 다시 실행하는 것 뿐이다. 하지만, 약간만 더 작성하면 우리의 MyHello 프로그램은 원하는 모습대로 동작하는 프로그램으로 완성될 것이다.


현재 버튼을 클릭하면(다르게 말하면 '터치'하면) 라벨의 내용이 변경되도록 코드를 작성해 보았다. 이제는 Text Field 에 입력한 내용으로 라벨의 내용을 변경하도록 작성해 보자. 기본 기능에 의해 텍스트 필드에 문자열을 입력하는 것 까지는 가능하지만, 현재 문자열 입력을 종료하고 키보드를 다시 닫는 동작이 이루어지지 않는 상태다. 먼저 키보드의 Enter 키 처리를 추가하도록 하자.

이것은 조금 성가신 작업이 필요한데, 다시 MyHelloViewcontroller.xib 를 더블 클릭하여 Interface Builder 를 실행해야 한다. 왜냐하면, 앞서 Interface Builder 에서 딱 하나 해주지 않고 미뤄둔 작업이 있기 때문이다.

Interface Builder 에서, View 화면에 있는 텍스트 필드를 마우스 우측 클릭으로 connection 창을 열고서 delegate 연결을 File's Owner 와 연결해준다. 이제는 아래 그림을 보면 다들 쉽게 이해할 수 있을 것이다.

이제 대충 짐작이 가지 않는가? textField 의 delegate 메시지를 우리가 작성중인 MyHelloViewController 에서 받아서 처리하겠다는 것을 명시하는 것이다. 이제 textField 에서 발생하는 어떤 종류의 사건들은 위임 메시지가 되어 우리가 작성하는 컨트롤러 클래스로 전달되게 된다. 그 메시지들 중 처리할 수 있는 것들만 메소드를 만들어서 처리하면 된다.

XIB 파일이 수정되었으면 어떻게 해야 하는지 기억나는가? 딱 한번 해보았기 때문에 많은 사람들이 기억하지 못할 지도 모르겠다. Interface Builder 에서 내용 수정이 일어났기 때문에, File's Owner 아이콘이 선택된 상태에서 메뉴의 'Write Class Files...' 을 선택하여 다시 MyHelloViewController 헤더 파일과 소스 파일을 저장해주어야 한다. 그리고 필히, 저장 확인시 Merge 를 선택해서 지금 작업중인 내용들을 보존해 주기 바란다.(Merge 수행 시 실행되는 소스 Merge 프로그램이 좀 원시적인 것은 아쉽다)

저장한 소스를 확인해 보면 MyHelloViewController.h 헤더 파일에 한가지 내용이 추가된것을 볼 수 있다.

@interface MyViewController : UIViewController 
<UITextFieldDelegate> { ... }

이제는 키보드에 Enter 키 입력이 발생하면 텍스트 필드의 해당 동작에 대한 위임(delegate) 메시지가 발생해서 컨트롤러로 전달되게 되었다. 현재는 이 위임 메시지를 처리하는 곳이 없다. 이것을 처리하려면 다음과 같이 
textFieldShouldReturn: 메소드를 만들어주면 된다. 이 메소드 역시 MyHelloViewController.m 코드에 추가하면 된다.
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField {
    if( theTextField == textField ){
        [textField resignFirstResponder];
    }
    return YES;
}

메소드를 선언하는 형태가 다소 복잡해 보이지만, 지금은 theTextFieldShouldReturn: 이름만 기억하고 넘어가도록 하자.
메소드 안에 있는 if() 문은 지금 이 프로그램에서는 필요 없다. 왜냐하면 if() 문의 조건문을 보면 알겠지만, Enter 키가 입력된 텍스트 필드가 구체적으로 어떤 것인지를 확인해서 
resignFirstResponder: 메 소드를 호출해주고 있기 때문이다. 현재 사용된 텍스트 필드 아이템 객체는 단 하나뿐이므로, 지금 구분하는 것은 사실상 의미가 없다. 하지만 두개 이상의 텍스트 필드가 사용되는 프로그램을 작성한다면, 현재 보여주고 있는 if 문과 같은 처리가 꼭 필요할 것이므로, 기억해 두는 것이 좋다.
resignFirstResponder: 라는 것의 의미는, 현재 텍스트 필드가 키보드를 보여주면서 모든 입력을 우선적으로 받아서 처리하는 First Responder 상태가 되어 있지만, 이제 그것을 포기하고 넘겨주겠다는 것을 의미한다. 따라서 textField 안에 있는 resignFirstResponder: 메소드 안에는 그에 해당하는 처리를 하는 코드가 있을 것이다. 그리고 우리는 YES 를 리턴한다.

이것으로 텍스트 필드에 열려있던 키보드는 다시 닫혀지게 된다. 여기까지 작성한 후, 다시 프로그램을 build 하고 실행해서 시뮬레이터를 실행해 보도록 하자. 이번에는 텍스트 필드에 문자를 입력한 후 엔터키를 눌러서 입력을 종료할 수 있게 되었다.

자, 이제 텍스트 입력이 자유롭게 되었으므로, 버튼의 동작을 원래 목적했던 것으로 완성하자.
Workspace 에서 에디터를 열고 앞서 작성했던 
changeGreeting: 메소드의 내용을 다음과 같이 수정해 보도록 한다.
- (IBAction)changeGreeting:(id)sender {
    NSString *nameString = textField.text;

    NSString *greeting = [[NSString alloc] initWithFormat:@"안녕하세요 %@!", nameString];

    label.text = greeting;
    [greeting release];
}

NSString 은 Cocoa Foundation 에서 정의된 문자열 객체로서, Mac 프로그래밍에서 모든 문자열은 이것으로 사용한다고 말해도 틀리지 않다. 메소드 첫 줄에서 textField 의 문자열을 nameString 으로 가져오고 있다. '가져온다'는 표현은 상당히 애매한 표현이다. 나중에 NSString 에 대한 각종 정보를 The Objective-C 2.0 Programming Language 문서나 String Programming Guide for Cocoa 문서 등을 참조하여 확인해 보기를 권한다.
Cocoa Foundation Class 들의 이름은 전부 대문자 NS 로 시작한다. 당연히, 이것은 NeXT Step 의 머릿글자다. 새로 만들어진 iPhone 용 Foundation 들의 이름은 더 이상 NS 로 시작하지 않지만, 기본적인 Cocoa Foundation 들의 모든 Class 는 이 규칙을 지킨 채 남아있다.

initWithFormat: 메소드는 마치 printf 처럼 문자열에 의해 지정된 형식으로 새로운 문자열을 만들기 위해 사용된다. 이렇게 해서 greeting 이라는 이름의 새로운 문자열 객체를 alloc 으로 만들고 있다. 새로 만들어진 문자열에 의해 Label 의 내용을 수정한 다음, greeting 은 release 를 호출해 주어야 한다.

이제 프로그램을 Build 해서 시뮬레이터로 직접 테스트 해 보자. 임의의 문자열을 입력한 후 버튼을 클릭하면 라벨의 문구 내용이 변경된다. 우리의 첫 번째 iPhone Application 이 완성된 듯 하다.


여기에 약간의 코드를 더 추가해서 프로그램을 완결짓도록 하자.
다음은 일부 코드가 추가된 MyHelloViewcontroller.m/h 소스 코드의 전체 내용이다.
#import <UIKit/UIKit.h>

@interface MyViewController : UIViewController <UITextFieldDelegate> {
    IBOutlet UITextField *textField;
    IBOutlet UILabel *label;
    
NSString *string;
}

- (IBAction)changeGreeting:(id)sender;

@end

#import "MyViewController.h"

@implementation MyViewController

- (IBAction)changeGreeting:(id)sender {
    
string = textField.text;

    NSString *nameString = string;
    
if ([nameString length] == 0)  nameString = @"이름은?";

    NSString *greeting = [[NSString alloc] initWithFormat:@"안녕하세요 %@!", nameString];

    label.text = greeting;
    [greeting release];
}

- (BOOL)textFieldShouldReturn:(UITextField *)theTextField {
    if (theTextField == textField) {
        [textField resignFirstResponder];
    }
    return YES;
}

- (void)dealloc {
    [textField release];
    [label release];
    
[string release];
    [super dealloc];
}
@end

그냥 사용해도 되는 textField.text 의 내용을 왜 굳이 string 이라는 이름의 인스턴스 변수를 만들어서 한번 더 거치는지, 그냥 코드를 보면 잘 이해가 되지는 않는다. 이것은 Apple 의 Tutorial 에서 하나의 예로서 보여주고 있는 방법으로서, 가장 단순한 방식으로 MVC 패턴의 Model 을 구현하여 다루고 있는 방법이다. 일반적으로 컨트롤러는 응용 프로그램의 정보를 자신의 모델 객체에서 관리하며, UI 아이템에 데이터를 저장해서 사용하지 않아야 한다. 일단 지금은 이정도로만 넘어가도록 하자. 이렇게 새로 추가된 string 객체 때문에 dealloc 메소드 안에서 release 처리가 추가되었다.

참고로 덧붙여 말하자면, 본 프로그램은 Tutorial 문서에 기반하고 있지만 
@property, @synthesize 구문은 사용하지 않고 작성되었다. 궁금하다면 Tutorial 문서에 있는 원본 소스코드를 참조하여 비교해 보기 바란다. 이 두 가지 구문에 대한 것은 조금 뒤에서 Objective-C 언어 문법을 정리하면서 다루도록 하겠다.


자, 이렇게 iPhone/iPod Touch 용 Application 을 완성했다.
하지만, 한가지 좀 더 재미있는 것을 추가해 보자. (완성이라고 말하고서는 자꾸 추가해서 미안하다. 하하핫.) 이것은 Tutorial 문서에서는 다루지 않는 것이지만, 간단한 작업이고 결과는 효과적이다.

시뮬레이터를 좌/우로 90도 돌려보자. 방향을 바꾸더라고 아무런 일도 일어나지 않는다. 당연히, 방향 전환에 대해서 우리의 프로그램이 처리하지 않고 있기 때문이다.

이제 다음과 같은 메소드를 컨트롤러에 추가하라.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations.
    return YES;
}

shouldAutorotateToInterfaceOrientation: 메시지는 iPhone 의 자세가 90도 단위로 변경되면 View 의 컨트롤러에게 전달된다. 컨트롤러에서 이 메시지를 받아서 YES 를 return 하면, View 는 자신의 형태를 현재 위치에 맞게 '변신' 한다. 당연히, View 위에 뿌리를 내리고 살림을 차리고 있던 모든 녀석들도 덩달아 돌아가거나 크기를 변경하게 된다.

아래 그림처럼 시뮬레이터를 90도 기울여보자.

이 그림을 보면, 가장 위에 있는 textField 는 변경된 가로 길이에 맞게 자신의 길이를 조절했다. 그러나, 아마 여러분의 프로그램은 그렇지 못할 것이다. 대부분 가로 길이가 고정된 채로 남아있고, 오른쪽에 빈 공간이 생겼으리라 예상된다.
왜 그럴까? 그것은 필자가 Interface Builder 에서 textField 를 화면에 배치할 때 뭔가 해주었기 때문이다.

위에 그림에서 텍스트 필드는 크기를 화면에 맞게 조정했지만, 라벨과 버튼은 고정된 자리를 지키고 있기 때문에 중앙에서 벗어난 자리에 있게 된다. 별로 보기 좋지 않으므로 이것도 항상 가운데 정렬을 유지하도록 수정하자. 수정은 당연히 Interface Builder 에서 해주면 된다.

아주 간단하다. 배치되어 있는 버튼을 선택하고 아래 그림처럼 Size 속성 탭을 보자. 이 그림에서 주목할 것은 가운데 영역의 
Autosizing 부분이다. 현재 버튼의 자동 위치,크기 설정을 붉은색 선을 클릭해서 켜고 끄는 것으로 모두 지정할 수 있다. 즉, 아래 그림처럼 설정하면 상단으로부터의 거리만 고정된 채, 화면 좌/우측과의 거리는 자동으로 같은 비율을 유지하게 된다. 같은 방법으로 Label 아이템의 Autosizing 설정도 변경해 보도록 하자.

이렇게 아이템의 설정을 변경하고, XIB 파일을 저장한다. 단, 소스 코드에 영향을 미치는 수정 사항이 아니므로, Write Class Files... 는 해줄 필요 없다.

이제 Xcode 에서 프로젝트를 다시 Build 한 후에 실행해서 그 결과를 확인해 보자. 어떤가? 복잡한 화면 처리도 View 에서 알아서 처리해 주기 때문에, Application 프로그래머는 불필요한 시간 낭비를 하지 않아도 된다. 이제 우리의 프로그램은 화면을 옆으로 돌려도 보기 좋은 레이아웃을 유지한다.

당연히, 화면이 옆으로 변경되었어도 키보드 입력 등 모든 동작은 정상적으로 이루어진다.

이로서 (이번엔 진짜로) 우리의 첫 번째 iPhone Application 제작을 끝마칠 때가 왔다. 모두 iPhone 프로그래밍의 매력을 충분히 느낄 수 있었으리라 생각한다. 이 프로그램을 각자 살펴보고 원하는대로 수정하면서 Xcode 와 Interface Builder 사용에 익숙해지기 바란다. 그리고 궁금한 점은 Apple 에서 제공하는 각종 문서를 검색하면 빠르게 적응할 수 있을 것이다.

특히, Objective-C 언어에 대한 사항은 위에서 계속 언급한 별도의 문서들을 따로 참조하는 것이 좋다. 지금까지 우리는 Objective-C 문법에 대한 사항은 자세히 살펴보지 않았는데, 이제 언어 문법과 개념적인 내용에 대해 다음에 간단하게 정리하고, 다음 프로젝트로 진행하는 것이 좋으리라 생각된다.

내 생각이지만, C++ 언어보다 훨씬 쉽다.

iphone programming : view 구성하기

2.7. IB 에서 View 구성하기

지금까지 우리는 정해진 템플릿에 의해 Xcode 가 기본으로 생성해준 코드들과 XIB 내용을 살펴보고, 프로그램이 시작되는 동작을 살짝 엿보았다.

이제 Interface Builder 에서 새로운 UI 를 작성해 보자. 처음에는 하나의 Label 아이템만을 사용했지만, 이제는 3가지 아이템을 View 창 위로 끌어다 놓고, 위치와 속성을 조절할 것이다.

우리가 사용해볼 아이템은 Text Field, Label, 그리고 Round Rect Button 이 되겠다. 필자는 아래 그림처럼 세 아이템을 나열하고, 크기와 위치를 조정한 후, 몇가지 속성을 수정하였다. 기본적으로 이것은 Apple 의 iPhone Application Tutorial 문서에서 보여주는 예제와 같다.


여러분은 각자 원하는대로 조금씩 그 형태를 다르게 해도 무방하다. 참고로, 위 그림에서 버튼의 색이 파랗게 보이는 이유는 필자가 버튼 속성에서 Highlighted 항목을 체크 표시로 변경했기 때문이다.

각각의 UI 아이템을 배치하고 속성을 조절하는 것에 대해서 더 이상 설명할 필요는 없을 것으로 생각된다. 대부분 이런 식으로 UI 를 제작하는 프로그래밍 툴을 한두가지 사용해 본 경험이 있을 것이고, 근본적으로는 이런 식의 UI 디자인 개발 도구 자체가 직관적으로 사용할 수 있도록 되어 있기 때문이다.
게다가, 이건 다른 회사도 아닌 Apple 에서 만든 (NeXT STEP 부터의 역사를 가진) 개발 도구란 말이다. 직관적으로 빠르게 학습 가능한 UI 로는 세계 최고의 회사가 만든 도구라는 것을 상기하자.


이제, 우리가 원하는 프로그램의 외형은 일단 완성되었다. 성급하지만, 이 상태에서 Interface Builder 의 XIB 파일을 저장하고, Xcode 로 전환한 후 Build & Run 을 수행해 보자. 시뮬레이터가 동작하면서 지금까지 작성한 결과를 보여준다.

인터페이스 빌더로 작성한대로 모든 것이 보여질 것이다. 물론, 버튼을 눌러도 아무 동작도 하지 않는다. 그렇다면, 이번에는 맨 위에 배치한 텍스트 필드를 클릭해보자.
텍스트 필드에 클릭을 했다는 것은 당연히 사용자가 원하는 문자를 입력하려는 것이다. 따라서, iPhone OS 는 사용자가 문자를 입력할 수 있도록 키보드를 제공한다. 우리는 텍스트 필드를 화면에 배치하면서 여기에 사용할 키보드 형태를 별도로 변경하지 않았기 때문에, 화면에 기본형 키보드가 나타날 것이다. 만일 시뮬레이터 환경 설정에서 다국어 입력 지원을 설정해 놓았다면, 키보드의 지구본 키를 클릭히여 한국어를 비롯한 다른 언어들의 키보드로 전환할 수도 있다.

키보드 버튼들을 클릭하면, 입력된 문자가 텍스트 필드에 입력된다. 그런데, Enter 키를 클릭해도 아무런 변화가 없다. 키보드 입력 상태를 벗어나기 위해서 화면 밖 부분을 클릭하거나 다른 시도를 아무리 해봐도, 키보드를 다시 닫고 처음 화면으로 돌아갈 수 없을 것이다!

그 이유는, 필요한 키보드 입력 처리 코드를 작성하지 않았기 때문이다. Enter 키 입력에 대한 처리를 추가하면 의도한대로 다시 키보드를 닫을 수가 있게 될 것이다. 하지만 현재 상태로는 Home 버튼을 눌러서 프로그램을 종료한 후 다시 실행하는 방법 밖에는 없다. 실망스럽겠지만, 온전한 동작을 하도록 코드를 구성하는 것은 조금 후에 작업하도록 하겠다.

그러면 시뮬레이터를 닫아서 구경은 그만두고, 다시 Interface Builder 로 돌아가 보도록 하자.


자, 다시 현재 상황을 보자.
IB 에서 구성한 사용자 인터페이스는 시뮬레이터에서 확인해 본 대로, 정확히 눈에 보이는 것 그대로 구성되어 있다. 하지만 서로 연결되어 동작하는 것은 하나도 없는 상태다.

각 UI 아이템들이 서로 유기적인 동작을 이루기 위해서는, 먼저 
'연결'이 되어야 한다. 따라서, 구체적인 코드를 작성하기 전해 해야 하는 작업은 이 아이템들을 연결하는 일이다.
구슬이 서말 이라도 꿰어야 보배이듯이, View 위에 아이템이 서말이라도 연결해야 보배다.

사실, 연결 작업 그 자체는 Objective-C 소스 코드에 코딩을 하는 작업이다. 그래서 지금 Interface Builder 를 닫고 Xocde Workspace 상에서 작업을 해도 된다. 실제 Apple 의 Tutorial 문서에서는 소스 코드에서 작업하는 것을 기본으로 설명하고 있다.

하지만, 여기에는 선택이 가능하다. 즉, 작업자의 기호에 따라 Workspace 에서 직접 소스코드를 작성해도 되며, 같은 작업을 Interface Builder 에서 해도 된다. 전적으로 이것은 프로그래머가 원하는 바에 달렸다.

이 문서에서는 이미 말한대로, Interface Builder 에서 작업하는 것을 중심으로 설명해 나가기로 한다. 작업 결과를 보면 자연스럽게 소스 코드를 어떻게 고치면 되는지 알 수 있고, 따라서 처음부터 소스코드에서 작업하는 것 보다는 IB 에서 하는 것이 기억하기 쉽다.



그러면, 짝짓기를 해보자.

Interface Builder 에서 작업했던 MyHellloViewController.xib 를 열고, 여기서 File's Owner 아이콘을 선택하면 Identity 창에 Class Actions 칸과 Class Outlets 창 등이 있는 것을 볼 수 있을 것이다.

지금부터 잘 보기 바란다.

먼저, 하나의 
Action 을 지정하자.
액션은 말 그대로, 어떤 동작을 할 것인지에 관한 것이다.
MyHelloViewController 에 Action 을 추가한다는 것은, 다른 외부 객체가 지금 우리가 생성하고 있는 이 객체로 어떤 동작(Action)을 요구할 수 있도록 연결점을 만든다는 뜻이다.

도데체 어떤 녀석이 이 컨트롤러에게 동작을 요구할 것인가? View 에서 이 Controller 에게 동작을 요구할 녀석이 누구일지 생각해 보자.
현재 View 위에 배치된 세 개의 아이템 중, '버튼' 만이 그런 요구를 할 녀석이다. 사용자가 버튼을 클릭하면 버튼은 controller 에게 '야, 나 눌렸어! 이제 알아서 해!" 라고 Action 을 요구해야 한다.

따라서, 우리의 MyHelloViewController 에는 버튼이 Action 을 요구할 수 있는 문이 있어야 한다.
그 작업이 바로 컨트롤러에 Action 을 추가하는 작업이다.

Interface Builder 상에서 Action 추가는, Identity 창에서 Class Actions 항목 아래쪽에 있는 플러스[+] 버튼을 누른 후 왼쪽 칸에 원하는 이름을 기입하면 된다. 지금 type 은 신경쓰지 말고 id 로 남겨둔다.

Action 의 이름은 콜론(:)으로 끝나는 문자열이다. 왜냐하면, 사실 Action 이라는 것의 개념은 다른 클래스에서 호출하는 메소드와 같다. 즉, 다른 누군가가 호출할 함수 하나를 만든 셈이다.




우리의 프로그램에서는 Action 은 하나로 충분하다. 이제, Identity 창의 아래쪽에서 
Outlet 을 추가하도록 한다.
Outlet 은 Action 과는 반대다.
Outlet 은, 컨트롤러 안에서 다른 객체를 지정해서 사용할 명칭을 의미한다.
다시 말해서, 미국에 있는 어떤 도시를 우리나라 사람이 '나성' 이라고 부르는 행위와 같다. 여기서 
'나성'이라는 단어는 우리나라가 사용하는 하나의 Outlet 이 되며, 이 Outlet 은 미국의 LA 와 연결된다.

비유가 좀 괴상한가? 말로 길게 설명하는 것이 오히려 도움이 안될지도 모르겠다.
일 단, 작업 진행을 계속 살펴보도록 하자. 그림에서 보는 것과 같이 Identity 창에서 두 개의 Outlet 을 추가하는데, label 이라는 이름의 아웃렛과 textField 라는 이름의 아웃렛이다. 이름은 아주 단순하게 지었다.
그리고 각 아웃렛의 우측 열에는 각 아웃렛이 연결될 Object 가 어떤 형태인지 그 클래스를 명시해 준다. 이렇게 위 그림과 같이 추가했다면 일단 Outlet 생성 작업은 끝이다.

다시 정리해보자. File's Owner 에 두 개의 Outlet 을 추가했다. 이 뜻은, MyHelloViewController 객체에서 label 이라는 이름으로 외부에 어떤 UILabel 형태의 객체를 참조해서 사용하겠다는 뜻이고, textField 라는 이름으로 외부의 어떤 UITextField 형태의 객체를 참조해서 사용하겠다는 뜻이다.

사용되어질 Action 도 만들고, 사용하겠다고 Outlet 까지 만들었는데, 누가 Action 을 사용해 줄 것이며, 누구의 객체를 Outlet 을 통해서 사용하겠다는 말인가?
컨트롤러 객체 안에서 혼자 주장해봤자 아무 소용 없다. 실제 우리가 목적으로 하는 형태로 실제 연결을 해 주면 진짜로 구슬 서 말이 꿰어서 보배가 된다.

IB 에서 연결 작업은 흥미롭다.
먼저, File's Owner 아이콘을 마우스 오른쪽 클릭 - 필자같이 아직도 원 버튼 마우스 사용자라면 Ctrl-클릭 - 을 한다. 그러면 반 투명으로 시커먼 목록 창이 하나 나타난다.

여기에는 컨트롤러 객체가 가지고 있는 아웃렛과 액션 등의 목록이 연결 상태와 함께 나타난다. 그중 우리가 앞에서 등록한 label 이라는 이름의 아웃렛이 보이는데, 여기서 우측에 있는 작은 동그라미를 클릭한 후 화면 밖으로 쭉 드래깅 해 보자!

파란 직선이 그어지는데, 이 선을 View 창에서 실제 Label 아이템 위에다가 놓고 마우스 버튼을 놓으면 해당 아웃렛과 지정한 라벨 객체의 연결이 완성된다. 아주 직관적인 작업이 아닐 수 없다.
설명이 조금 와닿지 않는다면, 아래 그림을 참조해서 잘 보기 바란다.


같은 방법으로 TextField 아웃렛도 연결해 보자. 잘 살펴보면, view 라는 이름의 아웃렛이 이미 View 와 연결되어서 등록되어 있는 것을 볼 수 있다. 이렇게 연결 작업이 끝난 label 아웃렛과 textField 아웃렛은 Controller 코드 안에서 원하는 목적에 맞게 사용할 수 있을 것이다.

이제 Outlet 을 준비하는 과정은 끝났다. 이제 남은 Action 의 연결을 완성하는 것으로 준비 과정을 마무리 짓기로 하자. Action 의 연결 작업도 Outlet 과 별로 다른점은 없다.

앞서 했던 방식대로, File's Owner 아이콘에서 마우스 우측 클릭으로 연결상태 창을 연 후에 이번에는 changeGreeting: 아웃렛의 우측 동그라미를 버튼과 연결한다.버튼 위레서 바우를 떼면, 버튼의 어떤 동작과 해당 Action 을 연결할 것인지 샌택하는 또 하나의 반투명 팝업 창이 나타난다. 여기서 가장 일반적으로 버튼 동작에 사용하는 
Touch Up Inside 와 연결하면 된다.

참고로, 반대로 작업할 수도 있다. View 의 버튼 아이템에서 마우스 우측 클릭을 하면 마찬가지로 오른쪽에 동그라미를 달고 있는 항목들로 이루어진 반투명 창이 나타나는데, 여기서 Touch Up Inside 의 오른쪽에 있는 동그라미에서 마우스를 클릭한 후 File's Owner 아이콘으로 끌고가면 된다. 마우스를 놓으면 어느 Action 과 연결할 것인지를 선택하는 창이 나타나는데, 우리가 만들어놓은 Action 은 하나 뿐이므로, changeGreetion: 하나만 선택할 수 있는 작은 창으로 나타날 것이다. 이것을 선택해 주면 역시 Action 의 연결 작업이 완결된다.


이렇게 GUI 객체간의 연결 작업을 마우스로 선을 그어 연결하는 방식은 기본적으로 NeXT STEP 의 프로그래밍 환경에서 하던 것과 별 차이가 없다. 현재의 Xcode 는 NeXT STEP 의 개발 환경이 아직도 진화중인 모습에 불과하다고 말할 수 있다.

자, 이제 정신을 차려보자. 어느덧 Interface Builder 에서 구성할 수 있는 모든 작업이 다 끝난 셈이다. 지금 우리가 뭘 했는지 다시 한마디로 말해보자면, Interface Builder 를 이용해서 View 를 구성했고, 이와 연결된 Controller 의 기본 형태를 작성했다.
MVC 중에서 View 와 Controller 일부를 작성한 셈이다.

Controller 를 작성했다고?
그렇다. 분명히 화면에 버튼과 라벨을 배치한 View 와 연결된, 그것의 컨트롤러인 MyHelloViewController 에 아웃렛과 액션을 정의해서 연결을 만들었다. 구체적인 동작은 없지만 Controller 의 기본 틀은 모두 작성한 것이다.

하지만, 당연히 Controller 라는 것은 Objective-C 소스 코드이어야 한다. 그래야 구체적인 동작을 작성해서 프로그램을 완성할 수 있으니까.
그렇다면, 지금까지 Interface Builder 에서 작성한 내용도 소스 코드로 작성되어 있어야 한다. 물론, 처음 템플릿에 의해 MyHelloViewController.m 과 MyHelloViewController.h 헤더 파일이 작성되어 있다. 그러나, 우리가 지금 추가로 Interface Builder 에서 작업한 내용은 아직 소스 코드에 반영되지 않은 상태다.

그래서, Outlet 과 Action 작성, 연결 등을 한 내용을 컨트롤러 소스 코드에 반영해주는 작업이 필요하다.
아래 그림처럼 Interface Builder 파일 메뉴를 보자. 여기서 
Write Class Files... 항목을 선택하면 작업한 내용을 소스 코드로 작성해서 저장하게 된다.(잠깐. 작성된 View 를 저장하기 위해서는 XIB 파일을 저장해야 하는 것은 당연하다. 혼동하지 말자.)



저장하는 파일의 이름과 위치, 그리고 헤더 파일을 함께 저장할지 등을 묻는 대화창이 다음 그림과 같이 나타난다. 일반적으로 별도의 변경 없이 그대로 Save 버튼을 클릭해서 진행하면 된다. 앞서 언급한 바와 같이, MyHelloViewController.m 과 .h 는 템플릿에 의해 이미 작성되어 있기 때문에, 저장할 위치에 같은 이름의 파일이 보일 것이다.


같은 이름의 파일이 이미 있으므로, Save 를 진행하면 당연히 다음과 같은 창이 나타난다. 소스 머지를 선택하는 것이 일반적인 선택이 될 것이다. 혹은 기존 소스를 새로운 소스로 완전히 대체할 수도 있다. 상황에 맞게 선택하면 된다.




Interface Builder 안에서 할 수 있는 일들은 다 한것 같다. 이제 Xcode 의 Workspace 로 돌아와서, 방금 저장한 MyHelloViewController.m 과 MyHelloViewController.h 파일의 내용을 확인해 보자. 분명히 새로 추가된 내용들이 보일 것이고, 그것은 우리가 만든 Outlet 과 Action 에 대한 코드일 것이다.

Interface Builder 에서 Outlet 과 Action 에 대해서 작업한 내용이 결과적으로 Controller 소스 코드에 저장되는 것이라면, 그 반대의 작업도 가능하지 않을까?
다시말해서, 처음부터 Workspace 에서 MyHelloViewController.m/h 소스코드에 직접 코딩해서 작업하면 되지 않을까 하는 것이다.

물론 가능하다. 조금 익숙해진다면 각자 기호에 따라서 처음부터 코드로 작성하는 것이 편한 사람들도 있을 것이다. 그러나 처음에는 Interface Builer 에서 작업하는 것이 보다 직관적인 것은 사실이다.

한가지 주의할 것이 있는데, 처음부터 소스 코드에서 Outlet 과 Action 을 만들어서 연결하는 작업을 했다면, 반대로 나중에 Interface Builder 를 다시 실행해서 해당 View 의 XIB 내요을 수정하려 한다면, 작성된 컨트롤러 클래스 내용을 IB 로 반영해야 작업에 실수가 없게 된다.

메뉴에서 
Read Class Files... 를 선택하면 아까 Write Class Files... 를 했던 것과는 반대로 클래스 소스 코드에 있는 내용이 Interface Builder 에 반영된다. Workspace 와 IB 사이를 왔다갔다 작업할 때는 꼭 이 점을 잊지말아야 사고를 막을 수 있을 것이다.


별것도 아닌 내용을 너무 장황하게 설명한 것은 아닌가 걱정된다. 그러나 Interface Builder 는 GUI 개발 도구 중에서 가장 쉬운, 그리고 MVC 모델이 완벽하게 실체화 된 개발 도구가 아닐까 감히 생각해본다. 따라서 처음 맥 환경에서 프로그래밍을 시도하려는 사람이라도 조금만 살펴보면 적응할 수 있으리라 생각된다.

또 한가지 명심할 것은, 지금까지 살펴본 것 처럼 View 를 구성하고 Controller 클래스를 만들고, Outlet 과 Action 을 작성하여 연결하는 등의 작업은 Mac Desktop 용 application 을 작성할 때에도 방법이 완전히 동일하다는 것이다. 차이가 있다면 오직 사용하는 UI 아이템들 뿐이다. 따라서, iPhone 프로그래밍 만을 하기 위해 Interface Builder 사용법을 익혔다 해도, 이미 절반 이상은 Mac OS 프로그래머가 된 셈이다.

반대로 생각하면, 기존에 Mac OS 에서 데스크탑용 프로그램을 Xcode 를 사용해서 작성할 줄 알던 사람이라면, iPhone 응용 프로그램은 아주 쉽게 작성이 가능한 것이다.
이 점이 전 세계를 놓고 봤을 때 무료로 배포된 iPhone SDK 를 통해서 곧 수 많은 응용 프로그램이 등장하리라는 예상이 가능한 요인 중 하나다.

맥 프로그래머가 극히 적은 국내에서는, 이 문서와 같은 기초 입문 문서가 많은 이들에게 도움을 줄 수 있으리라 감히 기대해본다.


이제 Controller 클래스가 제구실을 할 수 있도록 만들어보자.