Write to file not working

12,942

Solution 1

I wrote a quick example:
Missing: memory management / error handling / proper file handling

// Archive

NSMutableArray *array = [[NSMutableArray alloc] init];

NSString * input = @"/Users/Anne/Desktop/1.png";

[array addObject:[NSData dataWithContentsOfFile:input]];
[array addObject:[NSData dataWithContentsOfFile:input]];
[array addObject:[NSData dataWithContentsOfFile:input]];

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
NSString *path = @"/Users/Anne/Desktop/archive.dat";
[data writeToFile:path options:NSDataWritingAtomic error:nil];


// Unarchive

NSMutableArray *archive = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

NSData * firstObject = [archive objectAtIndex:0];
NSString * output = @"/Users/Anne/Desktop/2.png";
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:output];
[firstObject writeToURL:fileURL atomically:YES];

You can also add NSImages to the NSMutableArray:

NSString * input = @"/Users/Anne/Desktop/1.png";
NSImage *image = [[NSImage alloc] initWithContentsOfFile: input];
[array addObject:image];

But that will significantly increase the file size.

Solution 2

Response to the following comment:
So if I only need to access an image at runtime (in the archive), is there a way to access that image at an index without unarchiving the whole thing? Seems like unnecessary overhead to me.

I assume you're still struggling with this problem?
Hiding (or encrypting) app resources?

Like i mentioned earlier, combining all files into one big file does the trick.
Just make sure you remember the file-length of each file and file-order.
Then you can extract any specific file you like without reading the whole file.
This might be a more sufficient way if you only need to extract one file at the time.

Quick 'dirty' sample:

// Two sample files
NSData *fileOne = [NSData dataWithContentsOfFile:@"/Users/Anne/Desktop/1.png"];
NSData *fileTwo = [NSData dataWithContentsOfFile:@"/Users/Anne/Desktop/2.png"];

// Get file length
int  fileOneLength = [fileOne length];
int  fileTwoLength = [fileTwo length];

// Combine files into one container
NSMutableData * container = [[NSMutableData alloc] init];
[container appendData:fileOne];
[container appendData:fileTwo];

// Write container to disk
[container writeToFile:@"/Users/Anne/Desktop/container.data" atomically:YES];

// Read data and extract sample files again
NSData *containerFile = [NSData dataWithContentsOfFile:@"/Users/Anne/Desktop/container.data"];
NSData *containerFileOne =[containerFile subdataWithRange:NSMakeRange(0, fileOneLength)];
NSData *containerFileTwo =[containerFile subdataWithRange:NSMakeRange(fileOneLength, fileTwoLength)];

// Write extracted files to disk (will be exactly the same)
[containerFileOne writeToFile:@"/Users/Anne/Desktop/1_extracted.png" atomically:YES];
[containerFileTwo writeToFile:@"/Users/Anne/Desktop/2_extracted.png" atomically:YES];

// Only extract one file from the container
NSString * containerPath = @"/Users/Anne/Desktop/container.data";
NSData * oneFileOnly = [[NSFileHandle fileHandleForReadingAtPath:containerPath] readDataOfLength:fileOneLength]; 

// Write result to disk
[oneFileOnly writeToFile:@"/Users/Anne/Desktop/1_one_file.png" atomically:YES];

Tip: You can also save the 'index' inside the container file.
For example: The first 500 bytes contain the required information.
When you need a specific file: Read the index, get the file position and extract it.

Solution 3

You are archiving a NSMutable array of NSImage. This two classes conform to the NSCoding protocol required by NSKeyedArchiver, so I don't see where would be your problem.
So, here are many ideas to test.

First, are you sure that the data you think you have are valid? In your first code snippet, you write [NSData dataWithContentsOfFile:@"0.png"]. This method expects an absolute file path.

Assuming the problem is not in your code, just in your question, let's continue:

Do you have something different than nil in the variable data after your archiving? Ie, after the assignement to data, can you add this code. If the assertion fail, you will get an exception at runtime:

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
NSAssert(nil != data, @"My object data is nil after archiving");

If the problem was not here, what is the return of the line [data writeToFile:path options:NSDataWritingAtomic error:&error];
(Not the variable error, but the return value of the call to the method - writeToFile: options: error:)

What happens if you simplify your code and just do this:

result = [NSKeyedArchiver archiveRootObject:data
                                     toFile:archivePath];

If everything was ok, have you tried to unarchive your file with NSKeyedUnarchiver?

Solution 4

The problem is that [NSData dataWithContentsOfFile:@"0.png"] looks for the file "0.png" in the current directory, but what the application thinks of as the current directory is probably not the place you're expecting. For graphical apps, you should always either use an absolute path or a path relative to some place that you can get the absolute path of (e.g. your app bundle, the application support directory, some user-selected location).

For command-line tools, using the current directory is more common. But I doubt that's the case here.

Share:
12,942
sudo rm -rf
Author by

sudo rm -rf

Hi, I'm Jonathan. I'm an iOS & Mac developer with a specialty in Core Animation. I created this Stack Overflow account back when I knew absolutely nothing about programming. Thus this profile serves as a documentation of my journey to where I am today. Glad I was able to help people out along the way. Keep up with me elsewhere: GitHub: https://github.com/jwilling Twitter. https://twitter.com/willing Blog: http://jwilling.com

Updated on June 04, 2022

Comments

  • sudo rm -rf
    sudo rm -rf almost 2 years

    I'm trying to combine images in my app into one file and write it to disk.

    NSMutableArray *array = [NSMutableArray arrayWithObjects: 
                             [NSData dataWithContentsOfFile:@"0.png"], 
                             [NSData dataWithContentsOfFile:@"1.png"],
                             [NSData dataWithContentsOfFile:@"2.png"],
                             nil];
    
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
    NSError *error = nil;
    NSString *path=@"/Users/myusername/Desktop/_stuff.dat";
    [data writeToFile:path options:NSDataWritingAtomic error:&error];
    

    or

    NSArray *array = [NSArray arrayWithObjects:
                      [NSImage imageNamed:@"0"], 
                      [NSImage imageNamed:@"1"], 
                      [NSImage imageNamed:@"2"], 
                      nil];
    
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
    NSError *error = nil;
    NSString *path=@"/Users/myusername/Desktop/_stuff.dat";
    [data writeToFile:path options:NSDataWritingAtomic error:&error];
    

    But both produce a file that is 4KB (empty). If I NSLog the error it is (null). Am I making the data the wrong way?

    Edit: If I open the resulting file with a text editor, it looks like this: enter image description here