ProgrammingInObjectiveC:Chapter 16: Difference between revisions

From 흡혈양파의 번역工房
Jump to navigation Jump to search
(OC2 16장 :: 파일다루기 페이지 추가)
 
 
Line 298: Line 298:
{| style="border: 1px solid black;"
{| style="border: 1px solid black;"
|- style="color: white; background-color: black;"
|- style="color: white; background-color: black;"
|'''메서드 설명
|'''메서드'''||'''설명'''
|- style="vertical-align:top;"
|- style="vertical-align:top;"
| -(NSString *) currentDirectoryPath||현재 디렉터리 경로를 가져온다.
| -(NSString *) currentDirectoryPath||현재 디렉터리 경로를 가져온다.
Line 397: Line 397:


새 디렉터리의 이름을 바꾼다음, 프로그램은 changeCurrentDirectoryPath: 메서드를 사용하여 새 디렉터리를 현 디렉터리로 만든다. 그후 현재 디렉터리 경로를 표시하여 제대로 바뀌었는지 확인한다.
새 디렉터리의 이름을 바꾼다음, 프로그램은 changeCurrentDirectoryPath: 메서드를 사용하여 새 디렉터리를 현 디렉터리로 만든다. 그후 현재 디렉터리 경로를 표시하여 제대로 바뀌었는지 확인한다.


====디렉터리 내용 열거하기====
====디렉터리 내용 열거하기====

Latest revision as of 08:24, 3 August 2013

16장
파일다루기

16장 :: 파일다루기

Foundation 프레임워크는 우리가 파일 시스템에 접근하여 파일과 디렉터리에 기본적인 작업을 수행할 수 있게 해준다. 이런 작업은 NSFileManager 에서 지원하는데, 여기에는 다음일을 하는 메서드들이 들어 있다.

  • 새 파일 생성하기
  • 기존 파일 읽기
  • 데이터를 파일로 쓰기
  • 파일 이름 수정하기
  • 파일 이동(삭제) 하기
  • 파일 존재 여부 확인하기
  • 파일의 크기와 다른 속성 알아내기
  • 파일 복사 하기
  • 두 파일을 비교하여 내용이 동일한지 확인하기


이 작업들은 대부분 디렉터리에도 적용된다. 예를 들어, 디렉터리를 생성하거나, 디렉터리의 내용물을 읽거나, 삭제할 수 있다. 또 파일에 '링크'를 거는 기능도 있다. 이를 이용해 동일한 파일에 두 가지 이름을 붙여 두거나, 동일한 파일을 다른 디렉터리에 넣어둘 수 있다.

파일을 열고 다중 읽기-쓰기 작업을 수행하려면 NSFileHandle 클래스가 제공하는 메서드를 사용해야 한다. 이 클래스의 메서드는 다음 기능을 제공한다.

  • 읽기, 쓰기, 업데이트(읽고 쓰기)를 하기 위해 파일 열기
  • 파일내 특정 위치 탐색하기
  • 파일에서 특정 바이트를 읽어 오거나 파일에 특정 바이트 기록하기


NSFileHandle 이 제공하는 메서드는 장치나 소켓에도 적용할 수 있다. 하지만, 이 장에서는 일반 파일 다루기만 초점을 맞춘다.


파일과 디렉터리 다루기- NSFileManager

NSFileManager 는 '경로명' 을 사용하여 파일이나 디렉터리를 식별한다. 경로명은 NSString 객체로, 파일의 상대 경로명과 전체 경로명, 이렇게 두 가지가 있다. '상대' 경로명은 현재 디렉터리와 비교되어 표시된 경로다. 따라서, 파일명 copy1.m 은 현 디렉터리에 있는파일 copy1.m 을의미한다. 슬래시 문자(/)는 경로에 있는 디렉터리들을 구분해 준다. 예컨대 파일명 ch16/copy1.m 도 상대 경로명으로, 현재 디렉터리에 속하는 디렉터리 ch16 에 저장된 파일 copy1.m을나타낸다.

전체 경로는 '절대' 경로라고도 하는데 슬래시 문자로 시작한다. 맨 앞에 나타나는 슬래시는 사실 루트 디렉터리를 지칭한다. 내 매킨토시에서 홈 디렉터리의 전체 경로명은 /Users/stevekochan 이다. 이 경로명은 /(루트 디렉터리), Users, stevekochan 이라는 세 디렉터리를 지정한다.

특수 문자 틸데(~)는 사용자의 홈 디렉터리 약자로 사용한다. 따라서 ~linda 는 사용자 linda 의 홈 디렉터리 경로인 /Users/linda 의 축약형이다. 이렇듯 틸데 문자 하나만 붙이면 현재 사용자의 홈 디렉터리를 가리킬 수 있다. 예컨대 ~/copy1.m 이라는 경로는 현재 사용자의 홈 디렉터리에 있는 copy1.m 을 참조하라는 뜻이다. 경로명에 있는 현재 디렉터리를 나타내는 '.' 이나 부모 디렉터리를 나타내는 '..' 과 같은 UNIX 스타일의 특수 경로 문자들은 Foundation 파일 처리 메서드에서 사용하기 전에 제거해야한다. 이때 경로 유틸리티 모음을 사용할 수 있는데 이 유틸리티들은 이 장 후반부에서 다룰 것이다.

여러분의 프로그램에서 경로를 하드코딩하지 않도록 주의해야 한다. 이 장에서 앞으로 보겠지만, 메서드와 함수를 이용해 현재 디렉터리,사용자의 홈 디렉터리, 임시 파일을 만드는데 쓸 디렉터리의 경로를 얻어올 수 있다. 가능한한 이 메서드와 함수들을 사용해야 한다. 이 장의 뒷부분에서 Foundation 에 있는 함수로 사용자의 Document 디렉터리 같은 특별한 디렉터리 목록들을 얻는 방법을 보게된다.

파일을 다루는 NSFileManager 의 기본 메서드를 요약해 표 16.1 에 담았다. 이 표에서 path, path1, path2, from, to 는 모두 NSString 객체다. attr 는 NSDictionary 객체다. handler 는 오류를 직접 처리할 때 이용할 수 있는 콜백 핸들러다. 만일 handler 에 nil 을 지정했다고 해보자. 이때 기본 액션으로 BOOL 을 반환하는 메서드를 이용했을 경우, 작업이 성공하면 YES 를, 실패하면 NO 를 반환할 것이다. 이 장에서는 핸들러를 직접 작성하지 않는다.


각 파일 메서드는 NSFileManager 클래스의 객체로 호출하는데, 이 객체를 생성하려면 그 클래스에 defaultManader 메시지를 보내면 된다.

메서드 설명
-(NSData *) contentsAtPath: path 파일에서 데이터를 읽는다.
-(BOOL) createFileAtPath: path
contents: (NSData *) data attributes: attr
파일로 데이터를 기록한다.
-(BOOL) removeFileAtPath: path
handler:handler
파일을 삭제한다.
-(BOOL) movePath: from toPath:
to handler: handler
파일의 이름을 변경하거나 위치를 바꾼다 (to가 이미 있어서는 안 된다).
-(BOOL) copyPath: from toPath:
to handler: ahndler
파일을 복사한다(to가 이미 있어서는 안 된다).
-(BOOL) contentsEqualAtPath:
path1 andPath: path2
두 파일의 내용을 비교한다.
-(BOOL) fileExistsAtPath: path 파일이 존재하는지를 확인한다.
-(BOOL) isReadableFileAtPath: path 파일이 있고, 읽을 수 있는지 확인한다.
-(BOOL) isWritableFileAtPath: path 파일이 있고, 기록이 가능한지 확인한다.
-(NSDictionary *)fileAttributesAtPath:
path traverseLink: (BOOL) flag
파일 속성을 가져온다.
-(BOOL changeFileAttributes:
attr atPath: path
파일 속성을 변환한다.
표 16.1 자주 사용되는 NSFileManager의 파일 메서드


NSFileManager *fm;
    ...
fm = [NSFileManager defaultManager];


예를 들어, 파일 totodist 를 현 디렉터리에서 삭제하려면 위와 같이 NSFileManager 객체를 생성한 뒤, 다음과 같이 removeFileAtPath: 메서드를 호출한다.

[fm removeFileAtPath: @"todolist" handler: nil];


반환된 값으로 파일 삭제가 성공했는지를 확인할 수 있다.

if ([fm removeFileAtPath: @"todolist"handler: nil] == NO) {
    NSLog (@"Couldn't remove file todolist");
    return 1;
}


새로 생성하는 파일이나 기존 파일에 대한 정보를 받거나 변경하는 데 속성 딕셔너리를 사용하면, 다른 여러가지 속성을 지정할 수 있고 파일 퍼미션도 지정할 수 있다. 파일을 생성할 때, 이 매개변수에 nil 을 지정하면 파일의 기본 퍼미션이 설정된다. fileAttributesAtPath:traverseLink: 메서드는 지정한 파일의 속성을 담은 딕셔너리를 반환한다. traverseLink: 매개변수는 심볼릭 링크에 대해 YES 혹은 NO 를 나타낸다. 만일 파일이 심볼릭 링크이고 YES 가 지정되었다면, 링크된 목적 파일의 속성이 반환된다. 만일 NO 가 지정되었다면 링크자체의 속성이 반환된다.

기존 파일의 속성 딕셔너리에는 파일의 소유자, 크기, 생성 날짜 같은 정보가 들어 있다. 딕셔너리에 담긴 각각의 속성은 키로 추출할 수 있는데 이 키들은 모두 Foundation/NSFilexternager.h 파일에 정의되어 었다. 예를 들어, NSFileSize 는 파일 크기에 대한 키다.


프로그램 16.1은 파일로 하는 기본 작업을 일부 보여 준다. 이 예제는 현재 디렉터리에 test 파일이 있고 다음 세 줄짜리 텍스트가 있다고 가정한다.

This is a test file with some data in it.
Here's another' line of data.
And a third.



프로그램 16.1


// 기본 파일 작업.
// 현재 작업 디렉터리에 "testfile"이라는 파일이 존재한다고 가정한다.

#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Foundation/NSFileManager.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSDictionary.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * = [[NSAutoreleasePool alloc] init];
    NSString *fName = @"testfile";
    NSFileManager *fm;
    NSDictionary *attr;

    // 파일 매니저의 인스턴스를 생성한다.

    fm = [NSFileManager defaultManager];

    // testfile의 존재 여부를 먼저 확인한다.

    if ([fm fileExistsAtPath: fName] == NO) {
        NSLog (@"File doesn't exist!");
        return 1;
    }

    // 이제 복사본을 만들자.

    if ([fm copyPath: fName toPath: @"newfile"handler: nil] == NO) {
        NSLog (@"File copy failed!");
        return 2;
    }

    // 두 개의 파일이 일치하는지 확인한다.

    if ([fm contentsEqualAtPath: fName andPath: @"newfile"] == NO) {
        NSLog (@"Files are not equal!");
        return 3;
    }

    // 사본의 이름을 변경하자.

    if ([fm movePath: @"newfile"toPath: @"newfile2"
                handler: nil] == NO) {
        NSLog (@"file rename failed!");
        return 4;
    }

    // newfile2의 크기를 알아낸다.

    if ((attr = [fm fileAttributesAtPath: @"newfile2"
                traverseLink: NO]) == nil) {
        NSLog (@"Couldn't get file attributes!");
    }

    NSLog (@"File size is %i bytes",
    [[attr objectForKey: NSFileSize] intValue]);

    // 원본 파일을 삭제한다.

    if ([fm removeFileAtPath: fName handler: nil] == NO) {
        NSLog (@"File removal failed!");
        return 6;
    }

    NSLog (@"All operations were successful!");

    // 새로 생성한 파일의 내용을 표시한다.

    NSLog (@"%@", [NSString stringWithContentsOfFile: @"newfile2"
    encoding: NSUTF8StringEncoding error: nil]);

    [pool drain];
    return 0;
}

프로그램 16.1 의 출력결과


File size is 84 bytes
All operations were successful!

This is a test file with some data in it.
Here's another line of data.
And a third.


프로그램은 먼저 testfile 의 존재 여부를 확인한다. 파일이 있다면, 사본을 만들고 두 파일이 동일한지 확인한다. 숙련된 UNIX 사용자는 copyPath:toPath: 와 movePath:toPath: 메서드에서 목적 디렉터리만 지정하면 파일을 해당 디렉터리로 이동이나 복사하지 못한다는 점에 주의해야 한다. 반드시 디렉터리 내의 파일명을 지정해 주어야 한다.


objc2_notice_01
Xcode에서 File 메뉴에서 'New File...'을 선택하여 testfile 을 생성한다. 그때 뜨는 창의 왼쪽에서 Other 를 선택하고 오른쪽에서 EmptyFile 을 선택한다. 파일명으로 testfile 을 입력하고 실행 파일과 동일한 디렉터리 내에 생성하도록 설정한다. 이 디렉터리는 프로젝트의 Build/Debug 폴더일 것이다.


movePath:toPath: 를 사용하여 한 디렉터리의 파일을 다른 디렉터리로 옮길 수 있다(디렉터리 전체를 옮기는 데도 쓸 수 있다). 만일 두파일 경로가 동일한 디렉터리에 있다면(우리의 예처럼), 그저 파일 이름만 바꾸는 결과가 된다. 프로그램 16.1 에서는 그저 이 메서드를사용하여 newfile 을 newfile2 로 이름만 변경했다.

표 16.1 에서 설명한 대로 복사, 변경, 이등을 할 때 목적 파일이 이미 있어서는 안 된다. 만일 있다면 이 작업이 실패한다.

newfile2 의 크기는 fileAttributesAtPath:traverseLink: 메서드를 사용하여 알아낸다. 반환되는 딕셔너리가 혹시 nil 은 아닌지 확인한 후, NSDictionary 의 메서드인 objectForKey: 와 키 NSFileSize 를 사용하여 딕셔너리에서 파일 크기를 가져온다. 그 다음 딕셔너리에서 받은 정수값을 표시한다.

프로그램은 removeFileAtPath:handler: 메서드를 사용히여 testfile 을 삭제한다.

마지막으로 NSString 의 stringWithContentsOfFile:encoding:error: 메서드를 사용하여 newfile2 의 내용을 읽어 스트링 객체에 집어넣는다. 이 스트링 객체는 NSLog 의 인수로 건네져 표시된다.

프로그램 16.1 에서 각 파일작업이 성공했는지 확인한다. 그중 하나라도 실패하면, NSLog 로 오류를 기록하고 프로그램은 0 이 아닌 종료 상태(exit status)를 반환하며 종료된다. 관례상 0 이 아닌 값들은 프로그램 오류를 나타내는데, 각각 다른 오류 종류를 표시한다. 커맨드라인 도구를 작성한다면, 이 기법을 사용하면 셀 스크립트처럼 다른 프로그램이 반환값을 확인할 수 있으므로 매우 유용하다.


NSData 클래스 사용하기

파일을 다루다 보면 이따금 데이터를 '버퍼(buffer)' 라는 임시 저장 공간에 저장해야 할 때가 있다. 파일에 출력하고자 데이터를 모을 때도 저장공간이 자주 사용된다. Foundation 의 NSData 클래스를 이용하면, 버퍼를 생성하고, 파일 내용을 읽어 버퍼에 저장하고, 버퍼 내용을 파일로 기록하는 작업이 쉬워진다. 혹시라도 궁금해하는 독자를 위해 설명하자면, 32비트 응용 프로그램의 경우 NSData 버퍼는 최대 2GB 까지 저장할 수 있다. 64 비트 응용 프로그램은, 최대 8EB(exabyte, 다시 말해서 8,000,000,000 GB)까지 저장할 수 있다.

이미 예상했겠지만, 수정 불가능한 저장 공간(NSData)과 수정 가능한 저장 공간 (NSMutableData) 가운데 하나를 선택해 정의한다. 이 장과 다음 장에서 NSData 클래스의 메서드를 소개할 것이다.

프로그램 16.2는 파일 내용을 읽어 메모리의 버퍼에 저장하기가 얼마나 쉬운지 보여 준다. 프로그램은 newfile2 의 내용을 읽어 새 파일인 newfile3 에 기록한다. 어떤 면에서 보면, copyPath:toPath:handler: 방식만큼 직접적이지는 않지만, 파일을 복사하는 작업을 구현한다고 할 수 있다.


프로그램 16.2


// 파일의 사본을 만든다.

#import <Foundation/NSObject.h
#import <Foundation/NSString.h>
#import <Foundation/NSFileManager.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSData.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] inti];
    NSFileManager *fm;
    NSData *fileData;

    fm = [NSFileManager defaultManager];

    // newfile2 파일을 읽는다.

    fileData = [fm contentsAtPath: @"newfile2"];

    if (fileData == nil) {
        NSLog (@"File read failed!");
        return 1;
    }

    // file3에 데이터를 기록한다.
    if ([fm createFileAtPath: @"newfile3" contents: fileData
            attributes: nil] == NO) {
        NSLog (@"Couldn't create the copy!");
        return 2;
    }

    NSLog (@"File copy was successful!");

    [pool drain];
    return 0;
}

프로그램 16.2 의 출력결과


File copy was successful!


NSData 의 contentsAtPath: 메서드는 경로명을 받아 지정된 파일의 내용을 읽어 생성한 저장공간에 담는다. 그리고 저장공간 객체를 결과로 반환한다. 만일 읽기를 실패하면(파일이 없거나, 읽기 권한이 없는 경우) nil 을 반환한다.

createFileAtPath:contents:attributes: 메서드는 지정한 속성으로 파일을 생성한다(혹 attributes 인수가 nil 이라면 기본 속성으로 생성한다). 그런 뒤, 지정된 NSData 객체의 내용물이 파일에 기록된다. 우리 예제에서는, 이 데이터 영역에 앞서 읽은 파일의 내용이 들어 있다.


디렉터리 다루기

NSFileManager 가 지원하는, 디렉터리를 다루는 메서드를 표 16.2에서 요약한다. 이 메서드들은 대부분 표 16.1 에 나온 일반 파일에 사용되는 메서드와 동일하다.

메서드 설명
-(NSString *) currentDirectoryPath 현재 디렉터리 경로를 가져온다.
-(BOOL) changeCurrentDirectoryPath:
path
현재 디렉터리를 변경한다.
-(BOOL) copyPath: from toPath:
to handler: handler
디렉터리 구조를 복사한다. to는 이미 있어서는 안된다.
-(BOOL) createDirectoryAtPath:
path attributes: attr
새 디렉터리를 생성한다.
-(BOOL) fileExistsAtPath:
path isDirectory:(BOOL *) flag
파일이 디렉터리인지 확인한다(YES/NO 결과가 flag에 저장된다).
-(NSArray *) directoryContentsAtPath:
path
디렉터리의 내용을 나열한다.
-(NSDirectoryEnumerator *)
enumeratorAtPath: path
디렉터리 내용을 열거한다.
-(BOOL) removeFileAtPath:
path handler: handler
빈 디렉터리를 삭제한다.
-(BOOL) movePath: from toPath:
to handler: handler
디렉터리 이름을 변경하거나 디렉터리를 옮긴다. to가 이미 있어서는 안 된다.
표 16.2 자주 사용되는 NSFileManager 디렉터리 메서드


프로그램 16.3에 디렉터리를 다루는 기본 작업이 나와있다.


프로그램 16.3


// 기본 디렉터리 작업

#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Foundation/NSFileManager.h>
#import <Foundation/NSAutoreleasePool.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSString *dirName = @"testdir";
    NSString *path;
    NSFileManager *fm;

    // 파일 매니저의 인스턴스를 생성한다.

    fm = [NSFileManager defaultManager];

    // 현재 디렉터리를 받아 온다.

    path = [fm currentDirectoryPath];
    NSLog (@"Current directory path is %@", path);

    // 새 디렉터리를 생성한다.
    if ([fm createDirectoryAtPath: dirName attributes: nil] == NO) {
        NSLog (@"Couldn't create directory!");
        return 1;
    }

    // 새 디렉터리의 이름을 변경한다.

    if ([fm movePath: dirName toPath: @" newdir" handler: nil] == NO) {
        NSLog (@"Directory rename failed!");
        return 2;
    }

    // 새 디렉터리로 현재 디렉터리를 이동한다.

    if ([fm changeCurrentDirectoryPath: @"newdir"] == NO) {
        NSLog (@"Change directory failed!");
        return 3;
    }

    // 현재 디렉터리를 받아 표시한다.

    path = [fm currentDirectoryPath];

    NSLog (@"Current directory path is %@", path);
    NSLog (@"All operations were successful!");

    [pool drain];
    return 0;
}

프로그램 16.3 의 출력결과


Current directory path is /Users/stevekochan/progs/ch16
Current directory path is /Users/stevekochan/progs/ch16/newdir
All operations were successful!


프로그램 16.3 은 알기쉽게 되어있다. 먼저 현재 디렉터리 경로를 얻어온다. 그 다음에는 현재 디렉터리에 testdir 이라는 새 디렉터리를 생성한다. 프로그램은 movePath:toPath:handler: 메서드를 사용하여 새 디렉터리의 이름을 testdir 에서 newdir 로 바꾼다. 이 메서드로 디렉터리 구조 전체를 (내용을 포함하여) 파일시스템의 한 위치에서 다른 위치로 옮길수도 있음을 기억하자.

새 디렉터리의 이름을 바꾼다음, 프로그램은 changeCurrentDirectoryPath: 메서드를 사용하여 새 디렉터리를 현 디렉터리로 만든다. 그후 현재 디렉터리 경로를 표시하여 제대로 바뀌었는지 확인한다.

디렉터리 내용 열거하기

디렉터리 내용을 나열해야 할 때가 이따금 있다. 열거할 때는 enumerationAtPath: 나 directoryContentsAtPath: 메서드를 사용한다. enumerationAtPath: 는 지정된 디렉터리의 각 파일을 한번에 하나썩 열거하며, 기본적으로 파일중 하나가 디렉터리일 경우 디 렉터리의 내용도 열거한다. 이 과정에서 skipDescendants 메시지를 열거 객체에 보내면, 디렉터리에 들어 있는 내용을 열거하는 작업을 동적으로 중단할 수 있다. directoryContentsPath: 는, 지정된 디렉터리의 내용을 열거하고 파일 목록을 배열로 반환한다. 만일 디렉터리에 포함된 파일 중에 디렉터리가 있어도, 이 메서드는 그 디렉터리의 내용을 열거하지 않는다.

프로그램 16.4는 프로그램에서 이 메서드를 사용하는 방법을 보여 준다.


프로그램 16.4


// 디렉터리의 내용물을 열거한다.

#import <Foundation/NSString.h>
#import <Foundation/NSFileManager.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSArray.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSString *path;
    NSFileManager *fm;
    NSDirectoryEnumerator *dirEnum;
    NSArray *dirArray;

    // 파일 매니저의 인스턴스를 생성한다.

    fm = [NSFileManager defaultManager];

    // 현재 작업 디렉터리 경로를 받아 온다.

    path = [fm currentDirectoryPath];

    // 디렉터리를 열거한다.

    dirEnum = [fm enumeratorAtPath: path);

    NSLog (@"Contents of %@:", path);

    while ((path = [dirEnum nextObject]) != nil)
        NSLog (@"%@", path);

    // 디렉터리 열거의 다른 방법
    dirArray = [fm directoryContentsAtPath:
                [fm currentDirectoryPath]];
    NSLog (@"Contents using directoryContentsAtPath:");

    for ( path in dirArray )
        NSLog (@"%@", path);

    [pool drain];
    return 0;
}

프로그램 16.4 의 출력결과


Contents of /Users/stevekochan/mysrc/ch16:
a.out
dir1.m
dir2.m
file1.m
newdir
newdir/file1.m
newdir/output
path1.m
testfile

Contents using directoryContentsAtPath
a.out
dir1.m
dir2.m
file1.m
newdir
path1.m
testfile


다음 코드를 상세히 살펴보자.

dirEnum = [fm enumeratorAtPath: path];

NSLog (@"Contents of %@:", path);

while ((path = [dirEnum nextObject]) != nil)
    NSLog (@"%@", path);


디렉터리를 열거하는 작업은 파일 매니저 객체에 enumerationAtPath: 메시지를 보냄으로써 시작한다. 여기서 파일 매니저 객체는 fm 이다. enumerationAtPath: 메서드는 NSDirectoryenumerator 객체를 반환하고 이 객체는 dirEnum 에 저장된다. 이제 이 객체에 nextObject 메시지를 보낼 때마다, 열거 중인 디렉터리 안의 다음 파일에 대한 경로를 받는다. 열거할 파일이 더 존재하지 않으면 nil 이 반환된다.

프로그램 16.4 를 출력한 결과에서 두 열거 기법이 어떻게 다른지 볼 수 있다. enumeratorAtPath: 메서드는 newdir 디렉터리의 내용을 나열하는 반면, directoryContentsAtPath: 메서드는 나열하지 않는다. 만일 newdir 에 하부 디렉터리가 있었다면, enumerationAtPath: 는 그 디렉터리들도 열거했을 것이다.

자, 설명했듯이 코드를 다음과 같이 변경하면 프로그램 16.4 의 while 문을 실행하는 도중에 하부 디렉터리가 열거되지 않게 만들 수 있다.

while ((path = [dirEnum nextObject]) != nil) {
    NSLog (@"%@", path);

    [fm fileExistsAtPath: path isDirectory: &flag];

    if (flag == YES)
        [dirEnum skipDescendents];
}


여기서 flag 는 BOOL 변수다. fileExistAtPath: 는 지정한 경로가 디렉터리일 경우에는 flag 에 YES 를 저장하고, 디렉터리가 아닐 경우에는 NO 를 저장한다.

덧붙여 말하자면, dirArray 의 내용 전체를 표시하려 할 때, 위의 코드처럼 빠른 열거를 쓰는 대신, 다음처럼 NSLog 를 한번 호출해 표시할 수도 있다.

NSLog (@"%@", dirArray);


경로 다루기 - NSPathUtilities.h

NSPathUtilities.h 파일에는 경로를 다루는 함수와 NSString 을 확장하는 카테고리가 들어 있다. 가능한 한 이 함수들과 메서드를 사용하여 파일시스템의 구조, 특정 파일과 특정 디렉터리의 위치에서 프로그램을 독립시켜야 한다. 프로그램 16.5 는 NSPathUtilities.h 가 지원하는 몇몇 함수와 메서드를 사용하는 법을 보여 준다.


프로그램 16.5


// 기본 경로 작업

#import <Foundation/NSString.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSFileManager.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSPathUtilities.h>

int main(int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSString *fName = @"path.m";
    NSFileManager *fm;
    NSString *path, *tempdir, *extension, *homedir, *fullpath;
    NSString *upath = @"~stevekochan/progs/../ch16/./path.m";

    NSArray *components;

    fm = [NSFileManager defaultManager];

    // 임시 작업 디렉터리를 얻어 온다.

    tempdir = NSTemporaryDirectory ();

    NSLog (@"Temporary Directory is %@", tempdir);

    // 현재 디렉터리에서 기본 디렉터리를 추출한다.

    path = [fm currentDirectoryPath];
    NSLog (@"Base dir is %@", [path lastPathComponent]);

    // 현재 디렉터리의 fName 파일의 전체 경로를 생성한다.

    fullpath = [path stringByAppendingPathComponent: fName];
    NSLog (@"fullpath to %@ is %@", fName, fullpath);

    // 파일 확장자 얻어오기

    extension = [fullpath pathExtension];
    NSLog (@"extension for %@ is %@", fullpath, extension);

    // 사용자의 홈 디렉터리 얻어오기

    homedir = NSHomeDirectory ();
    NSLog (@"Your home directory is %@", homedir);

    // 경로를 각 요소로 분리한다.

    components = [homedir pathComponents];

    for ( path in components)
        NSLog (@"%@", path);

    // 경로를 표준화한다.

    NSLog (@"%@ => %@", upath ,
    [upath stringByStandardizingPath] );

    [pool drain];
    return 0;
}

프로그램 16.5 의 출력결과


Temporary Directory is /var/folders/HT/HTyGLvSNHTuNb6NrMuo7QE+++TI/-tmp-/
Base dir is examples
fullpath to path.m is /Users/stevekochan/progs/examples/path.m
extension for /Users/stevekochan/progs/examples/path.m is m
Your home directory is /Users/stevekochan
/
Users
stevekochan
~stevekochan/progs/../ch16/./path.m => /Users/stevekochan/ch16/path.m


함수 NSTemporaryDirectory 는 임시 파일을 생성하는 데 쓰는 시스템의 디렉터리 경로를 반환한다. 임시 파일을 이 경로에 생성했다면, 다 사용한 뒤 반드시 삭제해 줘야 한다. 또한, 파일명이 겹치지 않도록 주의해야 한다. 특히, 여러분의 응용 프로그램이 동시에 여러 인스턴스를 실행시키고 있다면 더 신경 쓴다(이 장의 연습문제 5 를 보라). 여러 명이 시스템에 로그인 하여 동일한 프로그램을 실행하는 경우,이런 일이 쉽게 발생한다.

lastPathComponent 메서드는 경로에서 마지막 파일을 추출한다. 이 메서드는 절대 경로에서 기본 파일 이름만 받고자 할 때 매우 유용하다.

stringByAppendingPathComponent: 메서드는 경로의 끝에 파일명을 끼워 넣을때 유용하다. 만일 수신자로 지정된 경로명이 슬래시로 끝나지 않는다면 이 메서드가 알아서 슬래시를 경로명에 끼워 넣어 추가된 파일명과 경로명을 분리해 준다. currentDirectory: 메서드와 stringByAppendingPathComponent: 메서드를 결합하여 현재 디렉터리에 있는 파일의 전체 경로를 생성할 수 있다. 이 기법이 프로그램 16.5 에서 사용된다.

pathExtension 메서드는 제공한 경로에서 파일 확장자만 추출해 준다. 예제에서 본 파일 path.m 의 확장자는 m 이므로, 이 메서드는 m 을 반환해 줄 것이다. 파일에 확장자가 없다면, 그저 빈 스트링을 반환한다.

NSHomeDirectory 함수는 현재 사용자의 홈 디렉터리를 반환한다. 특정 사용자의 홈 디렉터리가 필요하다면 NSHomeDirectorforUser 함수에 사용자 이름을 인수로 써준다.

pathComponents 메서드는 지정한 경로에 들어 있는 각 요소를 담은 배열을 반환한다. 프로그램 16.5 는 반환된 배열의 항목을 돌며 경로 요소들을 각각 다른 줄에 출력한다.

마지막으로, 앞에서 논의했듯이 경로명에 틸데(~) 문자가 들어 있을 수도 있다. NSFileManager 메서드는 ~를 사용자의 홈 디렉터리를 축약한 글자로 본다. 그러므로 ~user 는 지정한 사용자의 홈 디렉터리를 축약한 것으로 받아들인다. 만일 경로명에 틸데 문자가 있다면, stringByStandardizingPath: 메서드를 사용하여 풀어낼 수 있다. 이 메서드는 이런 특수 문자를 제거하거나 혹은 표준화한 경로를 반환한다. stringByExpandingTildeInPath: 메서드를 사용하여 틸데 문자만 확장할 수도 있다.


경로 작업에 자주 사용되는 메서드

표 16.3 에 경로를 다룰 때 자주 사용하는 메서드를 요약했다. 이 표에서 components 는 NSArray 객체로, 경로에 들어 있는 각 항목에 해당하는 스트링 객체를 담는다. path 는 파일 경로를 나타내는 스트링 객체다. ext 는 경로 확장자를 나타내는 스트링 객체다(예를들어 @"mp4" ).

메서드 설명
+(NSString *) pathWithComponents:
components
components의 항목들을 이용해서 유효한 경로를 구성한다.
-(NSArray *) pathComponents 경로를 구성 항목들로 분리한다.
-(NSString *) lastPathComponent 경로에 있는 마지막 항목을 추출한다.
-(NSString *) pathExtension 경로의 마지막 항목의 확장자를 추출한다.
-(NSString *) stringByAppendingPath
Component: path
기존 경로의 끝에 path를 더한다.
-(NSString *) stringByAppendingPath
Extension: ext
경로의 마지막 항목에 지정한 확장자를 더한다.
-(NSString *)
stringByDeletingLastPathComponent
마지막 경로 항목을 삭제한다.
-(NSString *) stringByDeletingPath
Extension
마지막 경로 항목의 확장자를 삭제한다.
-(NSString *) stringByExpanding
TildeInPath
사용자의 홈 디렉터리(~)나, 지정한 사용자의 홈 디렉터리(~user)에 이르는 절대 경로를 반환한다. 틸데를 풀어 절대 경로를 작성하는 것을 틸데를 확장한다고 말한다.
-(NSString *)
stringByResolvingSymlinksInPath
경로에 있는 심볼릭 링크를 풀려고 시도한다.
-(NSString *)
stringByStandardizingPath
~, ..(부모경로), .(현재 경로), 심볼릭 링크를 풀어 경로를 표준화하려 한다.
표 16.3 자주 사용되는 경로 유틸리티 메서드


함수 설명
NSString *NSUserName (void) 현재 사용자의 로그인 이름을 반환한다.
NSString *NSFullUserName (void) 현재 사용자의 사용자 이름을 반환한다.
NSString *NSHomeDirectory (void) 현재 사용자의 홈 디렉터리 경로를 반환한다.
NSString *NSHomeDirectoryForUser
(NSString * user)
user의 홈 디렉터리 경로를 반환한다.
NSString *NSTemporaryDirectory (void) 임시 파일을 생성할 때 사용할 수 있는 디렉터리의 경로를 반환한다.
표 16.4 자주 사용되는 경로 유틸리티 함수


표 16.4는 사용자, 사용자 홈 디렉터리, 임시 파일을 저장하는 디렉터리에 대한 정보를 제공하는 함수를 나열한다.

또한 Foundation 함수인 NSSearchPathForDirectoryInDomains 를 사용하여 응용프로그램 디렉터리같이 시스템에서 특별한 디렉터리들을 찾을 수 있다.


파일 복사하기와 NSProcessInfo 클래스 사용하기

프로그램 16.6 은 간단한 파일 복사를 수행하는 커맨드라인 도구를 보여준다. 이 커맨드는 다음과 같이 사용한다.

copy from-file to-file


NSFileManager 의 copyPath:toPath:handler: 메서드와 달리, 이 커맨드라인 도구는 to-file 이 디렉터리명이어도 된다. 이 경우, 파일은 to-file 디렉터리 안에 from-file 이라는 이름으로 저장된다. 또한 메서드와 달리, 이미 tcrfìleo] 있어도 덮어쓰기가 허용된다. 이렇게 하면 표준 UNIX 복사 명령인 cp 와 더 같아지게 된다.

커맨드라인에서 파일명은 main 의 argc, argv 인수를 사용하여 가져올 수 있다. 이 두 인수는 각각 커맨드라인에 입력된 인수의 개수(커맨드 이름도 포함)와, C 스타일 문자 스트링의 배열을 가리키는 포인터다.

argv 를 다룰 때 C 스트링 대신, Foundation 클래스인 NSProcessInfo 를 사용한다. NSProcessInfo 는 동작하는 프로그램(프로세스)에 대한 다양한 정보를 설정하고, 또 받아올 수 있는 메서드를 담고 있다. 표 16.5 에 이 메서드들이 요약되어 있다.


메서드 설명
+(NSProcessInfo *) processInfo 현재 프로세스에 대한 정보를 반환한다.
-(NSArray *) arguments 현재 프로세스의 인수를 NSString 객체의 배열로 반환한다.
-(NSDictionary *) environment 현재 (PATH, HOME과 같은) 환경 변수와 그 값을 나타내는 변수-값 묶음의 딕셔너리를 반환한다.
-(int) processIdentifier 운영체제가 동작 중인 각 프로세스를 구분하려고 부여한, 유일무이한 숫자인 프로세스 식별자를 반환한다.
-(NSString *) processName 현재 실행 중인 프로세스의 이름을 반환한다.
-(NSString *)
globallyUniqueString
호출될 때마다 다른 유일무이한 스트링을 반환한다. 유일무이한 임시 파일명을 생성할 때 사용할 수 있다(연습문제 5를 보라).
-(NSString *) hostName 호스트 시스템의 이름을 반환한다(내 Mac OS X 시스템에서는 Steve-Kochans-Computer.local을 반환한다).
-(NSString *) operatingSystemName 운영체제의 이름을 반환한다(내 Mac에서는 NSMACHOperatingSystem 상수를 반환한다. 가능한 반환 값들은 NSProcessInfo.h에 정의되어 있다).
-(NSString *)
operatingSystemVersionString
운영체제의 현재 버전을 반환한다(내 Mac OS X 시스템에서는 'Version 10.5.4 (Build 9E17)'이 반환된다).
-(void) setProcessName:
(NSString *) name
현재 프로세스의 이름을 name으로 설정한다.(사용자 디폴트 설정에서와 같이) 프로세스 이름을 추측하는 경우가 있으니, 사용할 때 주의해야 한다.
표 16.5 NSProcessInfo 메서드



프로그램 16.6


// 기본 복사 유틸리티 구현하기

#import <Foundation/NSString.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSFileManager.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSPathUtilities.h>
#import <Foundation/NSProcessInfo.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSFileManager *fm;
    NSString *source, *dest;
    BOOL isDir;
    NSProcessInfo *proc = [NSProcessInfo processInfo];
    NSArray *args = [proc arguments];

    fm = [NSFileManager defaultManager];

    // 커맨드라인의 인수 두 개를 확인한다.

    if ([args count] != 3) {
        NSLog (@"Usage: %@ src dest", [proc processName]);
        return 1;
    }
    source = [args objectAtIndex: 1];
    dest = [args objectAtIndex: 2];

    // 소스파일을 읽을 수 있는지 확인한다.

    if ([fm isReadableFileAtPath: source] == NO) {
        NSLog (@"Can't read %@", source);
        return 2;
    }

    // 목적 파일이 디렉터리인지 확인한다.
    // 만일 그렇다면 목적 파일 뒤에 소스를 붙인다.

    [fm fileExistsAtPath: dest isDirectory: &isDir];

    if (isDir == YES)
        dest = [dest stringByAppendingPathComponent:
                [source lastPathComponent]];

    // 목적 파일이 이미 존재하면 삭제한다.

    [fm removeFileAtPath: dest handler: nil];

    // 이제 복사를 수행한다.

    if ([fm copyPath: source toPath: dest handler: nil] == NO) {
        NSLog (@"Copy failed!");
        return 3;
    }

    NSLog (@"Copy of %@ to %@ succeeded!", source, dest);

    [pool drain];
    return 0;
}



프로그램 16.6 의 출력결과


$ ls -l
total 96
-rwxr-xr-x 1 stevekoc staff 19956 Jul 24 14:33 copy
-rw-r--r-- 1 stevekoc staff 1484 Jul 24 14:32 copy.m
-rw-r--r-- 1 stevekoc staff 1403 Jul 24 13:00 file1.m
drwxr-xr-x 2 stevekoc staff 68 Jul 24 14:40 newdir
-rw-r--r-- 1 stevekoc staff 1567 Jul 24 14:12 path1.m
-rw-r--r-- 1 stevekoc staff 84 Jul 24 13:22 testfile
$ copy
Usage: copy src dest
$ copy foo copy2
Can't read foo
$ copy copy.m backup.m
Copy of copy.m to backup.m succeeded!
$ diff copy.m backup.m
$ copy copy.m newdir
Copy of copy.m to newdir/copy.m succeeeded!
$ls -l newdir
total 8
-rw-r--r-- 1 stevekoc staff 1484 Jul 24 14:44 copy.m
$


NSProcessInfo 의 arguments 메서드는 스트링 객체의 배열을 반환한다. 배 열의 첫째 원소는 프로세스 이름이고, 나머지 원소들은 커맨드라인에서 입력된 인수를 담는다.

먼저,커맨드라인에 인수두개가 입력되었는지를 확인해야한다. arguments 메서드에서 반환된 배열 args 의 크기를 확인하여 알아낸다. 확인에 성공하면 프로그램은 args 배열의 소스파일과 목적 파일 이름을 추출하여 그 값을 각각 source 와 dest 에 저장한다.

그런 뒤 프로그램은 소스파일을 읽을 수 있는지 확인하여, 읽을 수 없다면 오류 메시지를 발행하고 프로그램을 종료한다.

다음명령문은 dest 로 지정된 파일이 디렉터리인지를 확인한다.

[fm fileExistsAtPath: dest isDirectory: &isDir];


앞에서 보았듯이 대답(YES 혹은 NO)은 변수 isDir에 저장된다.

만일 변수 dest 가 디렉터리라면, 소스파일 이름의 마지막구성 요소를 디렉터리명 의 뒤에 덧붙이고 싶을 것이다. 경로 유틸리티 메서드인 stringByAppendingPathComponent: 를 써서 이 작업을 수행할 수 있다. 따라서 source 의 값이 스트링 ch16/copy1.m 이고 dest의 값이 /Users/stevekochan/progs 인 데다가, dest 의 값에서 마지막 부분이 디렉터리 였다면, dest의 값을 /Users/stevekochan/progs/copy1.m 으로 바꾸게 된다.

copyPath:ToPath:handler: 메서드는 파일 덮어쓰기를 허용하지 않는다. 따라서, 오류를 피하기 위해 프로그램은 먼저 removeFileAtPath:handler: 메서드를 사용하여 목적 파일의 삭제부터 시도한다. 이 메서드의 성공 여부는 크게 중요하지 않다. 목적 파일이 없다면 어찌 되었든 실패할 것이기 때문이다.

프로그램의 마지막에 도달하면, 모든 작업이 무사히 수행되었다고 볼 수 있다. 만일 성공했다면 이를 나타내는 메시지를 표시한다.


기본 파일 작업 - NSFileHandle

NSFileHandle 이 제공하는 메서드를 사용하면 파일을 좀더 면밀하게 다룰 수 있다. 이 장의 시작 부분에서 이 메서드들로 가능한 작업들을 나열한것이 기억나는가?


파일을 다룰 때는 보통 다음과 같은 세 단계를 거친다.

  1. 파일을 열고, 이어지는 I/O 작업에서 그 파일을 참조하는 NSFileHandle 객체를 얻는다.
  2. 열려 있는 파일에 대해 I/O 작업을 수행한다.
  3. 파일을 닫는다.


표 16.6 에 자주 사용되는 NSFileHandle 메서드를 요약했다. 이 표에서 fh 는 NSFileHandle 객체이고, data 는 NSData 객체다. path 는 NSString 객체이고, offset 은 unsigned long long 형 이다.

표에 나오지 않은 메서드로는 표준 입력, 표준 출력, 표준 오류, 널 장치를 위한 NSFileHandle 을 가져오는 메서드들이 있다. 이것들은 fileHandleWithDevice 의 형태로, Device 가 StandardInput, StandardOutput, StandardError, NullDevice 가운데 하나다.

여기에 넣지 않은 또다른 메서드로는 백그라운드에서 데이터를 읽고 쓰는, 즉 비동기 작업을 하는 메서드들이 있다.

메서드 설명
+(NSFileHandle *)
fileHandleForReadingAtPath:
path 읽을 파일을 연다.
+(NSFileHandle *)
fileHandleForWritingAtPath: path
작성할 파일을 연다.
+(NSFileHandle *)
fileHandleForUpdatingAtPath: path
업데이트할 파일을 연다(읽기와 쓰기를 한다).
-(NSData *) availableData 장치나 채널에서 읽어올 수 있는 데이터를 반환한다.
-(NSData *) readDataToEndOfFile 파일의 끝까지 남은 데이터를 읽는다. 파일 크기는 최대 (UNIT_MAX) 바이트다.
-(NSData *) readDataOfLength:
(NSUInteger) bytes
지정한 특정 bytes 수만큼 파일에서 읽어들인다.
-(void) writeData: data 파일에 data를 기록한다.
-(unsigned long long)
offsetInFile
현재 파일의 오프셋을 가져온다.
-(void) seekToFileOffset: offset 현재 파일의 오프셋을 설정한다.
-(unsigned long long)
seekToEndOfFile
현재 파일의 오프셋을 파일 끝으로 설정한다.
-(void) truncateFileAtOffset:
offset
파일 크기를 offset 바이트로 설정한다(필요하다면 채워 넣는다).
-(void) closeFile 파일을 닫는다.
표 16.6 자주 사용되는 NSFileHandle 메서드


NSFileHandle 클래스에는 파일을 생성하는 메서드가 없다. 파일 생성은 앞에서 이미 살펴본 NSFileManager 메서드로 해야 한다. 따라서 fileHandleForWritingAtPath: 와 fileHandleForUpdatingAtPath: 는 모두 파일이 존재한다고 가정하고, 파일이 없을 때는 nil 을 반환한다. 두 메서드 모두, 파일 오프셋이 파일의 맨 처음으로 설정되므로, 파일 쓰기(혹은 업데이트 모드에서 읽기) 작업은 모두 파일의 시작점에서 시작한다. 또한 UNIX 프로그래밍에 익숙하다면, 한 가지 알아 둘 점이 있다. 파일을 쓰기 위해 연다고 해서 파일이 잘리지는 않는다. 파일을 자를 생각이라면 직접 해줘야 한다.

프로그램 16.7 은 이 장을 시작할 때 만들었던 원본 testfile 을 열어 내용을 읽고, 그 내용을 testout 이라는 파일에 복사한다.


프로그램 16.7


// 파일 핸들 기본 작업
// testfile이 현재 작업 디렉터리에 있다고 가정한다.

#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Foundation/NSFileHandle.h>
#import <Foundation/NSFileManager.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSData.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSFileHandle *inFile, *outFile;
    NSData *buffer;

    // testfile을 읽기 위해 연다.

    inFile = [NSFileHandle fileHandleForReadingAtPath: @"testfile"];

    if (inFile == nil) {
        NSLog (@"Open of testfile for reading failed");
        return 1;
    }

    // 필요하다면 출력 파일을 생성한다.

    [[NSFileManager defaultManager] createFileAtPath: @"testout"
    contents: nil attributes: nil];

    // outfile을 기록을 위해 연다.

    outFile = [NSFileHandle fileHandleForWritingAtPath: @"testout"];

    if (outFile == nil) {
        NSLog (@"Open of testout for writing failed");
        return 2;
    }

    // 자료가 담겨 있는 경우에 대비해 파일의 내용을 잘라낸다.

    [outFile truncateFileAtOffset: 0];

    // inFile에서 데이터를 읽어 outFile에 기록한다.

    buffer = [inFile readDataToEndOfFile];

    [outFile writeData: buffer];

    // 두 파일을 닫는다.

    [inFile closeFile];
    [outFile closeFile];

    // 파일의 내용을 확인한다.

    NSLog (@"%@", [NSString stringWithContentsOfFile: @"testout"
            encoding:NSUTF8StringEncoding error: nil]);

    [pool drain];
    return 0;
}

프로그램 16.7 의 출력결과


This is a test file with some data in it.
Here's another line of data.
And a third.


readDataToEndotFile: 메서드는 데이터를 한 번에 최대 (limits.h 파일에 정의되어 있고 FFFFFFFF(16) 과 동일한) UINT_MAX 바이트만큼 읽는다. 여러분이 어떤 프로그램을 작성하더라도 이 정도 크기면 충분히 크다. 또한, 작업을 나눠서 더 작은크기로 읽거나 쓸 수 있다. 또한 반복문을 만들어 readDataOfLength: 메서드를 사용하여 버퍼를 가득 채워 버퍼와 파일을 한꺼번에 전송할 수도 있다. 예를들어 버퍼 크기가 8,192(8kb) 이거나 130,072(128kb) 일 수 있다. 보통 2 의 거듭제곱이 사용되는데, 하부 운영체제가 I/O 작업을 그 크기의 데이터 단위로 처리하기 때문이다. 여러분의 시스템에서 다른 크기를 시험하여 무엇이 가장 성능이 좋은지 알아봐도 좋겠다.

읽기 메서드가 파일 끝에 도달할 때까지 아무 데이터도 읽지 못했다면 비어있는 NSData 객체(바이트가 없는 버퍼)를 반환할 것이다. 버퍼에 length 메서드를 적용해 0 과 같은지 확인하여 파일에서 읽어올 데이터가 남아있는지 확인할 수 있다.

업데이트를 하려고 파일을 열면,파일 오프셋이 파일의 시작점으로 설정되어 있을 것이다. 파일에서 검색하여 오프셋을 바꾼 후에, 읽기나 쓰기 작업을 할 수 있다. 따라서 databaseHandle 이 핸들인 파일에서 10 번째 바이트를 찾으려면, 다음처럼 메시지 표현식을 작성한다.

[databaseHandle seekToFileOffset: 10);


파일의 상대적 위치를 잡으려면, 현재 파일 오프셋을 얻은 다음, 거기에서 더하거나 빼야 한다. 따라서, 파일에서 128 바이트를 건너뛰려면 다음과 같은 명령문을 작성한다.

[databaseHandle seekToFileOffet: 10
    [databaseHandle offsetInFile] + 128];


파일에서 정수 다섯 개만큼 돌아가려면 다음을 작성한다.

[databaseHandle seekToFileOffet:
    [databaseHandle offsetInFile] - 5 * sizeof (int)];


프로그램 16.8 은 한 파일의 내용을 다른 파일에 붙여 넣는다. 읽기 작업을 위해 두 번째 파일을 열어 파일의 끝을 찾고 첫번째 파일의 내용을 두 번째 파일에 기록한다.

프로그램 16.8 실행 전의 FileA 내용

This is line 1 in the first file.
This is line 2 in the first file.


프로그램 16.8 실행 전의 FileB 내용

This is line 1 in the second file.
This is line 2 in the second file.



프로그램 16.8


// fileA를 fileB 끝에 붙인다.

#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Foundation/NSFileHandle.h>
#import <Foundation/NSFileManager.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSData.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSFileHandle *inFile, *outFile;
    NSData *buffer;

    // fileA를 읽기 위해 연다.

    inFile = [NSFileHandle fileHandleForReadingAtPath: @"fileA"];

    if (inFile == nil) {
        NSLog (@"Open of fileA for reading failed");
        return 1;
    }

    // fileB를 업데이트하기 위해 연다.

    outFile = [NSFileHandle fileHandleForWritingAtPath: @"fileB"];

    if (outFile == nil) {
        NSLog (@"Open of fileB for writing failed");
        return 2;
    }

    // outFile의 끝으로 이동한다.

    [outFileseekToEndOfFile];

    // inFile을 읽어 outFile에 그 내용을 기록한다.

    buffer = [inFile readDataToEndOfFile];
    [outFile writeData: buffer];

    // 두 파일을 닫는다.

    [inFile closeFile];
    [outFile closeFile];

    [pool drain];
    return 0;
}

프로그램 16.8 의 출력결과


Contents of fileB
This is line 1 in the second file.
This is line 2 in the second file.
This is line 1 in the first file.
This is line 2 in the first file.


출력 결과를보면 첫째 파일의 내용이 둘째 파일 끝에 잘 붙었음을 확인할 수 있다. 게다가 seekToEndOfFile: 은 탐색하고 난 뒤 현재 파일의 오프셋을 반환한다. 여기서는 이 값을 무시하지만, 필요하다면 이 정보를 사용하여 파일 크기를 얻어올 수도 있다.


연습문제

1. 프로그램 16.6 에서 개발한 복사 프로그램을 수정하여, 마치 표준 UNIX cp 명령처럼 소스파일을 하나 이상 받아 디렉터리에 복사할 수 있도록 만들어라. 그러려면 다음 명령문은 copy1.m, file1.m, file2.m 을 progs 디렉터리에 복사해야 한다.

localhost $ copy copy1.m file1.m file2.m progs

소스파일을 하나 이상 지정할 때 마지막 인수는 기존 디렉터리라는 점에 주의하자.


2. 인수를 두 개 받는 myfind 라는 커맨드라인 도구를 작성하라. 첫째 인수는 검색을 시작할 디렉터리이고, 둘째 인수는 찾을 파일명이다. 따라서, 다음 명령문은 /Users 에서 proposal.doc 파일 찾기를 시작한다.

localhost $ myfind /Users proposal.doc
/Users/stevekochan/MyDocuments/proposals/proposal.doc
localhost $


(위와 같이) 파일을 찾으면 파일의 전체 경로를 출력시키고, 찾지 못했다면 적절한 메시지를 출력하게 만들자.


3. 표준 UNIX 도구인 basename 과 dirname 을 여러분만의 버전으로 작성해 보라.


4. NSProcessInfo 를 이용해서, 게터 메서드들에 의해 반환되는 모든 정보를 표시하는 프로그램을 작성하라.


5. 이 장에서 설명한 NSPathUtilities.h 파일의 NSTemporatyDirectoty 함수와, NSProcessInfo 의 globallyUniqueString 메서드를 사용하여 NSString 의 카테고리인 TempFiles 를 생성한다. 호출될 때마다 다른 파일 이름을 반환하는 temporaryFileName 메서드를 정의하라.


6. 프로그램 16.7 을, 한 번에 kBufSize 바이트씩 읽고 쓰게끔 수정하라. kBufSize는 프로그램의 초반에 정의된다. kBufSize 보다 큰 파일을 사용하여 프로그램을 테스트해 보자.


7. 파일을 열어 내용을 한 번에 128 바이트씩 읽어들여서, 읽은 내용을 터미널에 출력하라. NSFileHandle 의 fileHandleWithStandardOutput 메서드를 사용하여 터미널에 출력하는 핸들을 가져올수 었다.


Notes