I noticed a strange lack of high performance I/O routines in Objective-C. All I see is:

  • Bulk I/O. E.g, contentsAtPath of NSFileManager or writeToFile of NSString. These are memory intensive and impractical for complex data structure.
  • Very low level buffer based I/O from NSFileHandle. This is not good for reading NSString, int and float.

It appears that most people fall back on C routines like fwrite and fscanf for the job. I decided roll out a class that handled the task of reading and writing NSString, int, float etc. I wanted to borrow Java’s readUTF and writeUTF methods for NSString. In fact, my implementation should be compatible to Java’s.

The implementation below reads and writes data using binary files. The class can improve upon more error checking. The readUTF method can be optimized by recycling the malloced buffer.

FileUtil.h

#import <Foundation/Foundation.h>

@interface FileUtil : NSObject {
    FILE *mFile;
}

- (BOOL) openRead: (NSString*) file;
- (BOOL) openWrite: (NSString*) file;
- (BOOL) openAppend: (NSString*) file;
- (void) close;
- (NSString*) readUTF;
- (void) writeUTF: (NSString*) string;
- (int) readInt;
- (void) writeInt: (int) value;
- (float) readFloat;
- (void) writeFloat: (float) value;

@end

FileUtil.m

#import "FileUtil.h"

@implementation FileUtil

- (FileUtil*) init {
    [super init];

    mFile = NULL;

    return self;
}

- (BOOL) openRead: (NSString*) file {
    [self close];
    mFile = fopen([file fileSystemRepresentation], "rb");

    return mFile != NULL;
}

- (BOOL) openWrite: (NSString*) file {
    [self close];
    mFile = fopen([file fileSystemRepresentation], "wb");    

    return mFile != NULL;
}

- (BOOL) openAppend: (NSString*) file {
    [self close];
    mFile = fopen([file fileSystemRepresentation], "ab");

    return mFile != NULL;
}

- (void) close {
    if (mFile != NULL) {
        fclose(mFile);
        mFile = NULL;
    }
}

- (void)dealloc {
    [self close];
    [super dealloc];
}

- (NSString*) readUTF {
    int length = 0;
    fread(&length, 2, 1, mFile); //Java's writeUTF writes length in 2 bytes only

    char *buff = malloc(length + 1); //Extra byte for '\0'
    fread(buff, 1, length, mFile);
    buff[length] = '\0';

    NSString *string = [NSString stringWithUTF8String: buff];

    free(buff);

    return string;
}

- (void) writeUTF: (NSString*) string {
    const char *utf = [string UTF8String];
    int length = strlen(utf);

    fwrite(&length, 2, 1, mFile); //Java's writeUTF writes length in 2 bytes only
    fwrite(utf, 1, length, mFile); //Write UTF-8 bytes
}

- (int) readInt {
    int i;

    fread(&i, sizeof(int), 1, mFile);

    return i;
}

- (void) writeInt: (int) value {
    fwrite(&value, sizeof(int), 1, mFile);
}

- (float) readFloat {
    float f;

    fread(&f, sizeof(float), 1, mFile);

    return f;
}

- (void) writeFloat: (float) value {
    fwrite(&value, sizeof(float), 1, mFile);
}

@end