Create folder/directory in Objective-C/cocoa - objective-c

I have this code for creating a folder/directory in Objective-C/cocoa.
if(![fileManager fileExistsAtPath:directory isDirectory:&isDir])
if(![fileManager createDirectoryAtPath:directory attributes:nil])
NSLog(#"Error: Create folder failed %#", directory);
It works fine, but I got creatDirectoryAtPath:attributes is deprecated warning message.
What's the newest way of making a directory builder in Cocoa/Objective-c?
SOLVED
BOOL isDir;
NSFileManager *fileManager= [NSFileManager defaultManager];
if(![fileManager fileExistsAtPath:directory isDirectory:&isDir])
if(![fileManager createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:NULL])
NSLog(#"Error: Create folder failed %#", directory);

Found in the documentation:
-[NSFileManager createDirectoryAtPath:withIntermediateDirectories:attributes:error:]

Your solution is correct, though Apple includes an important note within NSFileManager.h:
/* The following methods are of limited utility. Attempting to predicate behavior
based on the current state of the filesystem or a particular file on the
filesystem is encouraging odd behavior in the face of filesystem race conditions.
It's far better to attempt an operation (like loading a file or creating a
directory) and handle the error gracefully than it is to try to figure out ahead
of time whether the operation will succeed. */
- (BOOL)fileExistsAtPath:(NSString *)path;
- (BOOL)fileExistsAtPath:(NSString *)path isDirectory:(BOOL *)isDirectory;
- (BOOL)isReadableFileAtPath:(NSString *)path;
- (BOOL)isWritableFileAtPath:(NSString *)path;
- (BOOL)isExecutableFileAtPath:(NSString *)path;
- (BOOL)isDeletableFileAtPath:(NSString *)path;
Essentially, if multiple threads/processes are modifying the file system simultaneously the state could change in between calling fileExistsAtPath:isDirectory: and calling createDirectoryAtPath:withIntermediateDirectories:, so it is superfluous and possibly dangerous to call fileExistsAtPath:isDirectory: in this context.
For your needs and within the limited scope of your question it likely would not be a problem, but the following solution is both simpler and offers less of a chance of future issues arising:
NSFileManager *fileManager= [NSFileManager defaultManager];
NSError *error = nil;
if(![fileManager createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:&error]) {
// An error has occurred, do something to handle it
NSLog(#"Failed to create directory \"%#\". Error: %#", directory, error);
}
Also note from Apple's documentation:
Return Value
YES if the directory was created, YES if createIntermediates is set
and the directory already exists), or NO if an error occurred.
So, setting createIntermediates to YES, which you already do, is a de facto check of whether the directory already exists.

Thought I'd add to this and mention some more from the documentation about using the +defaultManager method:
In iOS and Mac OS X v 10.5 and later you should consider using [[NSFileManager alloc] init] rather than the singleton method defaultManager. Instances of NSFileManager are considered thread-safe when created using [[NSFileManager alloc] init].

You may prefer to work with the NSFileManager method:
createDirectoryAtURL:withIntermediateDirectories:attributes:error:
It works with URL's instead of path strings.

Related

Why is NSURL's NSURLDocumentIdentifierKey (almost) always nil?

OSX Yosemite introduced a very handy attribute on NSURL: NSURLDocumentIdentifierKey.
Quoting from the documentation:
NSURLDocumentIdentifierKey
The document identifier returned as an NSNumber (read-only).
The document identifier is a value assigned by the kernel to a file or directory. This value is used to identify the document regardless of where it is moved on a volume. The identifier persists across system restarts. It is not transferred when the file is copied, but it survives "safe save” operations. For example, it remains on the path to which it was assigned, even after calling the replaceItemAtURL:withItemAtURL:backupItemName:options:resultingItemURL:error: method. Document identifiers are only unique within a single volume. This property is not supported by all volumes.
Available in OS X v10.10 and iOS 8.0.
Unfortunately, the value seems to be mostly nil (except rare examples that seem completely disconnected one to the other).
In particular, this code will throw an exception at the last line (tested on Yosemite 10.10.3):
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *attributesFlags = #[NSURLNameKey, mNSURLDocumentIdentifierKey];
NSDirectoryEnumerator *en = [fileManager enumeratorAtURL:[NSURL URLWithString:NSHomeDirectory()]
includingPropertiesForKeys:attributesFlags
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:^BOOL(NSURL *url, NSError *error) {
NSAssert(NO, #"An error has occured");
return YES;
}];
for(NSURL *URL in en) {
NSNumber *documentID = nil;
NSError *error = nil;
BOOL result = [URL getResourceValue:&documentID forKey:NSURLDocumentIdentifierKey error:&error]; \
NSAssert(result == YES && error==nil, #"Unable to read property. Error: %#", error); \
NSLog(#"Processing file: %#", URL);
// This will break most of the times
NSAssert(documentID != nil, #"Document ID should not be nil!!");
}
Perhaps I misunderstood the documentation but it seems to me NSURLDocumentIdentifierKey should be available on every file on disk.
I filed a bug with Apple on this issue and got a feedback on my report. As of today, the information on tracking the DocumentIdentifier is not part of the documentation yet, but the ticket is still open.
The missing information is, that the filesystem does not track the DocumentIdentifier by default. You'll have to enable the tracking by setting a flag on each file that you want to track using chflags with the UF_TRACKED flag.
The following script will print the DocumentIdentifier for a file:
https://gist.github.com/cmittendorf/fac92272a941a9cc64d5
And this script will enable tracking the DocumentIdentifier:
https://gist.github.com/cmittendorf/b680d1a03aefa08583d7
Apparently Yosemite assigns a DocumentIdentifier to a file only when it knows something is trying to track its identity (like Versions or iCloud).
I don't see any way to talk to the kernel and tell it to start tracking files you're interested on. I hope this changes in future releases, since the API has been made public on OS X 10.10 and it's mostly useless at this point.
This issue still exists in macOS 10.14. It probably won't change.
The work-around is to get the inode from NSFileManager, like this:
NSFileManager *fmgr = [NSFileManager defaultManager];
NSDictionary *attributes = [fmgr attributesOfItemAtPath:url.path error:nil;
if (attributes != nil) {
NSNumber *inode = [attributes objectForKey:NSFileSystemFileNumber];
...
}

Xcode 5 [NSString writeToFile] without absolute path

I've checked out a few other posts about this topic, but I am still left with some doubt on whether or not [NSString writeToFile] is writing to the relative path.
NSError *error = nil;
BOOL success = [str writeToFile:#"someFile.txt"
atomically:YES
encoding:NSUTF8StringEncoding
error:&error];
NSString *status = success ? #"Success" : #"Failure";
if(success){
NSLog(#"Done Writing: %#",status);
}
else{
NSLog(#"Done Writing: %#",status);
NSLog(#"Error: %#",[error localizedDescription]);
}
writeToFile works when given the path to a certain folder and by NSLogging the error, I can see what kind of error occurs. However, when running the above code, no error occurs and after having done a thorough search, I think I can safely say that a file was never created. What's going on behind the scenes?
Well it's certainly working, which you confirm yourself as your code traps and reports errors very nicely. Your only issue is that you don't know where the file is being written to, and in this case, as no path has been specified it will be to the current working directory, which is a concept in pretty much all operating systems (even Windows!).
I must admit that I don't know what the default current working directory is under iOS, but you can find out yourself with:
NSString *cwd = [[NSFileManager defaultManager] currentDirectoryPath];
NSLog(#"cwd='%#'", cwd);

using instance method instead of init

I just wroted this line:
BOOL directoryResult = [[NSFileManager alloc]
createDirectoryAtURL:[[NSURL alloc]
initFileURLWithPath:[self.documentsPath
stringByAppendingFormat:#"/level%d", levelCount] isDirectory:YES]
withIntermediateDirectories:NO attributes:nil error:nil];
NSLog(#"BOOL: %d", directoryResult);
and I have two questions: how it is possible that this method is working properly? After [NSFileManager alloc] I'm not using init.
Why compiler does not complaining? Is init inside createDirectoryAtURL? Is it good way of programming?
And secondly in URL parameter of createDirectoryAtURL I'm creating NSURL just in place
[[NSURL alloc] initFileURLWithPath:[self.documentsPath stringByAppendingFormat:#"/level%d", levelCount] isDirectory:YES]
same question as above: Is it good way of programming or should I create such object before that line and just put object here?
[NSFileManager defaultManager] returns singleton instance of the file manager, use it to perform the tasks. It's quite common practice in Cococa. I'm not sure why does your code work properly, I can only guess that this particular method doesn't use any internal variables, so it's valid to call it even without init (although you should never do that).
As for the NSURL construction, the answer depends on compiling options. Do you use ARC? If the answer is 'yes', your code is valid, else it lead to the memory leak. In generat it's better either to create an object and call autorelease (non-ARC apps) explicitly, or use class methods like [NSURL fileURLWithPath:path].
Also, don't treat it as offense, but I believe you're asking this questions in the wrong place. Basic memory management questions should be asked to a good book, one like "Cocoa programming for Mac OS X" by Aaron Hillegass.
It's not guaranteed that object created without initialization will work properly. So you should init the object. Documentation example:
BOOL isDir=NO;
NSArray *subpaths;
NSString *fontPath = #"/System/Library/Fonts";
NSFileManager *fileManager = [[NSFileManager alloc] init];
if ([fileManager fileExistsAtPath:fontPath isDirectory:&isDir] && isDir)
subpaths = [fileManager subpathsAtPath:fontPath];
[fileManager release];
Also NSFileManager has a shared manager (already created and intialized object)
NSFileManager* fileManager = [NSFileManager defaultManager];
But there is a warning in documentation:
This method always returns the same file manager object. If you plan to use a delegate with the file manager to receive notifications about the completion of file-based operations, you should create a new instance of NSFileManager (using the init method) rather than using the shared object.

Unknown error creating file in objective c

I'm creating a mac app that needs to create a file with the contents of another file, i'm creating it as follows:
NSString *p = #"/AfilethatEXISTS.plist";
NSString *user1 = #"~/Library/MyApp/myFile";
NSString *pT1 = [user1 stringByExpandingTildeInPath];
[[NSFileManager alloc] createFileAtPath:[NSURL URLWithString:pT1] contents:[NSData dataWithContentsOfFile:p] attributes:nil];
However returning no error, its not creating the file?
There are several things wrong with this code, but not enough context to tell you what is going wrong.
First, there should never be a file in / directly. That directory should be sacrosanct and many users will not be able to write to that directory without admin access.
Secondly, paths should be managed via the path manipulation APIs on NSString and NSURL.
Next, pT1 isn't really an URL and that is URLWithString: may be returning nil. Use fileURLWithPath: instead.
Finally, there isn't any error checking in that code and, thus, there is no way to tell how you might have discovered no error. What have you checked?
First off, you're creating the file manager instance incorrectly. To create a new instance, you need to both allocate and initialize it.
You're trying to pass an NSURL object, which won't be created correctly since the string you're using to create it with isn't a URL. But that doesn't matter anyway, because even if the NSURL was created, -createFileAtPath:contents:attributes: expects an NSString - just pass pT1 directly.
Better still, since you're basically just copying p to pT1, use the NSFileManager method for doing that. Not only is it conceptually a better fit, it also gives you a chance to examine a returned NSError object to see what (if anything) went wrong.
NSError *error;
NSFileManager *fm = [[[NSFileManager alloc] init] autorelease];
if (![fm copyFileAtPath:p toPath:pT1 error:&error]) {
// If copyFileAtPath:toPath:error: returned FALSE, an error occurred, and
// error will point to an NSError instance with more information
}

Objective-C: need to call a method in another class from FinishedLaunching

I got this far... here is my code:
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
// create the d/b or get the connection value
SQLiteDB *dbInstance = [[SQLiteDB alloc] init];
}
Now, the question is: this bit of code is supposed to check to see if a database exists, and if not, create it. My problem is I am having a problem figuring out exactly how to write the first line of the called method and where to place it in SQLiteDB.m. Is this an instance method (-) or a class method (+)?
I'm sorry for being so lame on this, but once I see it, I'll have the hang of it... the rest of the code is written in C#, and I can handle the conversion to Obj_C.
The following is a method to copy an existing database from your Bundle to the Documents directory, but can easily be adapted for a new database. Just use the fileExistsAtPath: method logic below and replace the actions to take with your custom database creation code.
Put this in your AppDelegate.m file:
- (void)prepareDatabase
{
//add Database Versioning check to see if the resources database is newer
// generally as simple as naming your database with a version on the end
NSFileManager *filemanager = [NSFileManager defaultManager];
NSString *databasePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:#"/YOURDATABASE.s3db"];
if(![filemanager fileExistsAtPath:databasePath]) {
//Database doesn't exist yet, so we copy it from our resources
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:#"/YOURDATABASE.s3db"];
if([filemanager copyItemAtPath:defaultDBPath toPath:databasePath error:nil]) {
NSLog(#"Database Copied from resources");
} else {
NSLog(#"Database copy FAILED from %# to %#",defaultDBPath,databasePath);
}
}
}
Then in your applicationDidFinishLaunching: method call this:
[self prepareDatabase];
I'm assuming that by "see if the database exists" you mean "see if the database file exists on disk". For that, you use method fileExistsAtPath: of class NSFileManager. It's an instance method, but you can use [NSFileManager defaultIntance].
Calculate the path to the file first (it's up to you how). Check if the file exists. If yes, open the file, if not, create a new database with that filename.