NSFileManager & NSFileHandle? - objective-c

Do I have this right ...
To manipulate files on disk (create, copy, rename etc.) you use NSFileManager
To manipulate file contents (open, read, close etc.) you use NSFileHandle
I just want to make sure I am understanding this right.
EDIT_001
Thanks, thats what I figured Joshua, so I am assuming that by using the example below, open and close are both handled automatically by the implementation.
fileContents = [NSString stringWithContentsOfFile:fileOnDisk
encoding:NSMacOSRomanStringEncoding
error:&fileError];
gary

More or less, yes. From the docs:
NSFileHandle objects provide an object-oriented wrapper for accessing open files or communications channels.
... though NSFileHandle isn't necessary to read/write files. You can write an NSString to / read from a file with one line of code and no handle. Depends on what you want to do.

I believe, for the most part, that NSFileHandle is a wrapper around file descriptors -- good for 'relatively' low level reading or writing.
If you are just pushing the contents of a string or NSData to or from a file the associated methods on those classes are hard to beat.

Related

Saving data encoded with NSCoder

I'm encoding some NSDictionary with NSCoder and I want to save it somehow inside the app. Could anyone please tell me how I could do it?
I want to give the user the ability to see what he's done previously when he opens the app.
You can write it to a file in your Application Support folder or, if it's small, you can put it into NSUserDefaults. For either of those, though, if the dictionary contains only property list types, you can write the dictionary directly. You don't have to encode it to an NSData first.

Does each individual sound require an instance of AVAudioPlayer?

I have a few game sounds, such as four different beep tones, and each one is played depending on the action the user performs. Do I need to instantiate four different AVAudioPlayer objects?
Or do I just need one main player? If I just need one, then how do I switch the file URL?
This is what I am doing now:
NSError *error;
soundPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:markedSound error:&error];
but it seems to take a few seconds to initialize. Is there a way to preload the files and switch between them quickly?
Continue creating as many instance as you have sounds.
After initialization, use the method prepareToPlay:
Prepares the audio player for playback by preloading its buffers.
Discussion Calling this method preloads buffers and acquires the audio
hardware needed for playback, which minimizes the lag between calling
the play method and the start of sound output.
Calling the stop method, or allowing a sound to finish playing, undoes
this setup.
For example:
NSError *error;
soundPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:markedSound error:&error];
[soundPlayer prepareToPlay];
For the moments, you only need 4 songs, so this would be premature optimization. But if later, the number of sounds you need increase a lot, you can wrap all this in a sub-class and do lazy loading. As the documentation states, using the stop method is the equivalent of unloading your file.
You don't necessarily need to instantiate four different AVAudioPlayer objects, although this is one option (that may be simplest if you only have four sounds).
One you could make would be to load the sounds into memory in advance, which you can do by using the initWithData method instead of the initWithContentsOfURL (having loaded all your sound files in advance to NSData objects). This may speed things up slightly, although it also depends on your sound format. Uncompressed files will obviously be loaded much quicker than an MP3, for example.
If this still doesn't work out for you, your best bet would be to look at some of the lower level audio technologies provided, which are more suited for games work (OpenAL, Audio Toolbox, etc), but these are a fair bit more complex than a simple AVVAudioPlayer.

How to find file types in objective c

