Can an iOS app read its own entitlements at runtime? - iphone

Can an iOS app discover, inspect, or otherwise read its own entitlements at runtime?
Ideally, I could read the whole (processed) contents of my .entitlements file as a plist. Getting just the app identifier prefix would be an acceptable second-best.
Goals here include: allowing the app to be signed with various app identifier prefixes, without needing to make a matching change in code; and to act differently in the presence or absence of shared keychain access groups. This is library code, so the less I impose on the client app's configuration, the better.

In short, no. The entitlements file is only used at compile-time and is not copied into the app bundle.
Clarification: During development, the entitlements are written into the embedded.mobileprovision file in the app bundle. When your app is released as an IPA on the App Store, it will not contain a embedded.mobileprovision.

As others mentioned in comments, the signed executable of your app contains an embedded entitlements plist, which suggests it should be possible.
You will need to use some non-ios-public (but documented) APIs. Try the following code:
// Declare the private SecTask functions in your header file
void* (SecTaskCopyValueForEntitlement)(void* task, CFStringRef entitlement, CFErrorRef _Nullable *error);
void* (SecTaskCreateFromSelf)(CFAllocatorRef allocator);
// And call it in your code like this:
CFErrorRef err = nil;
NSArray* groups = SecTaskCopyValueForEntitlement(SecTaskCreateFromSelf(NULL), CFSTR("com.apple.security.application-groups"), &err);

