콘텐츠로 건너뛰기
Home » 파일에서 한줄씩 읽어 들이기 – Objective-C

파일에서 한줄씩 읽어 들이기 – Objective-C

파이썬에서는 텍스트 파일을 가리키는 파일 객체는 파일 내용의 각 라인에 대한 이터레이터처럼 작동한다. 따라서 별다른 노력을 들이지 않고 for ... in 구문을 통해서 텍스트 파일의 각 라인을 읽어서 처리하는 것이 가능하다. 이 기능의 멋진 점은 파일 객체가 느긋하게 동작하기 때문에 각 라인이 필요한 시점에 한 라인씩 읽어들인다는 것인데, 따라서 각 라인의 길이가 적절하다면 파일의 크기가 아무리 크더라도 안전하게 읽어들일 수 있다는 것이다.

Objective-C에서 텍스트 파일은 보통 NSStringstringWithContentOfFile: 메소드를 사용한다. 이 메소드의 문제는 파일을 한 번에 읽어들인 다는 점이다. 따라서 읽을 파일이 얼마나 클 것인지에 대한 제약이 확실하지 않다면 사용하기 위험한 방법일 수 있다.

이런 경우에 사용할 수 있는 방법으로 NSInputStream을 이용하는 것을 들 수 있다. (참고: Reading From Input Streams) 문제는 이 입력 스트림은 단순히 파일만을 위한 클래스가 아니어서 비동기로 작동한다. 정해진 분량만큼 버퍼로 읽어들인 후 이벤트를 생성해서 델리게이트가 처리하는 방식으로 돌아가기 때문에 파일에 사용하기에는 좀 번거로운 면이 있다.

조금씩 읽기

이래 저래 방법이 없나 알아보던 중에 그냥 C로 구현하면 되지하는 생각이 들었다.

NSString * readLineAsNSString(FILE *file) {
    int charsRead = 4096;
    char buffer[4096];
    NSMutableString *result = [NSMutableString stringWithCapacity:4096];
    while( charsRead == 4096) {
        if (fscanf(file, "%4095[^\r\n]%n%*[\r\n]", buffer, &charsRead) == 1) {
            [result appendFormat:@"%s", buffer];
        } else {
            break;
        }
    }
    return result;
}

위 코드는 흔한? fscanf() 함수를 사용한다. scan* 류의 함수는 입력 받는 형식에 대해 포맷을 지정할 수 있다는 것은 기본적으로 알고 있는 것인데, 여기에 사용되는 포맷에 몇 가지 기능이 더 있다.

  • 먼저 포맷 앞에 숫자를 표시하면 최대 해당 바이트 수까지를 읽어들인다는 뜻이다. 그리고 %[^...] 포맷은 [^...] 내부의 문자가 아닌 것을 읽겠다는 이야기다. 따라서 %4095[^\r\n] 은 개행문자까지 읽어들이되, 개행 문자 앞의 최대 4095 바이트를 읽는다는 이야기다.
  • %n 은 바로 앞의 패턴에서 읽어들인 바이트 수를 의미한다.
  • %*는 이 이후의 읽어들인 분량은 읽기만 하고 버퍼로 옮기지는 않는다. %*[...] 는 …까지를 읽되, 읽기만 하고 무시한다. 왜냐하면 앞에서 [^\r\n]에 의해서 개행 문자 앞까지만 읽었기 때문이다.

이 함수를 사용하면 열어 놓은 파일을 매번 넘겨서 한 줄씩 읽어들일 수 있게 된다. 물론 한 줄이 4095자보다 길다면, 그 4095자까지만 읽게 될 것이다. 다음과 같은 식으로 파일을 열어서 한 줄씩 출력할 수 있다.

사용법

FILE *f = fopen("mytext.txt", "r");
while (!feof(f)) {
    NSString *line = readLineAsNSString(f);
    NSLog(@"%@", line);
}
fclose(f);

feof() 함수는 파일의 끝에 도달했는지 여부를 체크하는 함수이다. (파일의 끝까지 읽었다면 1을 리턴) 이 함수를 사용하여 루프를 돌면서 파일을 한 줄 씩 처리할 수 있다.