Foundation Framework Classes
OSXDEV
이전에 NSObject를 소개할 때 이야기 했듯이 모든 클래스는 NSObject에서 상속 되었으며 파운데이션 클래스에 포함된 모든 클래스도 예외가 아니다.(예외가 있다면 분산객체에 사용되는 NSProxy와 여기서 상속된 클래스인 NSDistantObject와 NSProtocolChecker일 것이다. NSProxy도 NSObject와 마찬가지로 루트 클래스이다.)
모든 파운데이션 프래임웍의 클래스를 소개하는것은 힘드니 자주 사용되는 파운데이션 프래임웍 클래스에 대해서 간략하게 소개하고 사용빈도가 높은것은 예제를 간단히 보이도록 하겠다.
[편집] 2.1 NSArchiver, NSUnarchiver
가장 기본적인 NSArchiver는 객체를 파일에 저장할 수 있게 객체를 인코딩 해주는 역할을 한다. 가장 흔히 사용되는
+archiveRootObject:toFile:
과
+archivedDataWithRootObject:
메소드의 예를 한번 보도록 하자.
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString *tempString = [NSString stringWithString:@"Hello World"];
if ( ![NSArchiver archiveRootObject:tempString toFile:@"HelloWorldArchivedString"] ) {
NSLog(@"archiving failed");
}
NSString *unarchivedString = (NSString *)[NSUnarchiver unarchiveObjectWithFile:@"HelloWorldArchivedString"];
if ( unarchivedString ) {
NSLog(unarchivedString);
}
[pool release];
return 0;
}
<리스트 1>
<리스트 1>은 NSArchiver와 NSUnarchiver를 간단하게 보여주는 예제이다. Xcode에서 프로젝트를 Foundation Tool로 해서 main함수 안에서 간단하게 테스트 해본 것이다.
중요한 것은 NSArchiver에 있는
+ (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path
메소드와 NSUnarchiver에 있는
+ (id)unarchiveObjectWithFile:(NSString *)path
메소드이다.
간단히 NSString을 만든 후에 그것을 디스크에 파일로 아카이빙 했다가 다시 읽어 들여서 화면에 출력해주는 기능을 한다.
이 클래스들은 NSCoder에서 상속된 것들로 NSCoder는 추상클래스 이다.
[편집] 2.2 NSString, NSMutableString
자주 소개되었던 NSString을 여기서 또 간략하게 소개하는 이유는 NSString자체 보다는 NSMutableString을 소개하기 위해서 이다. NSString 자체는 변경이 불가능하다. 즉, 두개의 문자열을 합치거나 변경을 가하기 위해서는 NSMutableString을 이용해야 한다.
[편집] 2.3 NSArray, NSMutableArray
NSArray는 어레이를 제공한다. NSArray 역시 크기가 고정적이며 만일 가변적인 크기의 어레이를 이용할려면 NSMutableArray를 이용해야 한다. NSArray에 객체를 넣으면 reference count가 1증가하며 만일 NSArray를 release 해주면 어레이가 가지고 있던 모든 객체에 release메시지를 보낸다.
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSMutableArray *array = [NSMutableArray arrayWithCapacity:10];
NSLog(@"count = %d", [array count]);
NSLog(@"add strings");
NSMutableString *string1 = [NSMutableString stringWithCapacity:10];
[string1 setString:@"Hello"];
NSMutableString *string2 = [NSMutableString stringWithCapacity:10];
[string2 setString:@"Macintosh"];
NSMutableString *string3 = [NSMutableString stringWithCapacity:10];
[string3 setString:@"Carbon"];
[array addObject:string1];
[array addObject:string2];
[array addObject:string3];
NSLog(@"count = %d", [array count]);
int i;
for ( i = 0; i < [array count]; i++ ) {
NSMutableString *string = [array objectAtIndex:i];
NSLog(@"string%d = %@", i, string);
}
NSLog(@"modifiy string3");
[string3 setString:@"Cocoa"];
NSMutableString *tmpString = [array objectAtIndex:1];
[tmpString setString:@"PC"];
for ( i = 0; i < [array count]; i++ ) {
NSString *string = [array objectAtIndex:i];
NSLog(@"string%d = %@", i, string);
}
NSLog(@"%@", string2);
[array release];
[pool release];
return 0;
}
<리스트 2>
2003-10-27 00:51:12.226 Array[685] count = 0
2003-10-27 00:51:12.228 Array[685] add strings
2003-10-27 00:51:12.239 Array[685] count = 3
2003-10-27 00:51:12.239 Array[685] string0 = Hello
2003-10-27 00:51:12.240 Array[685] string1 = Macintosh
2003-10-27 00:51:12.240 Array[685] string2 = Carbon
2003-10-27 00:51:12.240 Array[685] modifiy string3
2003-10-27 00:51:12.240 Array[685] string0 = Hello
2003-10-27 00:51:12.240 Array[685] string1 = PC
2003-10-27 00:51:12.240 Array[685] string2 = Cocoa
2003-10-27 00:51:12.240 Array[685] PC
<결과 1>
<리스트 2>는 NSMutableArray를 이용해서 array를 설명하기 위한 예제이다.
먼저 NSMutableArray를 만든다음 카운트를 출력해보면 0이 나온다. 그리고 세개의 문자열을 추가했는데 뒤에 수정할 것을 생각해서 NSMutableString으로 만들었다. 어레이에 3개의 스트링을 추가해주고나서 카운트를 출력하면 3이 된것을 볼 수 있다.
자, 이번에는 실제 어레이에 있는 문자열을 꺼내서 출력을 해보았다. 그리고나서 3번제 문자열을 Carbon에서 Cocoa로 바꾸어보았다. 그랬더니 결과가 Cocoa로 바뀐것을 볼 수 있다. 즉, 어레이에 들어갈때는 문자열이 복사되어서 들어가는것이 아니고 포인터만 들어가 있다는것을 알 수 있다.
이번에는 또 다르게 어레이에 있는 두번째(0부터 시작되니까 인덱스는 1) 항목을 꺼내서 그 문자열을 바꾸어보았다. 역시 예상했던데로 문자열이 바뀌어서 출력되었다.
그리고 모두 같은 메모리를 사용하는 같은 객체라는 것을 보여주기 위해서 string2에 들어있는 값을 출력해보았다.
전에 객체 생성과 소멸에서 설명했듯이 stringWithCapacity같은 메소드로 생성된 객체들은 자동으로 소멸되는 것이라서 따로 release해주지 않는다는 것에 주의하자.
[편집] 2.4 NSDictionary, NSMutableDictionary
이 클래스는 Key와 value 짝으로 객체들을 구성할 수 있게 해주는 것으로 객체는 딕셔너리 안에서 유니크한 키로 구분된다. key와 value의 짝을 entry라고 한다.
[편집] 2.5 NSException
NSException 클래스는 코코아의 익셉션 핸들링 시스템을 구성하는데 코코아에서의 익셉션 핸들링은 NSException과 몇개의 매크로로 할 수 있다. 이번 연재의 범위를 벗어나는 내용이므로 익셉션에 대한 내용은 다음에 다시 다룰 수 있도록 한다.
[편집] 2.6 NSFileHandle
파일이나 소켓을 통해서 데이타를 읽고 쓰는데 사용되는 클래스이다.
int fd; // 이미 오픈된 소켓이나 파일의 file descriptor라고 가정 NSFileHandle *fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
위와 같은 방법으로 사용가능하다.
[편집] 2.7 NSFileManager
NSFileManager는 파일시스템을 다루는 일을 할 수 있게 해준다. 죽, 파일의 복사나 이동 같은것을 지원한다. 하지만 여기엔 조금의 맹점도 있다. 매킨토시의 파일 시스템은 일반 다른 OS와 다른 부분이 있는데 바로 포크라는 개념이 사용되고 있기 때문이다.
즉, 한개의 파일도 완전히 다른 두개의 구획으로 나눠서 처리되는데 한쪽을 데이타 포크, 다른쪽을 리소스 포크라고 한다. 요즘은 데이타 포크만 사용하는 파일도 많지만 리소스 포크를 여전히 사용하는 파일들도 많다. 이런 포크를 세밀하게 다룰려면 코코아로는 좀 힘들고 결국 카본과 섞어서 사용해야 하는데 개발자들에게는 조금 골치아픈 문제이기도 하다.
BOOL isDir = NO;
NSArray *subpaths;
NSFileManager *manager = [NSFileManager defaultManager];
if ( [manager fileExistsAtPath:_viewingPath isDirectory:&isDir] && isDir ) {
subpaths = [manager directoryContentsAtPath:_viewingPath];
int count = [subpaths count];
int i;
for ( i = 0; i < count; i++ ) {
NSString *path = [subpaths objectAtIndex:i];
NSString *dirPath = [_viewingPath stringByAppendingPathComponent:path];
isDir = NO;
if ( [manager fileExistsAtPath:dirPath isDirectory:&isDir] && !isDir ) {
if ( [IVUtils isSupportedFile:path] ) {
[_imagePathArray addObject:dirPath];
}
}
}
}
위의 코드는 필자가 작성하고 있던 프로그램에서 일부 가져온 것이다.특정 path에서 파일 목록을 가져와서 만일 지원하는 파일 포맷이면 _imagePathArray라는 어레이에 넣어주는 일을 하는 코드이다. 간단하게 NSFileManager의 역할을 볼 수 있는것 같아서 가져왔다.
[편집] 2.8 NSNotification, NSNotificationCenter
NSNotification과 NSNotificationCenter를 이용해서 Notification 디자인 패턴을 적용할 수 있다. 이것은 디자인 패턴의 obserber 디자인 패턴과 유사하다고 보면 되지만 Objective-C언어의 특징을 잘 살려서 훨씬 쎄련된 모습이다.
#import <Cocoa/Cocoa.h>
@interface MyController : NSObject
{
}
- (id) init;
- (void) testNotification:(NSNotification *)theNotification;
- (void) applicationDidFinishLaunching:(NSNotification *)theNotification;
- (void) applicationWillTerminate:(NSNotification *)theNotification;
- (IBAction)pushButton:(id)sender;
@end
<리스트 3 - MyController.h>
#import "MyController.h"
@implementation MyController
- (id) init
{
self = [super init];
if ( self ) {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(testNotification:) name:@"TestNotification" object:nil];
[center addObserver:self selector:@selector(applicationDidFinishLaunching:) name:NSApplicationDidFinishLaunchingNotification object:nil];
[center addObserver:self selector:@selector(applicationWillTerminate:) name:NSApplicationWillTerminateNotification object:nil];
}
return self;
}
- (void) testNotification:(NSNotification *)theNotification
{
NSLog(@"testNotification");
}
- (void) applicationDidFinishLaunching:(NSNotification *)theNotification
{
NSLog(@"applicationDidFinishLaunching");
}
- (void) applicationWillTerminate:(NSNotification *)theNotification
{
NSLog(@"applicationWillTerminate");
}
- (IBAction)pushButton:(id)sender
{
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:@"TestNotification" object:nil];
NSLog(@"pushButton");
}
@end
<리스트 3 - MyController.m>
이 예제를 보면 먼저 컨트롤러가 init될때 addObserver메소드를 통해서 3개의 노티피케이션을 등록한다. 하나는 사용자가 정의해서 하는것(TestNotification)이고 나머지 둘(NSApplicationDidFinishLaunchingNotification, NSApplicationWillTerminateNotification)은 코코아 프래임 웍에 이미 정의되어 있는 것이다.
전자의 경우는 사용자가 직접 postNotificationName:object:메시지를 보내야 하지만 후자의 경우는 코코아 프래임 웍 내에서 필요할때 알아서 해준다.
노티피케이션을 받을 객체 (예제에서는 컨트롤러)에서 default notification center에 addObserver메소드를 이용해서 노티피케이션을 받을 오브젝트와 메소드 그리고 노티피케이션 이름을 정해주면 그 상황이 생기면 미리 정해준 노티피케이션이 발생하며 정해준 메소드를 호출해준다.
강제로 노티피케이션을 발생시키는 것은 pushButton메소드를 보면 알 수 있듯이 postNotificationName:object:메소드를 이용한다.
[편집] 2.9 NSPipe
NSPipe는 유닉스의 pipe를 객체화 시켜놓은 것이다. 알다 시피 파이프는 양쪽 끝을 통해서 통신을 하게 해주는 역할을 하는 것으로 단방향 커뮤니케이션으로 관련된 두개의 프로세스를 역어준다. 이 파이프에 대해 굳이 이야기 하는 이유는 코코아 애플리케이션에서 놀라운 일을 가능하게 해주기 때문이다.
코코아 애플리케이션을 작성하면서 코어부분은 UI가 없는 일반적인 유닉스 프로그래밍으로 하거나 또는 기존의 프로그램을 이용하고 그 위에 UI만 입히는 방법이 가능하기 때문이다.
실제 유명한 매킨토시용 MPlayer는 이런 방법으로 작성되어 있으며 많은 개발자들이 많은 이유로 이 방법을 사용하고 있다.
[편집] 2.10 NSTask
NSTask클래스를 이용하면 다른 프로그램을 실행시키고 그 실행 상태를 모니터링 할 수 있다. NSTask 는 NSThread와 다르게 완전히 분리된 메모리 공간을 사용하는 프로세스를 생성하는데 사용된다.
몇몇가지 값들에 대해서 프로세스를 실행시킨 프로세스의 값을 가져와서 쓴느데 예를 들면 현재 디렉토리나, 스텐다드 입출력 같은 것이다. 그리고 NSTask는 한번만 실행 할 수 있다.
앞서 설명한 NSPipe와 연관지어보면 코코아 애플리케이션에서 터미널 프로그램을 NSTask로 실행하고 그것과 NSPipe를 이용해서 데이타를 주고 받는 구조가 될 것이다.
이런 방법은 매킨토시 개발에서 상당이 유용하고 유명한 방법으로 애플에서도 Moriarity라는 간단한 예제를 제공하고 있다.
http://developer.apple.com/samplecode/Moriarity/index.html 를 참고하기 바란다.
[편집] 2.11 NSThread
NSThread는 쓰레드를 컨트롤 하는 오브젝트 이다. 쓰레드를 실행하거나 종료하거나 또는 지연시키기 위해 사용할 수 있다.
주로 어떤 복잡한 작업을 하는 과정중에 UI가 멈추는 현상을 막기위해서도 많이 사용되기도하고 큰 어떤 일을 몇개의 작은 일로 나눌때 사용되기도 한다.
만일 듀얼이상의 프로세서를 가진 컴퓨터의 경우 훨씬 많은 성능 향상을 보여줄 것이다.
#import <Foundation/Foundation.h>
@interface MyThread : NSObject {
}
- (void)runThread:(id)anArgument;
@end
<리스트 4 - MyThread.h>
#import "MyThread.h"
@implementation MyThread
- (void)runThread:(id)anArgument
{
while (1) {
NSLog(@"Thread");
}
}
@end
<리스트 4 - MyThread.m>
#import <Foundation/Foundation.h>
#import "MyThread.h"
int main (int argc, const char * argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
MyThread *thread = [[MyThread init] alloc];
[NSThread detachNewThreadSelector:@selector(runThread:) toTarget:thread withObject:nil];
getchar();
[thread release];
[pool release];
return 0;
}
<리스트 4 - main.m>
<리스트 4>는 간단한 쓰레드를 만들어서 돌리는 예제로 파운데이션 툴을 이용해서 만들면 된다. 엔터를 누를때까지 쓰레드는 계속 실행되는 구조로 되어있는데 이정도 예제면 필요할 때 적절히 사용 할 수 있을 것이다.
[편집] 2.12 NSTimer
NSTimer는 타이머 오브젝트를 만들며 타이머 그 자체이기도 하다. 즉 정해준 시간이 경과하면 자동으로 등록해둔 메소드를 불러주는 기능을 가진 클래스이다. 주기적인 갱신이 필요한 경우 사용하면 된다.
[편집] 2.13 NSXMLParser
이번에 새로 나온 10.3버젼(팬서)부터 코코아에 XML파서가 내장되었다. 기존에 카본 파서가 있기는 했지만 유용성 면에서 많이 떨어졌었기 때문에 무척 반가운 일이 아닐 수 없다. NSXMLPaser 클래스는 SAX기반의 XML도큐먼트 파싱을 지원한다. NSXMLParser는 내부적으로 LibXML을 사용한다.
이상 간단하게 Foundation Framework에 있는 클래스들을 살펴 보았다. 사실 Foundation Framework만 살펴봐서 실제 코딩을 할 수 있는것은 아니다. UI작업이 들어가게 되면 앱킷(AppKit)쪽을 봐야 하는데 이쪽 역시 분량이 만만치 않다. 이 부분들에 대해서는 독자 여러분들이 직접 문서를 뒤져가면서 보기를 바란다. 현재까지의 연재를 충분히 소화했고 또 다른 개발 환경에서 개발을 해본 경험이 있다면 어렵지 않게 접근 할 수 있을 것이라고 생각한다.




