BLOG ARTICLE 파일 | 2 ARTICLE FOUND

  1. 2009.06.04 iMerge - 간단한 맥용 파일머지 툴 (4)
  2. 2008.03.03 NSFileHandle을 이용한 간단한 파일 입출력 (1)

몇일 전에 만들었다가 아이콘을 넣고 조금 다듬어서 올려 봅니다. 맥에서 여러개의 파일을 하나로 합치는 간단한 툴입니다. 아이폰용 사전 어플을 업그레이 하다가 음성파일이 너무 많아 파일을 하나로 합치고 파일명과 시작과 끝 위치정보를 저장하기 위해서 만들었습니다.

사용 방법은 간단합니다. 좌측 하단의 [+] 버튼을 클릭하여 통합할 파일을 등록 한 후에 [Save] 버튼을 클릭하여 저장합니다.
합쳐진 개별 파일에 대한 정보가 저장되는 파일의 종류는 text, xml, sqlite3, 세가지 형식에서 선택할 수 있습니다. 각각 *.txt, *.xml, *.db의 확장자로 저장되며 형식은 아래와 같습니다.

* txt
6-1.png|0|968
9-1.png|968|1965
8-1.png|1965|2939
5-1.png|2939|3840
3-1.png|3840|4821
0-1.png|4821|5749
각 파일들은 '\n'으로 구분되면 파일 정보들은 '|'를 구분자로 저장이 됩니다. 읽어올 시에는 마지막의 '\n'를 삭제하셔야 합니다.

* xml
<?xml version="1.0" encoding="utf-8"?>
<Files>
    <FileEntry>
        <name>6-1.png</name>
        <start>0</start>
        <end>968</end>
    </FileEntry>
    <FileEntry>
        <name>9-1.png</name>
        <start>968</start>
        <end>1965</end>
    </FileEntry>
    <FileEntry>
        <name>8-1.png</name>
        <start>1965</start>
        <end>2939</end>
    </FileEntry>
    <FileEntry>
        <name>5-1.png</name>
        <start>2939</start>
        <end>3840</end>
    </FileEntry>
    <FileEntry>
        <name>3-1.png</name>
        <start>3840</start>
        <end>4821</end>
    </FileEntry>
    <FileEntry>
        <name>0-1.png</name>
        <start>4821</start>
        <end>5749</end>
    </FileEntry>
</Files>

* sqlite3
files란 테이블에 name, start, end 필드 순으로 저장됩니다.


아이폰 어플에서는 UIImage나 AVAudioPlayer등 멀티미디어 파일에 관련된 오브젝트들은 대부분 NSData 타입을 지원하기 때문에 원하는 데이터를 쉽게 가져올 수 있습니다. 아래는 sqlite 타입으로 통합된 파일인 music.dat에서 abc.mp3 파일을 찾아내어 플레이하는 간단한 예입니다.

if (sqlite3_prepare_v2(sDB, "SELECT start, end FROM files WHERE name='abc.mp3'", -1, &statement, NULL) == SQLITE_OK) {
    if (sqlite3_step(statement) == SQLITE_ROW) {
        long start = sqlite3_column_int(statement, 0);
        long end = sqlite3_column_int(statement, 1);

        // 데이터 파일을 연다      
        NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:
                              [[NSBundle mainBundle] pathForResource:@"music" ofType:@"dat"]];
      
        // 시작위치만큼 파일 포인터 이동
        [file seekToFileOffset:start];
       
        // 원본파일 크기만큼 읽어온다
        NSFileData *data = [file readDataOfLength:end - start];
        [file closeFile];
       
        // mp3 플레이
        AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithData:fileData error:&err];
        [player play];
    }
}

1,000개의 오디오 파일을 통합하여 사용해 보았는데 문제는 없었습니다. 몇번 사용하지를 않아서 버그나 오류가 있을 수 있을 것 같습니다. 알려 주시면 수정하겠습니다.



유닉스 계열에서는 실제 파일뿐만 아니라 소켓, 파이프, 장치등 모든 것을 파일로 간주하고 관리합니다. (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];