The iOS file system in-depth (and how to be the best using critical thinking)

Have you ever wondered how all those people out there figured out how to manipulate the iOS file system in their apps? For some strange reason, Apple has never provided well-organized documentation on the subject. Here’s how I feel: “Ask and simple question and get an obtuse and overly complex answer.” There are many articles and tutorials out there, including my own, showing you examples of Objective-C or Swift code for manipulating the iOS file system, and most of the code looks basically the same. Nonetheless, this code is deceivingly complex, often underestimated, and rarely well-explained or well-understood.

Where did everybody find this boilerplate code? From simple observation, I’ve found that in many cases, developers use a copy and paste methodology, i.e., look up a few keywords in a web search engine, find the code needed on sites like StackOverflow or some blog, copy it, paste it into an Xcode project, and beat on it until it works. I don’t want you to feel this way after reading my tutorials.

I hope you’ll find it edifying and interesting to read about how I figured out how to understand and navigate the iOS file system using the “most of the code looks basically the same” boilerplates. But I bet you’ll find it even more intriguing to find that I’ve discovered an much better alternative to the boilerplate code.


INTRODUCTION

How many of you have worked with the sandboxed file system that comes provided with your iOS apps? Notice I said “comes provided with” because, whether you use it or not, it’s there. If you haven’t availed yourself of the iOS file system, there’s a good chance that, if you’re dedicated to iOS app development long-term, you’ll end up using it sooner rather than later. I’ve used the local iOS file system extensively.

HISTORICAL BACKGROUND

So how did I figure out how to use the iOS local file system? Apple’s documentation on it isn’t the greatest, but after reading through it several times, experimenting with my own code (mainly Objective-C while learning it early on), and talking with other developers, I came to understand the iOS file system and wrote some of my own standard code for using it.

How did I figure out that you should store files that will be directly presented to and/or manipulated by your users in your app’s Documents directory? Where did I get the code I showed you in my iOS file system tutorial for determining the path to an app’s Documents directory, like shown here?

Notice there are two lines of code in my method, but one is commented out. Both lines provide the same functionality. I showed you two alternatives, i.e.,

… and …

for historical reasons. Back in the day when only Objective-C was available, I obtained the very first element of an array by addressing its first or 0-th (zero-th) element. Generally, this is the most commonly used technique for getting an app’s Documents directory.

Now that we have Swift, I can get the very first element of an array by using the first instance property of the Array collection type.

HOW I FIGURED THINGS OUT

When first presented with an app that required use of the iOS file system, I logged into my Apple Developer account and headed for the developer documentation or “knowledge base,” if you will. I first found and read the “File System Programming Guide”, especially the sections on “iOS Standard Directories: Where Files Reside”, “Where You Should Put Your App’s Files”, and “Locating Items in the Standard Directories.”

Defining a “standard directory”

Apple specifically tells us where to put local files associated with an iOS app — and they tell us why. Remember that unless you specify otherwise, your app’s own little file system is “sandboxed” from the rest of the host device’s file system for security purposes. I advise you to become familiar with the concept of sandboxing.

For the sake of brevity, I’ll urge you to read the contents at the Apple links I provided herein and to refer to my own discussion of which local directories you should use in apps that need to access to the local file system. Here’s a diagram that I created to help you understand the most commonly used app directories:

Finding the “standard directories”

When you’re “Locating Items in the Standard Directories,” here’s the advice that Apple provides:

When you need to locate a file in one of the standard directories, use the system frameworks to locate the directory first and then use the resulting URL to build a path to the file. The Foundation framework includes several options for locating the standard system directories. By using these methods, the paths will be correct whether your app is sandboxed or not:

The URLsForDirectory:inDomains: method of the NSFileManager class returns a directory’s location packaged in an NSURL object. The directory to search for is an NSSearchPathDirectory constant. These constants provide URLs for the user’s home directory, as well as most of the standard directories.
The NSSearchPathForDirectoriesInDomains function behaves like the URLsForDirectory:inDomains: method but returns the directory’s location as a string-based path. Use the URLsForDirectory:inDomains: method instead. …

Apple’s discussion on “Locating Items in the Standard Directories” shows some Objective-C sample code for finding your app’s Application Support directory, meant “for any files that are not user data files” (see “Listing 2-2 Creating a URL for an item in the app support directory”):

Wait a minute… We were just looking for the Application Support directory. Why are we possibly getting multiple versions of it?! Huh?

