Years ago when I was working with C# I could easily create a temporary file and get its name with this function:
Path.GetTempFileName();
This function would create a file with a unique name in the temporary directory and return the full path to that file.
In the Cocoa API's, the closest thing I can find is:
NSTemporaryDirectory
Am I missing something obvious or is there no built in way to do this?
A safe way is to use mkstemp(3).
[Note: This applies to the iPhone SDK, not the Mac OS SDK]
From what I can tell, these functions aren't present in the SDK (the unistd.h file is drastically pared down when compared to the standard Mac OS X 10.5 file). I would use something along the lines of:
[NSTemporaryDirectory() stringByAppendingPathComponent: [NSString stringWithFormat: #"%.0f.%#", [NSDate timeIntervalSinceReferenceDate] * 1000.0, #"txt"]];
Not the prettiest, but functional
Apple has provided an excellent way for accessing temp directory and creating unique names for the temp files.
- (NSString *)pathForTemporaryFileWithPrefix:(NSString *)prefix
{
NSString * result;
CFUUIDRef uuid;
CFStringRef uuidStr;
uuid = CFUUIDCreate(NULL);
assert(uuid != NULL);
uuidStr = CFUUIDCreateString(NULL, uuid);
assert(uuidStr != NULL);
result = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"%#-%#", prefix, uuidStr]];
assert(result != nil);
CFRelease(uuidStr);
CFRelease(uuid);
return result;
}
LINK :::: http://developer.apple.com/library/ios/#samplecode/SimpleURLConnections/Introduction/Intro.html#//apple_ref/doc/uid/DTS40009245
see file :::AppDelegate.m
I created a pure Cocoa solution by way of a category on NSFileManager that uses a combination of NSTemporary() and a globally unique ID.
Here the header file:
#interface NSFileManager (TemporaryDirectory)
-(NSString *) createTemporaryDirectory;
#end
And the implementation file:
#implementation NSFileManager (TemporaryDirectory)
-(NSString *) createTemporaryDirectory {
// Create a unique directory in the system temporary directory
NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:guid];
if (![self createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:nil]) {
return nil;
}
return path;
}
#end
This creates a temporary directory but could be easily adapted to use createFileAtPath:contents:attributes: instead of createDirectoryAtPath: to create a file instead.
If targeting iOS 6.0 or Mac OS X 10.8 or higher:
NSString *tempFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
Swift 5 and Swift 4.2
import Foundation
func pathForTemporaryFile(with prefix: String) -> URL {
let uuid = UUID().uuidString
let pathComponent = "\(prefix)-\(uuid)"
var tempPath = URL(fileURLWithPath: NSTemporaryDirectory())
tempPath.appendPathComponent(pathComponent)
return tempPath
}
let url = pathForTemporaryFile(with: "blah")
print(url)
// file:///var/folders/42/fg3l5j123z6668cgt81dhks80000gn/T/johndoe.KillerApp/blah-E1DCE512-AC4B-4EAB-8838-547C0502E264
Or alternatively Ssswift's oneliner:
let prefix = "blah"
let url2 = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("\(prefix)-\(UUID())")
print(url2)
You could use mktemp to get a temp filename.
The modern way to do this is FileManager's url(for:in:appropriateFor:create:).
With this method, you can specify a SearchPathDirectory to say exactly what kind of temporary directory you want. For example, a .cachesDirectory will persist between runs (as possible) and be saved in the user's library, while a .itemReplacementDirectory will be on the same volume as the target file.
Don't use legacy APIs like NSTemporaryDirectory, get a proper URL instead from FileManager.
let tmpURL = FileManager
.default
.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
You'd still have to create the directory.
You could use an NSTask to uuidgen to get a unique file name, then append that to a string from NSTemporaryDirectory(). This won't work on Cocoa Touch. It is a bit long-winded though.
Adding to #Philipp:
- (NSString *)createTemporaryFile:(NSData *)contents {
// Create a unique file in the system temporary directory
NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:guid];
if(![self createFileAtPath:path contents:contents attributes:nil]) {
return nil;
}
return path;
}
Related
I have an old Xcode/ObC quiz game that I launched quite a few years ago, pre-swift, that has been, and still is quite successful for me. At least the local version.
I am now at the finish line rewriting this game in Unity3d c#.
Something I have been thinking about lately is how to to maintain the "old" statistics that is saved in a plist file in IOS. I have been google this but I would need some more information to really understand how to proceed.
What will happen with the stats-plist file when I upgrade the current Xcode/ObC with the new Unity-based project, will it still be there and is it possible to easily find it? This particular plist is added when the first player is added and then updated with stats and new players.
Is there a good, and easy, way reading plist from Unity and convert to a normal text file?
To be able to find the file from Unity I am thinking of launching a maintenance release of the ObC based game and only copy this plist file to another directory (Document) to prepare for the new big release. When starting the Unity-based game for the first time I could then read the copied file and process so the player do not lose his/her stats.
The problem I have is that the only time I have updated the actual ObC code the last 5 - 6 years is when I updated the app from 32 to 64 bit so my skills on ObC is very limited at the moment.
I have been thinking of using something like this for the plist:
NSFileManager *filemgr;
filemgr = [NSFileManager defaultManager];
if ([filemgr copyItemAtPath: #"/tmp/myfile.txt" toPath: #"/Users/demo/newfile.txt" error: NULL] == YES)
NSLog (#"Copy successful");
else
NSLog (#"Copy failed");
I would really appreciate some advise how I should process this.
Here is some code you can use to list the doc and app support contents. I guard this with the #define as I do not want this in the final app. I also use it to perform some cleanup (the commented out stuff) that you can use if you need to delete something.
#ifdef DEBUG
// Auxiliary function to list directory contents
+ (void) docList
{
NSFileManager * fileManager = NSFileManager.defaultManager;
NSURL * docUrl = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
NSString * docPath = docUrl.path;
NSLog( #"User document directory" );
NSLog( #"%# listing follows", docPath );
[fmUtil traverse:docPath tab:#"\t" fileManager:fileManager];
}
// Auxiliary function to list application support path
+ (void) supList
{
NSFileManager * fileManager = NSFileManager.defaultManager;
NSURL * docUrl = [NSFileManager.defaultManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask].firstObject;
NSString * docPath = docUrl.path;
NSLog( #"Application support directory" );
NSLog( #"\t%#", docPath );
NSLog( #"\tAppending bundle identifier" );
docPath = [docPath stringByAppendingPathComponent:NSBundle.mainBundle.bundleIdentifier];
NSLog( #"\t%#", docPath );
NSLog( #"%# listing follows", docPath );
[fmUtil traverse:docPath tab:#"\t" fileManager:fileManager];
}
+ (void) traverse:(NSString *)root tab:(NSString *)tab fileManager:(NSFileManager *)fileManager
{
NSArray * dir = [fileManager contentsOfDirectoryAtPath:root error:NULL];
for ( NSString * s in dir )
{
// See if this is a directory or not
NSString * t = [root stringByAppendingPathComponent:s];
BOOL isDir = NO;
[fileManager fileExistsAtPath:t isDirectory:& isDir];
if ( isDir )
{
// Report
NSLog(#"%#%#/",tab,s);
// Traverse
[fmUtil traverse:t tab:[tab stringByAppendingString:#"\t"]
fileManager:fileManager];
}
else
{
// Get size of normal file
NSDictionary * fa = [fileManager attributesOfItemAtPath:t error:NULL];
NSNumber * fz = [fa objectForKey:NSFileSize];
NSDate * fm = [fa objectForKey:NSFileModificationDate];
unsigned long long n = fz.unsignedLongLongValue;
// Some formatting
NSString * f = s;
while ( f.length < 50 )
{
f = [f stringByAppendingString:#" "];
}
NSLog(#"%#%# %15llu bytes (%#)", tab, f, n, [fm descriptionWithLocale:NSLocale.currentLocale] );
// To delete something for test purposes ...
/*
if ( [t.lastPathComponent isEqualToString:#"P5041-1-BuildingStatistics"] && [fileManager removeItemAtPath:t error:NULL] )
{
NSLog( #"%#%# now xxxxxxxxxxxxxxxxxx", tab, f );
}
if ( [t.lastPathComponent isEqualToString:#"index"] && [fileManager removeItemAtPath:t error:NULL] )
{
NSLog( #"%#%# now xxxxxxxxxxxxxxxxxx", tab, f );
}
*/
}
}
}
#endif
Put this in your app delegate e.g. inside application:didFinishLaunchingWithOptions:.
Well, today I started to look back on this "problem" and after testing it turned out not to be a problem at all. I tried initially to find the path via my Objective-C code (this post) but when testing I realised that the files were stored in the (IOS game) Document directory, the same as "Application.persistentDataPath". In Unity I used the following path's:
filePathPlayerData = Application.persistentDataPath + "/playerdata.plist";
filePathPlayerStats = Application.persistentDataPath + "/playerstatistics.plist";
Reading and process the file is easy as well as it is XML structure. However due to the simple format I opened the files as text files wrote my own simple parser in c# (unity), rather than using an XML parser. The whole "problem" turned out not to be any problem.
I am on OSX, Objective-C.
I have a path/NSURL like
/Users/xxx/Desktop/image2.png
But i pass it to a third party application that excpects finder pathes like
Harddisk:Users:Desktop:image2.png
Is there any method (i can't find) to convert pathes like that or get them out of an NSURL (if possible without string modifying)?
In AppleScript it is
return POSIX file "/Users/xxx/Desktop/image2.png" --> Harddisk:Users:xxx:Desktop:image2.png
EDIT: This is pretty much the same: Cocoa path string conversion
Unfortunately, the method is deprecated...
There is no (easy) alternative at the moment.
The function CFURLCopyFileSystemPath is not deprecated, only the enum case kCFURLHFSPathStyle is deprecated but the raw value 1 is still working and avoids the warning.
I'm using this category of NSString
#implementation NSString (POSIX_HFS)
- (NSString *)hfsPathFromPOSIXPath
{
CFStringRef hfsPath = CFURLCopyFileSystemPath((CFURLRef)[NSURL fileURLWithPath:self], 1);
return (NSString *)CFBridgingRelease(hfsPath);
}
#end
The function works also in Swift. The Swift version is a bit more sophisticated and adds the trailing semicolon representing a dictionary implicitly, here as an extension of URL:
extension URL {
func hfsPath() -> String?
{
if let cfpathHFS = CFURLCopyFileSystemPath(self as CFURL, CFURLPathStyle(rawValue: 1)!) { // CFURLPathStyle.CFURLHFSPathStyle)
let pathHFS = cfpathHFS as String
do {
let info = try self.resourceValues(forKeys: [.isDirectoryKey, .isPackageKey])
let isDirectory = info.isDirectory!
let isPackage = info.isPackage!
if isDirectory && !isPackage {
return pathHFS + ":" // directory, not package
}
} catch _ {}
return pathHFS
}
return nil
}
}
Vadians answer is better than this one - but if vadians method is deprecated, this will be an alternative. Idea is to use applescripts methods to get HFS path called easily with an osascript from an NSString category.
NSString category (credits: https://stackoverflow.com/a/19014463/4591992)
#implementation NSString (ShellExecution)
- (NSString*)runAsCommand {
NSPipe* pipe = [NSPipe pipe];
NSTask* task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/sh"];
[task setArguments:#[#"-c", [NSString stringWithFormat:#"%#", self]]];
[task setStandardOutput:pipe];
NSFileHandle* file = [pipe fileHandleForReading];
[task launch];
NSString* result = [[NSString alloc] initWithData:[file readDataToEndOfFile] encoding:NSUTF8StringEncoding];
return result;
}
#end
Usage for this case:
NSString* posixToHFS = [NSString stringWithFormat:#"osascript -e 'POSIX file \"%#\" as text'",filePath];
filePath = [posixToHFS runAsCommand];
In my own testing (on 10.13.6, 10.14.6 and 10.15b7), #vadian's solution doesn't work with paths where a folder name component contains a "/" (when viewed in Finder), which then appears as a ":" in a POSIX path and as a "/" in a HFS path.
Demonstration of the bug
Here's a quick test program that you can build by creating a new "command line" project in Xcode:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSString *posixPath = #"/EndsInSlash:";
NSURL *url = [NSURL fileURLWithPath:posixPath];
if (url == nil) {
NSLog(#"Oops, this went wrong");
} else {
CFStringRef hfsPath = CFURLCopyFileSystemPath((CFURLRef)url, 1);
NSString *res = (NSString *)CFBridgingRelease (hfsPath);
NSLog(#"HFS path: <%#>", res);
}
}
return 0;
}
When you run it, you'll probably see a correct result printed, i.e. a path that ends in "/". However, that only works if the folder does not exist. So, create a folder named "EndsInSlash/" (not a file!) in your root folder and run the app again - now the resulting path does not end in "/" any more as it should.
Work-around
Below is a "smart" function that uses the faster CFURLCopyFileSystemPath() function whenever possible, i.e. unless a ":" appears in the POSIX path - in which case it performs the conversion on its own, by splitting up the POSIX path into its components, converting them individually (replacing ":" into "/"), prepending the volume name and then merging the components again. This appears to work fine even on macOS 10.15 (Catalina), despite the deprecation warnings.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
static NSString* stringWithHFSUniStr255(const HFSUniStr255* hfsString)
{
CFStringRef stringRef = FSCreateStringFromHFSUniStr(nil, hfsString);
NSString* result = CFBridgingRelease(stringRef);
return result;
}
NSString* hfsPathFromPOSIXPath (NSString *posixPath)
{
if (posixPath == nil) return #"";
if ([posixPath containsString:#":"]) {
// slow version, but can handle ":" appearing in path components
NSString *result = nil;
FSRef ref;
Boolean isDir;
if (FSPathMakeRef ((const UInt8*)posixPath.UTF8String, &ref, &isDir) == noErr) {
HFSUniStr255 elemName;
FSCatalogInfo catInfo;
NSMutableArray<NSString*> *elems = [NSMutableArray arrayWithCapacity:16];
while (FSGetCatalogInfo (&ref, kFSCatInfoNodeID, &catInfo, &elemName, nil, &ref) == noErr) {
[elems insertObject: stringWithHFSUniStr255(&elemName) atIndex:0];
if (catInfo.nodeID == 2) break;
}
result = [elems componentsJoinedByString:#":"];
}
return result;
} else {
// see https://stackoverflow.com/a/45085776/43615
NSURL *url = [NSURL fileURLWithPath:posixPath];
if (url == nil) {
// could not convert because the path doesn't exist
return nil;
}
CFStringRef hfsPath = CFURLCopyFileSystemPath((CFURLRef)url, kCFURLHFSPathStyle);
return (NSString *)CFBridgingRelease (hfsPath);
}
}
#pragma clang diagnostic pop
See also
Discussion of a related bug with AppleScript, with a work-around: https://forum.latenightsw.com/t/xxx/2097
Bug report filed with Apple: http://www.openradar.me/radar?id=4994410022436864
I have some constant strings defined in my #implementation file like:
static NSString * const contentDisplayDateKeyPath = #"content.display_date";
static NSString * const contentIDKeyPath = #"content.id";
Could I get the content of contentDisplayDateKeyPath use a string which holding the variable's name in runtime?
ex:
NSString *constantName = #"contentDisplayDateKeyPath"
[self valueForKey:constantName]
then I'll get content.display_date
Can this be achieved?
I am trying to achieve this by using CFBundleGetDataPointer
CFBundleRef mainBundle = CFBundleGetBundleWithIdentifier(CFBridgingRetain([[NSBundle mainBundle] bundleIdentifier]));
void *stringPointer = CFBundleGetDataPointerForName(mainBundle, CFBridgingRetain(obj));
NSString *string = (__bridge NSString *)stringPointer;
But the stringPointer is always null.
Thanks for help
This should do it for you.
NSString *__autoreleasing *string = (NSString*__autoreleasing*)dlsym(RTLD_DEFAULT, "<name of constant like PKPaymentNetworkVisa>");
NSLog(#"%#", *string);
Use a map with the key as the constant name and the value as the constant value:
static NSDictionary *_constants = #{
#"contentDisplayDateKeyPath" : #"content.display_date",
#"contentIDKeyPath" : #"content.id",
// etc.
};
...
NSString *constantName = #"contentDisplayDateKeyPath";
NSString *constantValue = _constants[constantName];
Another option is to encapsulate this into a singleton object and access your constants through read only properties. Check out What should my Objective-C singleton look like? to see the singleton design pattern.
This question was answered here:
How do I lookup a string constant at runtime in Objective-C?
The solution worked perfectly for me.
You can use CFBundleGetDataPointerForName to lookup a constant's value at runtime
-(NSString *)lookupStringConstant:(NSString *)constantName
{
void ** dataPtr = CFBundleGetDataPointerForName(CFBundleGetMainBundle(), (__bridge CFStringRef)constantName);
return (__bridge NSString *)(dataPtr ? *dataPtr : nil);
}
Many methods of reading from a filesystem using NSFileManager and lower level APIs on iOS involve built-in caching, so reading from directories that haven't changed can be quite fast, even if there's lots of items in the directories.
I have a situation where I want to be able to enumerate files in a directory using a glob:
i.e. the folder has files named like this:
1-1-0.png
1-2-0.png
1-3-0.png
1-3-1.png
2-2-1.png
5-1-1.png
5-1-2.png
5-2-1.png
5-3-0.png
6-1-1.png
...
1501-5-2.png
I might want to get all filenames matching 5-*-1.png, which would give me back 5-1-1.png and 5-2-1.png.
Loading the whole directory listing then doing the globbing in RAM is pretty straightforward, but is there a method for doing this at an OS level that would have caching built-in, so repeated calls to the same glob would give cached (faster) results?
You can use the glob() function as outlined in this gist: https://gist.github.com/bkyle/293959
#include <glob.h>
+ (NSArray*) arrayWithFilesMatchingPattern: (NSString*) pattern inDirectory: (NSString*) directory {
NSMutableArray* files = [NSMutableArray array];
glob_t gt;
NSString* globPathComponent = [NSString stringWithFormat: #"/%#", pattern];
NSString* expandedDirectory = [directory stringByExpandingTildeInPath];
const char* fullPattern = [[expandedDirectory stringByAppendingPathComponent: globPathComponent] UTF8String];
if (glob(fullPattern, 0, NULL, >) == 0) {
int i;
for (i=0; i<gt.gl_matchc; i++) {
size_t len = strlen(gt.gl_pathv[i]);
NSString* filename = [[NSFileManager defaultManager] stringWithFileSystemRepresentation: gt.gl_pathv[i] length: len];
[files addObject: filename];
}
}
globfree(>);
return [NSArray arrayWithArray: files];
}
i know if we record video in iphone 3gs the video will be stored in temporary directory.so how do i get the path and play the video using mpmovieplayer?
- (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(NSString *)contextInfo{
NSLog(#"didFinishSavingWithError--videoPath in temp directory:%#",contextInfo);
NSString *file,*latestFile;
NSDate *latestDate = [NSDate distantPast];
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:[[contextInfo stringByDeletingLastPathComponent]stringByDeletingLastPathComponent]];
// Enumerate all files in the ~/tmp directory
while (file = [dirEnum nextObject]) {
// Only check files with MOV extension.
if ([[file pathExtension] isEqualToString: #"MOV"]) {
NSLog(#"latestDate:%#",latestDate);
NSLog(#"file name:%#",file);
NSLog(#"NSFileSize:%#", [[dirEnum fileAttributes] valueForKey:#"NSFileSize"]);
NSLog(#"NSFileModificationDate:%#", [[dirEnum fileAttributes] valueForKey:#"NSFileModificationDate"]);
// Check if current jpg file is the latest one.
if ([(NSDate *)[[dirEnum fileAttributes] valueForKey:#"NSFileModificationDate"] compare:latestDate] == NSOrderedDescending){
latestDate = [[dirEnum fileAttributes] valueForKey:#"NSFileModificationDate"];
latestFile = file;
NSLog(#"***latestFile changed:%#",latestFile);
}
}
}
// The Video path.
latestFile = [NSTemporaryDirectory() stringByAppendingPathComponent:latestFile];
NSLog(#"This Is The Recent Video:%#",latestFile);
}
Then open Console to view the results :-) hope this helps
- (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(NSString *)contextInfo{
NSString *originalPathOfVideo=videoPath;//this will give the path of ur storing video,use this as a url and assign this url to ur mpmovieplayer
}