I have done a lot of research on this and haven't found anything useful. I want am making a simple program in objective c for personal use that opens files and gives information about them. The only problem i have encountered is that i cannot find the file of a file i am opening. Without simply looking at the extension, is there a way to find the full file format of a file in objective c?
If possible, i would also like to be able to save that file in a different format. Information on this subject is also important for this application. Help will be greatly appreciated.
Mac OS X has type information attached to each file which specifies what the type of the file is supposed to be. This information is given by the application which last saved the file, so it is not necessarily correct. Also, new versions of OS X ignore this information and go entirely off of the file extension. However, the information is still stored and can be retrieved using NSFileManager's attributesOfItemAtPath:error: method.
As mentioned by quixoto above, OS X now maps extensions to UTIs. The UTI of a file can be retrieved using NSWorkspace, which can also tell you what the UTI means. This code will get the localized description of the file at /full/path/to/file:
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
NSString *description = [ws localizedDescriptionForType:[ws typeOfFile:#"/full/path/to/file" error:nil]];
The typeOfFile:error: method requires an absolute path.
Consider: what do you mean by "full file format"? What kinds of files and level of detail do you care about?
You can't really get "format" information from the file system like you want. Believe it or not, the file system has no idea what format files are in.
File systems traditionally store files as a stream of bytes with a name, a size, and some other attributes ("hidden", permissions, etc). That's all that it's responsible for, and it's the application's problem to read those bytes and interpret them to mean something useful.
The extension is the traditional hint to an application about what a file contains, but as you might guess, it's certainly not a verified guarantee.
Modern Mac OS X has Quick Look, which uses a system-wide framework that adds some smarts on top of this, with mappings between extensions and UTIs, which are a richer notion of file type. Docs are here. I don't think there's a way to query this system for the mappings between extensions and UTIs, but I'm not sure.
Loading a file of one format and saving in another is 100% dependent on the file types you're talking about, and you're going to have ask very specific questions about specific formats if you really care to accomplish this. (And that topic extends well beyond a couple built-in method calls in Cocoa.)
This information is now (since OS X 10.5) handled through UTIs.
You can get it like this:
NSURL *desktopURL = [[[NSFileManager defaultManager] URLsForDirectory: NSDesktopDirectory
inDomains: NSUserDomainMask] lastObject];
NSURL *workingdirURL = [desktopURL URLByAppendingPathComponent:#"WorkingDir" isDirectory:YES];
NSArray *docURLs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:workingdirURL
includingPropertiesForKeys:#[NSURLTypeIdentifierKey] options:NSDirectoryEnumerationSkipsHiddenFiles
error: nil];
NSMutableArray *pdfURLs = [NSMutableArray array];
for (NSURL *docURL in docURLs) {
id resourceValue;
BOOL found = [docURL getResourceValue: &resourceValue
forKey: NSURLTypeIdentifierKey
error: nil];
if ( found && [resourceValue isEqual:#"com.adobe.pdf"]) {
[pdfURLs addObject: docURL];
}
}
You can see the docs for more information about the file properties that can be retrieved this way. There is quite a lot of information available through this method.

Writing multiple NSData to File

I need a hint on how to write multiple NSData chunks to single file. Downloading a file using NSURLConnection in chunks. Each chunk is downloaded in a separate NSOperation thread. As the chunks finish downloading they need to be written to a file so combined result is the file downloaded.
What would be the best way to manage the NSData that is returned and writing it to a single file?
No need to eat up memory by creating a large NSMutableData object (not possible with a large download anyway as you would run out of memory), nor do you need to waste time creating tons of small files and concating them (could take a very long time with a large file, iDevice disk IO is not very fast).
Just create an NSFileHandle and use it to write each NSData object to the end of the file as they come in.
NSFileHandle *handle = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
[handle seekToEndOfFile];
[handle writeData:dataPiece];
[handle closeFile];
You'll need to first create the file though so that you can open it with the NSFileHandle. To do that you can just write the first NSData piece using the following, then write the rest using the file handle.
[dataPiece writeToFile:filePath atomically:YES];
edit: Actually I just re-read the question and realized you're using separate threads to download the chunks so they may not finish in order, so my solution won't work. I must have been tired the day I answered and skipped right over it. But, hopefully my answer can at least help others that are downloading chunks in order or single threaded.
Write each chunk to a separate file. Then when the last chunk is downloaded, concatenate each file in the correct order into a single large file.
Assuming that you know what the final data size will be, and you want to stick to Foundation classes/Objective-C, you could create an instance of NSMutableData that can be shared across these operations. When an operation completes its chunk, it should lock some shared mutex, write its completed download to the appropriate place in the NSMutableData object and then unlock the shared mutex.
Once all the operations are joined, you can simply write the mutable data to a file using the writeToFile: convenience methods on the NSData class. If you're more proficient in C/BSD, you could also create the file as an mmap and simply write to it. Since all the data are going to discrete seconds in the mapping, you can write without locking a mutex. Once all the operations are joined, you can remove the mmap and close the file.

Is there a reason why I shouldn't just define #define NSFM [NSFileManager defaultManager] instead of writing it out each time?

There is a built-in shortcut NSApp, is there any reason that I shouldn't add one for NSFileManager?
#define NSFM [NSFileManager defaultManager]
I think omitted this will make my code cleaner and I can't see any benefit to keeping it in. (I plan on doing this in all my projects from now on, so it won't be obscure.)
NSFileManager *fm = [NSFileManager defaultManager]
Why don't you just use a local variable?
NSFileManager *fm = [NSFileManager defaultManager];
// use fm...
or better yet, inject the file manager as a method argument:
- (void)myMethod {
//using [NSFileManager defaultManager]
}
becomes
- (void)myMethodWithFileManager:(NSFileManager*)fm {
//usin fm
}
Since the defaultManager is a singleton (effectively a global), it makes testing really hard. Injecting the dependency saves you typing (as you want) within the method and makes it much easier to unit test—you can inject a test double instead of the defaultManager.
Finally, Cocoa and Objective-C generally come down on favoring explicit code over short code. The philosophy is basically that using more verbose names makes the code easier to read (and thus to maintain). This philosophy goes all the way to Objective-C's selector style with interleaved named arguments. Unless you really can't handle the extra typing (and Xcode's code completion won't work for you), stick with the existing names. When in Rome and all that...
If it makes your code cleaner, I'm all for it. Just keep in mind that any other developers who have to read your code will not immediately know what NSFM or fm represent.
I would suggest a slightly more descriptive name: NSFileMgr. To most Cocoa developers, this would make the purpose of the variable a lot clearer without having to look it up.
UPDATE: See Barry Wark's answer for some very good points in regards to testing.
I would use a local variable every time because using the defaultManager is not thread safe. If at any point you start using threads in your application you may end up with hard to find bugs and not know why until you run upon this bit of documentation.