The example code tells you that if any directories are found, there may be several. While this seems obtuse and confusing, it is a result of providing cross-portability with macOS. I’ll expand on the topic of providing a unified API for macOS and iOS, something Apple’s been talking about for awhile, later on in this tutorial.

Notice that the Objective-C version of the shared NSFileManager has a URLsForDirectory:inDomains: method. It corresponds one-to-one to the Swift FileManager method for urls(for:in:).

The Objective-C NSSearchPathDirectory enumeration’s case for NSApplicationSupportDirectory corresponds to the Swift FileManager.SearchPathDirectory enumeration’s case for applicationSupportDirectory.

It should more than obvious to you by now that you ask for the NSURL or URL to the “standard directory” of interest — Documents/, Library/, tmp/ — by specifying an instance of the NSSearchPathDirectory or FileManager.SearchPathDirectory, when using Objective-C or Swift, respectively.

What’s this “domain” stuff?

The discussion and sample code shown in Apple’s documentation doesn’t say much about the inDomains parameter in the URLsForDirectory:inDomains: method, and certainly doesn’t mention the Swift equivalent of the in parameter in the urls(for:in:) method. This is an example of a situation where you have to be a good reader and researcher.

While Apple’s documentation goes into great depth for macOS as it pertains to “Domains Determine the Placement of Files”, there’s about zero discussion of considering the same topic for iOS. Perhaps this is the key phrase you want to consider:

The user domain contains resources specific to the users who log in to the system. Although it technically encompasses all users, this domain reflects only the home directory of the current user at runtime. User home directories can reside on the computer’s boot volume (in the /Users directory) or on a network volume. Each user (regardless of privileges) has access to and control over the files in his or her own home directory.

Think about it. A device like an iPhone or iPad is generally considered to be dedicated to one person (one user). These devices can only have one Apple ID logged in at a time. Thus, we can be confident that, on iOS, we’ll always be using the NSUserDomainMask constant for the inDomains argument when calling the URLsForDirectory:inDomains: method in Objective-C, and using the .userDomainMask constant for the in argument when calling the urls(for:in:) method in Swift.

Why iterate through multiple URL objects to find a “standard directory?”

Why do iOS methods like URLsForDirectory:inDomains: return an array of NSURL objects when most of us developers are just looking for one NSURL? (Most of us are now using Swift, so we’re looking for one URL.) The stated objective of the method is that it “Returns an array of URLs for the specified common directory in the requested domains” and the signature or declaration of the Objective-C method is:

The Swift equivalent is:

The documentation for method’s “Return Value” continues, stating, “The directories are ordered according to the order of the domain mask constants, with items in the user domain first and items in the system domain last.”

Compare this to my Swift code for retrieving the “standard” Documents directory. I point to an example of getting the Documents directory because, from my experience, when first installing and using an iOS app, barring intervention on your part, Documents is likely to be the only directory created for you when you install your app, and thus the only one available to you initially.


The Apple Objective-C example code for “Locating Items in the Standard Directories” calls URLsForDirectory:inDomains: and gets an NSArray back. Notice the comment in Apple’s sample code, “Use the first directory (if multiple are returned),” which looks like this:

Whenever I call the Swift equivalent, urls(for:in:), I do the same thing and “Use the first directory (if multiple are returned),” as in my protocol-oriented version of the code:

You may ask, “Why the forced unwrapping of the call to get the first element of the array of URL objects?” Good question and here’s the answer.

I’m using first! because I’ve never encountered an app with no Documents directory. But, I know of no guarantee that everything will work perfectly during app development. What if something goes wrong during an app installation an no Documents directory is created for your app? The urls(for:in:) method could return what it promised, an instance of [URL], initialized, but possibly empty, so the first element of [URL] could be nil if the array is empty, by definition:

So now I’m taking a shortcut and forcibly unwrapping the optional first to cut down on the amount of code you have to read. You’ll see below that I error check for a nil value in first.

Legitimate reasons to iterate through multiple URL objects to find a “standard directory”

Remember I mentioned cross-portability between iOS and macOS? If I build an Xcode project targeting macOS and using the Cocoa App template, I can compile and run the following Swift code on my MacBook Pro:

In this case, I do get an array of multiple directories, like so:

I printed the path components of the URL objects returned so I could copy the output from my Xcode console and paste into Terminal and prove to myself that I could cd (change directory) into both the /Users and /Network/Users directories.

This example highlights the usage of domains. You may have multiple domains on a possibly multi-user device like a MacBook Pro. On an iOS device, you’re most likely confined solely to the .userDomainMask because of sandboxing, though this may change in future iOS versions. (EXCEPTION: There are apps sold through the Mac App Store that use sandboxing.)

