how to create a file in Objective-C / Xcode?

I am really struggling to find how one creates a file that doesn't already exist in Objective-C, and then use that file write data. In C, one uses a fopen command, usually wrapped in an if statement to catch a failure...


if ((file_pointer = fopen(file_name, "wb")) == NULL) {

fprintf(stderr, "could not create file.\n");

exit(EXIT_FAILURE);

}


You have created a file, and you have a file_pointer to use as you write data to your file ... fprintf(file_pointer, %d, someInt); So easy.


With Objective-C and Xcode... I'm already getting the feeling like there's many ways to skin a cat... but I can't get any of them to work for me.

I see some people use an NSData method...



BOOL written = [dataBuffer writeToFile:destinationPath options:0 error:NULL]; //assumes a NSData file *dataBuffer and NSString *destinationPath.


But this doesn't work, no file is actually created.


I also tried:

NSFileHandle *file;

NSData *data = [...

file = [NSFileHandle fileHandleForUpdatingAtPath: @"/tmp/filename.xyz"];

[file writeData: data];


But this also failed to create a file. There also seems to be a whole other trajectory using NSFileManager and a "createFileAtPath" method, which I ALSO could not get to work.


HELP! What am I missing?

Accepted Reply

You got to nearly the right method, and you called it nearly right. 🙂


In general, the simplest and best method to use is -[NSData writeToURL:options:error:], which is the URL-based variant of the NSData method you actually tried. URL-based file methods are preferred to path-based methods. (The reasons are a different discussion. If you really, really want to use a path instead of a URL, you can.)


The only mistake you made was to pass NULL for the error result. With anything to do with files, failures are not exactly unusual, so you really need to have the error to see what went wrong. My guess is that your path string was malformed, or pointed to a nonexistent directory.


Note that for the directory, you would generally use NSFileManager to find standard directories, or to create your own directories. The APIs here are pretty simple to use, but there is a small learning curve in finding the best APIs to use, and avoiding the ones which will lead you astray. (Don't use "createFileAtPath", for example. The general rule of thumb is: don't use any NSFileManager API that specifies a path, unless there is also a similar one that uses a URL; and don't use any NSFileManager API that doesn't return an error via a parameter as well as a succeeded/failed return value. Path-based API that doesn't follow these rules can be assumed to be obsolescent.)


The other consideration here is that if your app is sandboxed, you cannot write to locations outside your application sandbox container without the "permission" of the user. This permission is obtained by using NSOpenPanel or NSSavePanel, and using the security-scoped URLs that these classes provide.

Replies

You got to nearly the right method, and you called it nearly right. 🙂


In general, the simplest and best method to use is -[NSData writeToURL:options:error:], which is the URL-based variant of the NSData method you actually tried. URL-based file methods are preferred to path-based methods. (The reasons are a different discussion. If you really, really want to use a path instead of a URL, you can.)


The only mistake you made was to pass NULL for the error result. With anything to do with files, failures are not exactly unusual, so you really need to have the error to see what went wrong. My guess is that your path string was malformed, or pointed to a nonexistent directory.


Note that for the directory, you would generally use NSFileManager to find standard directories, or to create your own directories. The APIs here are pretty simple to use, but there is a small learning curve in finding the best APIs to use, and avoiding the ones which will lead you astray. (Don't use "createFileAtPath", for example. The general rule of thumb is: don't use any NSFileManager API that specifies a path, unless there is also a similar one that uses a URL; and don't use any NSFileManager API that doesn't return an error via a parameter as well as a succeeded/failed return value. Path-based API that doesn't follow these rules can be assumed to be obsolescent.)


The other consideration here is that if your app is sandboxed, you cannot write to locations outside your application sandbox container without the "permission" of the user. This permission is obtained by using NSOpenPanel or NSSavePanel, and using the security-scoped URLs that these classes provide.

What QuinceyMorris said and…

One of the joys of Objective-C is its seamless integration with C. If you prefer using the C standard I/O API, there’s no reason not to. And in some cases there are good reasons to stick with the C API:

  • It supports buffered I/O, which is important when you want to write your code using small primitives and maintain efficiency.

  • It is particularly well suited to parsing and formatting text files, that is,

    fscanf
    and
    fprintf
    .

ps When doing this you must correctly convert between path C strings and

NSURL
file URLs. This involves the ‘file system representation’ APIs. For example:
// Convert from file URL to path C string...

NSURL * u = … some file URL …
FILE * f = fopen(u.fileSystemRepresentation, "r");

// Convert from path C string to file URL...

const char * p = … some C Path …
NSURL * u = [NSURL fileURLWithFileSystemRepresentation:p isDirectory:false relativeToURL:nil];

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I use these:

-(void)writeAnArray:(NSArray *)array atPath:(NSString *)path{
    NSFileManager* sharedFM = [NSFileManager defaultManager];
    NSURL *appDir=[[sharedFM URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] objectAtIndex:0];
    NSURL *fileURL=[appDir URLByAppendingPathComponent:path];
    [array writeToURL:fileURL error:nil];
}

-(NSArray *)readAnArray:(NSString *)path{
    NSFileManager* sharedFM = [NSFileManager defaultManager];
    NSURL *appDir=[[sharedFM URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] objectAtIndex:0];
    NSURL *fileURL=[appDir URLByAppendingPathComponent:path];
    NSError *error;
    NSArray *fileArray=[NSArray arrayWithContentsOfURL:fileURL error:&error];
    if(error)fileArray=[[NSArray alloc] init];
    return fileArray;
}
  • Hello, I was glad to find this solution, but there is a stolperstein: I use macOS Sierra, writeToURL:: was not available. I used  fileURL.absoluteString and writeToFile:: The method ran, but without success, the path looks o.k. file:///Users/uwejagoda/Documents/Geek/data.vr, data is an archived empty Dictionary.

    I hope you have a brain wave (Waves are my lifeblood)

    Uwe

Add a Comment

Awesome, thank you all! Kind of a lot to investigate, but I think I need to start with NSURL, learn how that works. Thanks again, so nice to have this support.

I used fileURL.absoluteString and writeToFile:

If you have a file URL and you need to get a path, get the path property. The absoluteString property returns a string representation of the file URL itself, and APIs expecting a path will not accept that.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"