AFAIK, you can do it. For example, In order to read YourFileName.entitlements and get AppGroup ID, please follow below steps:
Step 1: Add your .entitlements to your target by "Copy Bundle Resources"
Step 2: Using below source code:
NSString *path = [[NSBundle mainBundle] pathForResource:#"YourFileName"
ofType:#"entitlements"];
NSDictionary *dict = [[NSDictionary alloc]initWithContentsOfFile:path];
NSString *suiteName = [[dict objectForKey:#"com.apple.security.application-groups"] firstObject]; // It's your data

Related

Path to mobile documents folder?

To get to my application documents folder, I use this code:
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
I want to access this folder, however:
~/Library/Mobile Documents
How can i easily access this as a path value? Can I do this in a similar way?
The benefit of using the constants to access system provided directories is that if Apple decide to change the structure, your application will still work. Hardcoding in something like ~/Library/Mobile Documents is brittle.
However, you can access the Library directory with the same NSSearchPathForDirectoriesInDomain with the NSLibraryDirectory constant. Then, you should just append the Mobile Documents directory path.
// Set the NO to YES to get the full path, not the ~ version.
NSString *path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, NO) lastObject];
path = [path stringByAppendingPathComponent:#"Mobile Documents"];
Looking at the constant values in http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Constants/Reference/reference.html#//apple_ref/doc/c_ref/NSSearchPathDirectory, it appears there is no specific constant for the Mobile Documents directory, so the hardcoding approach might be your only option.
Mobile Documents are iCloud documents. So you want to store documents in iCloud.
On OS X they are definitely in ~/Library/Mobile Documents (10.7 and 10.8), but on iOS you should not look.
"All documents of an application are stored either in the local sandbox or in an iCloud container directory."...
"A user should not be able to select individual documents for storage in iCloud. "
http://developer.apple.com/library/ios/#documentation/DataManagement/Conceptual/DocumentBasedAppPGiOS/ManageDocumentLifeCycle/ManageDocumentLifeCycle.html
So if your user picks iCloud then you should use iCloud.
How long the iCloud document model will last is anyones guess, but that's the way it works today. The whole thing seems a masterpiece of poor UI design, as this direct answer to your question shows:
-(NSURL*)ubiquitousContainerURL {
return [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
}
In my recent macOS app I had the same need: how to gain access to the root folder of your iCloud directory.
IMPORTANT!
This is written for an unsandboxed version, since the app is only intended for myself. If you plan to release an app on the Mac App Store, do not turn off sandboxed version.
I turn off sandbox in the app's entitlements file.
This code will access your iCloud root folder:
let pathToiCloudFolder = NSString(string: "com~apple~CloudDocs").expandingTildeInPath
let backUpFolderUrl = FileManager.default.urls(for: .libraryDirectory, in:.userDomainMask).first!
let backupUrl = backUpFolderUrl.appendingPathComponent("Mobile Documents/" + pathToiCloudFolder)
print("Backup Folder:", backupUrl)

Separate User from App Database

My app just got rejected by Apple with the following reason:
2.23
We found that your app does not follow the iOS Data Storage
Guidelines, which is required per the App Store Review Guidelines.
In particular, we found that on launch and/or content download, your
app stores 10.3 MB. To check how much data your app is storing:
Install and launch your app
Go to Settings > iCloud > Storage & Backup > Manage Storage
If necessary, tap "Show all apps"
Check your app's storage
The iOS Data Storage Guidelines indicate that only content that the
user creates using your app, e.g., documents, new files, edits, etc.,
may be stored in the /Documents directory - and backed up by iCloud.
What I do is, I deliver the database in the resource folder with about 10 MB and copy that database to the library path on initial startup (see code below). When looking at my app settings after the startup within the device settings, it actually says, that the documents & data folder contain this 10 MB of data. The app does not need that database anymore when it is once installed, so I just tried to remove the DB from the resource folder, when the copy is done by using the removeItemAtPath. But there seems to be a permission issue with that.
Here the code I am using to populate the database at initial startup:
// Copy the database from the app resource, if it is not already existing in the library path
- (void) copyDatabaseIfNeeded {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSString *dbPath = [documentsDir stringByAppendingPathComponent:#"abiliator.sqlite3"];
BOOL success = [fileManager fileExistsAtPath:dbPath];
if(!success) {
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"abiliator.sqlite3"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:dbPath error:&error];
if (!success) {
NSLog(#"Failed to create writable database file with message '%#'.", error);
}
else {
success = [fileManager removeItemAtPath:defaultDBPath error:&error];
if (!success) {
NSLog(#"Failed to remove the source database file with message '%#'.", error);
}
}
}
}
After researching for hours about that issue, I am actually pretty sure, that I am not doing ANYTHING wrong at all. The Library path is the location to store updateable files according to Apple documentation. Especially if the data is wanted to be backed up and hidden / not exposed to the user. Both is the case for my app, I want the data to be backed up and I don't want the user to see my database. So Library seems perfectly right. Only thing I could think of is the size of the database in the resource directory. I could reduce that by zipping it. But what are the limits? Neither the reviewers nor the documentation could tell me anything specific on that.
So what is actually wrong? And if the resource directory is not the right place to store my source database for initial setup, what other directory could I use in my project?
thanks a lot for any hint.
René
Got a reply from Apple mentioning that I am supposed to store the user data in a different database than the data I am delivering. Though I am not very keen on that, as it increases the code complexity unneccessary and the user data in my app can get larger than the one I deliver anyway, I would like to make sure, that I get the correct solution approach for that now.
What I intend to implement is 2 directories: /Library/UserDB and /Library/AppDB. The AppDB would contain the delivered DB and the UserDB would contain the user data only and I would flag the AppDB Dir as non-backup. Guess that would make Apple happy and get my app approved for that matter, am I right? Would appreciate any opinion about that approach before I start implementing.

Prevent iCloud sync of data (using .nosync?)

EDIT: So far, the best I've been able to come up with is a pop-up to ask the user to disable iCloud sync, along with moving all the data to the Documents directory so it won't get wiped: In iOS5, is it possible to detect if a user has an app set to back up?
I develop offline mapping application for iPhone/iPad.
We used to store all of the data (many gigs potentially) in the Caches directory.
As of iOS5, the files in the Caches directory can be randomly deleted when the user's hard drive starts getting full.
How can I store local data, without the data being synced to iCloud, iTunes, and without it being randomly deleted? My local data is a large directory tree with many small data files, in thousands of subdirectories.
I moved our directory tree from the library cache directory to a data.nosync directory in the documents directory, because we had read this might be a solution. However, the data in the nosync folder is still being backed up to iCloud.
Here is now I create the directory:
NSString* noSyncDirectory() {
static NSString *directory = nil;
if (!directory) {
directory = [[NSString stringWithFormat:#"%#/%#",
documentsDirectory(), #"data.nosync"] retain];
[Constants createDirectoryIfNeeded:directory];
}
return directory;
}
From: https://developer.apple.com/library/ios/#qa/qa1719/_index.html
You can use the following method to set the "do not back up" extended attribute. Whenever you create a file or folder that should not be backed up, write the data to the file and then call this method, passing in a URL to the file.
#include <sys/xattr.h>
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
const char* filePath = [[URL path] fileSystemRepresentation];
const char* attrName = "com.apple.MobileBackup";
u_int8_t attrValue = 1;
int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
return result == 0;
}
More information can be found: https://developer.apple.com/icloud/documentation/data-storage/
Further note: While the developer documentation incorrectly implies ("These files will not be purged and will not be included in the user's iCloud or iTunes backup.") that the do-not-backup flag doubles as a do-not-purge flag that is not the case. Simply leaving files in the Caches Directory and flagging them do-not-backup will not prevent their wipe.
Perhaps you can disable backup for your app and store data files somewhere else in the app tree. Any stuff that needs to be backed can be put in a common area outside your app.
You might be able to do it in provisioning: invalid code signing app submission
or settings:
When you want to give the user the option to enable or disable iCloud usage entirely for your app. If your app includes a Settings bundle or inline preferences, you could include a preference to toggle whether your app stores content in iCloud at all. For example, an app whose data consists entirely of privately managed files might do this to give the user the choice of how those files are stored.
or by removing the com.apple.developer.ubiquity-container-identifiers entitlement (which could get auto-added) with Xcode: Configuring Your App's iCloud Entitlements
Otherwise you might need to issue a warning with instructions on disabling through the UI:
http://www.pcmag.com/article2/0,2817,2394702,00.asp#fbid=bpIwPLZ1HeQ
Another workaround is to group the maps into collections that are installed as separate applications. That would be a way to store the data without creating any directories that sync or get backed-up. The data will be stored in the the .app directory and will be protected.
Depending on how the cache space reclamation function works, it might not delete recently accessed or modified files. You could try periodically touching them on a timer. You could also add some old files as decoys and detect when they've been deleted or when space is low to at least issue a warning or re-download the deleted objects...
This issue might not have a workaround yet... You could possibly try calling URLForUbiquityContainerIdentifier explicitly, since it does some initialization on the first invocation. Then create a sub-directory with a .nosync suffix (based on this example).
The first time you call this method for a given container directory, iOS extends your application sandbox to include that container directory. Thus, it is important that you call this method at least once before trying to search for files in iCloud. And if your application accesses multiple container directories, you should call the method once for each directory.
The doc on .nosync:
To ensure that the persistent store itself is not synced by iCloud: when you set a value for the NSPersistentStoreUbiquitousContentNameKey, UIManagedDocument puts the persistent store in a .nosync directory inside the document package. If you make use of additional content (using the writeAdditionalContent:toURL:originalContentsURL:error: method), you must make sure that the document directory is not a package. Typically you give the document directory an extension that is not recognized as a document extension.
You may want to ensure you have the com.apple.developer.ubiquity-container-identifiers entitlement.
The iCloud Containers field identifies the list of container directories that your app can access in the user’s iCloud storage. (This field corresponds to the com.apple.developer.ubiquity-container-identifiers entitlement.)
Maybe uncheck "Enable Entitlements" in the summary pane of the project or edit the profile to remove the *ubiquity settings. (Notes for ios 5 beta 7 reference entitlements.)
There is also the setUbiquitous function:
setUbiquitous:itemAtURL:destinationURL:error:
Sets whether the item at the specified URL should be stored in the cloud.
Parameters
flag
Specify YES to move the item to iCloud or NO to remove it from iCloud (if it is there currently).
http://developer.apple.com/library/ios/#DOCUMENTATION/Cocoa/Reference/Foundation/Classes/NSFileManager_Class/Reference/Reference.html#//apple_ref/occ/instm/NSFileManager/setUbiquitous:itemAtURL:destinationURL:error:
There is a newer way to prevent iCloud syncing of data without using extended attributes directly:
- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString
{
NSURL* URL= [NSURL fileURLWithPath: filePathString];
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
NSLog(#"Error excluding %# from backup %#", [URL lastPathComponent], error);
}
return success;
}
See Apple's Technical Q&A QA1719 for more details.
Did you try just naming the directory ".nosync" without the data in front? It could be generally . directories are ignored, or perhaps that specifically.
But, it seems like the behavior is just as a user would want it - potentially gigs of space used by an application they may not be using currently, where the space could be reclaimed automatically. I am not sure but you would think the system would be smart about reclaiming files created recently only after there was no choice, so if the user had just stored maps recently they would not be deleted unless there was no other choice.
For the purposes of filing a bug I am going to ask for a way to mark a directory for user prompting before deletion - so that if they are syncing a lot of movies and it would clear out a data set like the maps you are talking about, the user would be asked if they want to remove the data "offline maps" from application "MyCoolMapper" to proceed with the sync.

Implementing and Testing iOS data protection

Just saw the Session 209 - Securing Application Data from de 2010 WWDC.
The keynote explains a lot of things, including the way you can set data protection attributes to your files (NSFileProtectionComplete, NSFileProtectionNone) and how to decide which protection is best for your case.
I just implemented it, but can't figure out how to test if the security is on, any ideas?
In addition, I have a sql lite database that needs to be accessed in background from time to time, and this method of data protection seems to be not good enough.. any link or tutorial that guide me through the best db protection? (found sql cipher but is kinda heavy to add in a evoluted project)
Thanks!
Update: With iOS 6 it's supposedly possible to require data protection for your application by using an entitlement that needs to be configured on the App ID in the iOS provisioning profile. I haven't tested this yet, and this is the best information I could find on it https://devforums.apple.com/message/707939#707939
My investigations into this matter lead me to believe that it is very difficult to determine if data protection is enabled on a device.
File protection is enabled by setting the NSFileProtectionKey file attribute to NSFileProtectionComplete
For example, to create a protected file you could run code like:
[[NSFileManager defaultManager] createFileAtPath:[self filePath]
contents:[#"super secret file contents" dataUsingEncoding:NSUTF8StringEncoding]
attributes:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
forKey:NSFileProtectionKey]];
Unfortunately this code will execute without error even if Data Protection is not enabled on the device (or if the code is run on the Simulator where Data Protection is not available).
Worse, the NSFileProtectionComplete attribute will be be set regardless of whether the file is protected or not. The following:
self.fileProtectionValue = [[[NSFileManager defaultManager] attributesOfItemAtPath:[self filePath]
error:NULL] valueForKey:NSFileProtectionKey];
NSLog(#"file protection value: %#", self.fileProtectionValue);
will spit out file protection value: NSFileProtectionComplete no matter whether Data Protection is enabled or not.
There are two methods that I've been able to use to discover if File Protection is working as expected. Unfortunately neither of these methods are suitable for detecting if Data Protection is enabled on a device in the field.
Both methods work on the idea that a protected file can not be read if the device is locked.
Method one involves using a timer to attempt to read the file after the device is locked, but while your application continues to run:
[self performSelector:#selector(doReload) withObject:nil afterDelay:20];
- (void)doReload {
NSLog(#"protected data available: %#",[[UIApplication sharedApplication] isProtectedDataAvailable] ? #"yes" : #"no");
NSError *error;
self.fileContents = [NSString stringWithContentsOfFile:[self filePath]
encoding:NSUTF8StringEncoding
error:&error];
NSLog(#"file contents: %#\nerror: %#", self.fileContents, error);
}
If you run the above code and lock a data protected device it will spit out:
protected data available: no
file contents: (null)
error: Error Domain=NSCocoaErrorDomain Code=257 "The operation couldn’t be completed. (Cocoa error 257.)" UserInfo=0x16e110 {NSFilePath=/var/mobile/Applications/D71F1F1F-6C25-4848-BB1F-51539B47EC79/Documents/protected_file, NSUnderlyingError=0x16e010 "The operation couldn’t be completed. Operation not permitted"}
The 20 second delay is necessary because there is a 10 second or so grace period where protected data is still available after a Data Protection enabled device is locked.
The second method is to create a protected file in an application, exit the application, lock the device, wait 10 seconds, and then use the XCode organizer to download the contents of the application. This will produce an error message and the protected file will be empty.
If either of the above tests fail to behave as described then Data Protection is either not enable, or your File Protection code was not implemented correctly.
Because I've not found any way to verify within the application that Data Protection is enabled before I write confidential information to disk, I've filed a feature enhancement request with Apple to be able to mark an application as requiring Data Protection to be enabled. (rdar://10167256)
Apple does offer a solution to this through their Mobile Device Management (MDM) APIs, which combined with a third party server can be used to enforce policies that require Data Protection to be enabled on devices.
You can use the iExplorer app to detect if your files are encrypted. iExplorer lets you browse the filesystem of your iPhone/iPad, and open the file (of course your device must be plugged into your Mac).
When the device is locked, the files can't be read correctly.
From the NSFileManager class doc:
The file is stored in an encrypted format on disk and cannot be read from or written to while the device is locked or booting.
You just pass the constant when you set the file attributes.
When writing the contents of an NSData object to disk using the writeToFile:options:error: method, include the NSDataWritingFileProtectionComplete option.
Use the setAttributes:ofItemAtPath:error: method of NSFileManager to add the NSFileProtectionKey attribute (with the NSFileProtectionComplete value) to an existing file
http://developer.apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/StandardBehaviors/StandardBehaviors.html
EDIT (Determining the Availability of Protected Files)
A protected file is accessible only when a device is unlocked. Because applications may continue running while a device is locked, your code should be prepared to handle the possibility of protected files becoming unavailable at any time. The UIKit framework provides ways to track whether data protection is currently enabled.
*
Use applicationProtectedDataWillBecomeUnavailable: and applicationProtectedDataDidBecomeAvailable: methods and use them to track changes to the availability of protected data.
*
An application can register for the UIApplicationProtectedDataWillBecomeUnavailable and UIApplicationProtectedDataDidBecomeAvailable notifications.
*
The protectedDataAvailable property of the shared UIApplication object indicates whether protected files are currently accessible.
Any application that works with protected files should implement the application delegate methods. When the applicationProtectedDataWillBecomeUnavailable: method is called, your application should immediately close any protected files and refrain from using them again until the applicationProtectedDataDidBecomeAvailable: method is called. Any attempts to access the protected files while they are unavailable will fail.
Verifying file protection on jailbroken devices
To step further, if you would like to test the file protection of exact file, then you would need a jailbroken device. For that, here are the (non-detailed) steps:
1) Jailbreak an iOS device
2) Install Open SSH via Cydia (This is required to remotely access files from that device) (https://cydia.saurik.com/openssh.html)
3) Login from your computer (using Mac client or Terminal) as a root user to your device.
To find location of your app's directories and files, there are various ways. Either you can
grep the process of an app (Such as ps ax | grep YourAppName) - Make sure app is running on device to get the process details. It should give the location of app bundles
Alternatively, you can also search specific file using find you are interested in. For eg. find / -type f -name YouAppName.sqlite. It should give file location on the device.
From here, you can try to see if the file is really accessible or not, when phone is locked with a passcode; or not.
- You can simply run cat YouAppName.sqlite to see if contents are accessible. Ia f file is protected, it should show
Operation not permitted
error; else if would show contents of file.
Again, this is required if you'd really like to check file protection of an individual file. If entitlements and capabilities are set properly, verifying entitlements should be enough for fileprotection.
On a side node, file explorer tools such as iExplorer don't help much in verification of FileProtection, because such tools require a device to be in "trusted" mode, so they have permissions to access the content of your device/apps.
Good luck!
Testing can be done within Xcode:
Delete and reinstall your app
On your iOS device go to Settings > Passcode and ensure 'Require Passcode' is set to 'Immediately'
Lock your phone and wait 20 seconds
On Xcode go to Window > Devices
Select the app you'd like to test
Click the settings cog and choose 'download container'
Right click the downloaded .xcappdata file and select 'show package contents'. Anything you can view here has not been encrypted by NSFileProtectionComplete
Unlock your phone and repeat steps 3-6. Files that previously did not appear that you can now view were successfully encrypted.
File protection can be enabled on a per-file or per-directory basis, or can be enabled for the whole application (using entitlements and the provisioning profile). To determine if a file or directory is protected, check the filesystem attributes for the data protection key. This should be valid even it's a parent directory that was set to be protected:
- (BOOL) isProtectedItemAtURL:(NSURL *)URL {
BOOL result = YES;
NSDictionary *attributes = nil;
NSString *protectionAttributeValue = nil;
NSFileManager *fileManager = nil;
fileManager = [[NSFileManager alloc] init];
attributes = [fileManager attributesOfItemAtPath:[URL path] error:&error];
if (attributes != nil){
protectionAttributeValue = [attributes valueForKey:NSFileProtectionKey];
if ((protectionAttributeValue == nil) || [protectionAttributeValue isEqualToString:NSFileProtectionNone]){
result = NO;
}
} else {
// handle the error
}
return result;
}
To determine if the protected content is available, UIApplication provides a method for querying the protection state, isProtectedDataAvailable. Using it with the above method would allow you to determine wether a particular file or directory is available:
- (BOOL) isItemAtURLAvailable:(NSURL *)URL {
BOOL result = NO;
if ([self isProtectedItemAtURL:URL]){
// Item is protected
if ([[UIApplication sharedApplication] isProtectedDataAvailable]){
// Protected content is available
result = YES;
}
} else {
result = YES;
}
return result;
}
I don't think you can test Data Protection with computer-based tools and a non-jailbroken iPhone anymore - maybe you could in the past. Please see my answer here for an updated method for testing Data Protection: https://stackoverflow.com/a/40044841/1165843
For a complete testing I would suggest using tools such as iDB ( https://github.com/dmayer/idb ) for performing pentesting on your iOS app, as described in this guide.
Also relevant for you may be this Cheat Sheet for iOS security testing.

How do I fix Cocoa error 513?

I have an iPhone application in which a number of domain objects are populated with user-entered data. In order to restore state after being interrupted, these objects implement the NSCoding protocol and are written to disk (the Documents directory) in the applicationWillTerminate message. Then, when the application is launched again, the bytes of data are loaded up and the domain objects are repopulated. Code to get the documents directory is as follows:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
This worked great in the simulator but once I deployed the app to the iPhone it stopped working. The reason why is iPhone error code 513 - which apparently means "permission denied." There is a note in the iPhone dev docs that explain a little bit more -
On the device, the returned path
(documentsDirectory) is similar to the
following:
/var/mobile/Applications/30B51836-D2DD-43AA-BCB4-9D4DADFED6A2/Documents
However, on the Simulator, the
returned path takes the following
form:
/Volumes/Stuff/Users/johnDoe/Library/Application
Support/iPhone
Simulator/User/Applications/118086A0-FAAF-4CD4-9A0F-CD5E8D287270/Documents
This is the exact behavior that I'm seeing. I'm not really sure how this relates to getting a permission denied error and what I can do to fix it. It does say below -
To read and write user preferences,
use the NSUserDefaults class or the
CFPreferences API. These interfaces
eliminate the need for you to
construct a path to the
Library/Preferences/ directory and
read and write preference files
directly. For more information on
using these interfaces, see “Adding
the Settings Bundle.”
If your application contains sound,
image, or other resources in the
application bundle, you should use the
NSBundle class or CFBundle opaque type
to load those resources. Bundles have
an inherent knowledge of where
resources live inside the application.
In addition, bundles are aware of the
user’s language preferences and are
able to choose localized resources
over default resources automatically.
For more information on bundles, see
“The Application Bundle.”
I don't see how I can use the Application Bundle to load bytes of data though. Any help or examples?
Not sure how this works, but apparently using stringByAppendingPathComponent instead of stringByAppendingString for path creation fixed the problem.
The paragraph related to the application bundle refers to:
NSString *path = [[NSBundle mainBundle] pathForResource:#"somefile" ofType:#"png"];
This and other methods from NSBundle allow you to refer resources from inside the application bundle without actually knowing where the application bundle is located.
Disclaimer: I haven't worked with the iPhone.
With the disclaimer in mind, in plain OS X it's considered bad form to write stuff inside the application bundle. You save stuff under the user's Application Support directory -> see NSApplicationSupportDirectory.
I got this error (513) because the path was wrong. Double checking my path fixed the problem :)