I tried another macOS-based experiment. I wanted to see if I could write some Swift code to list all user home directories on my MacBook Pro. Here’s the code:

The code worked perfectly and here’s the Xcode console output:

HOW I SHOULD WRITE MY CODE

Given the discussion so far, do you think my current Swift code for getting an app’s Documents directory is robust? Let me remind you of the code I’m discussing:

While this will probably work 99% of the time, it doesn’t account for a scenario in which, for some unforeseen reason, the Documents directory doesn’t get created when an app is installed from the App Store — say, a scenario in which an iPhone crashes during app installation. What if somehow the Documents directory is deleted due to some strange circumstance(s) while the user is actually running the app, or perhaps due to an operating system upgrade.

After 30 years of developing software, I’ve seen pretty much everything.

To be really robust, I would add some error checking to my code. During any app operation which requires access to the Documents directory, or any other such similar sandboxed directory, I could check to see if my iOS file management code returns a bonafide URL or nil:

If I encounter nil in a file-centric app, I would report to the user via a UIAlertController.

OH THE PAIN OF IT ALL!

A year or so after Swift came out, I stumbled upon a method, url(for:in:appropriateFor:create:), that I consider a much cleaner iOS/macOS file system API method for getting a URL for a “standard directory” like Documents. I don’t know why I went so long without ever noticing this method. I don’t know why I went so long without ever noticing that someone else had noticed this method. I just did a web search and found almost no references to url(for:in:appropriateFor:create:). One of the only mentions I could find was in a technical/release note for iOS 8. url(for:in:appropriateFor:create:) is so much easier to understand and use because it returns one non-optional URL and doesn’t involve that nonsense about “Use the first directory (if multiple are returned).”

Let me first show you how I’m now retrieving each URL for the the iOS “standard directories:”

Here’s the declaration for the url(for:in:appropriateFor:create:) instance method of the FileManager class:

Notice that we’ve got a shouldCreate argument which, if set to true, determines “Whether to create the directory if it does not already exist.” Pretty nice.

Don’t worry about the argument label and argument appropriateFor url. Most developers will always leave this argument as nil because its use case will be relatively rare. I encourage you to read up on this method and start using it as soon as possible in your apps.

Hallelujah!

CONCLUSION

My main goal in writing this article was to convince you that you need to understand the development tools you’re using. You want to be more than a “programmer.” You want to be the best of the best; not only a a software developer, designer, and software engineer, but a bit of a researcher. The more you understand your tools and what’s going on “under the hood” of the proverbial facade covering your car engine, the better you’ll be at solving difficult problems and coming up with innovative designs for new products.

Trust me. You don’t want to be one of those developers who rely on copy and paste programming. I mean that you don’t want to be one of the hackers who look up a few keywords in a web search engine, find the code needed on sites like StackOverflow or some blog, copy it, paste it into an Xcode project, and beat on it until it works.

Of course you want — sometimes need — to do research and reach out for help from other developers and resources. But try first to figure out problems yourself by looking at your development environment’s documentation. In the case of iOS and macOS developers, that would mean turning first to the Apple Developer portal.

As you can see from this post, Apple’s documentation doesn’t always have the clearest and most up-to-date information. I have to turn to other resources to find answers to my questions. It’s nothing to be embarrassed about. But I went far beyond just copying and pasting code. I strove to understand the problem at hand and solve it myself.

Make understanding what you’re doing a top priority during development. Don’t make “rush-and-just-get-it-done” your mantra. I know there is a lot of deadline-related pressure out there in the software world, but the deeper you understand your methodologies and tools, the higher quality your code, the more you’ll be admired and sought-after as a go-to developer. There will always be the high-pressure type management personalities trying to push you around. As you grow as a developer, you’ll find more and more that you can avoid these kind of people. You’ll eventually find that you can say “no” to these “Type A” personalities or just get up and leave and find a better position with a different employer.

Author: Andrew Jaffee

Avid and well-published author, software engineer, designer, and developer, now specializing in iOS mobile app development in Objective-C and Swift, but with a strong background in C#, C++, .NET, JavaScript, HTML, CSS, jQuery, SQL Server, MySQL, Oracle, Agile, Test Driven Development, Git, Continuous Integration, Responsive Web Design, blah, blah, blah ... Did I miss any fad-based catch phrases? My brain avatar was kindly provided by http://icons8.com under a Creative Commons Attribution-NoDerivs 3.0 Unported license.

Leave a Reply

Your email address will not be published. Required fields are marked *