The Sandbox L5.2.1

When an app is installed iOS creates a sandbox of several pre made containers which only that app can access. It's composition looks like the following.

sandbox composition

Bundle Container

This container includes the application itself, specifically a directory that includes the executable code and the resources like images and sounds files or whatever else the app might use.

Data Container

This container holds all the user in app data. Within it are three main subdirectories - Documents, Library, and Temp.

  • Temp - used to store very short lived data that does not need to be persisted across launches.

  • Documents - (important stuff) this is where user data should go. iTunes will makes copies of this directory and iOS will never delete any of it's contents.

  • Library - this is where any non-user data, or files that you don't want exposed to the user is stored. This is also the place where NSUserData is lives. It's path looks something like this Library/Preferences/info.myapp.mobile.plist

    • The Caches subdirectory lives within the Library directory. This is where things that won't be necessary in the future or are easy to recreate live. iTunes and iCloud will never make a backup its content.
    • The Tmp subdirectory also lives in the sandbox and is very similar to Caches directory. The main difference is when they are deleted. Tmp will be deleted more often under normal circumstances, but for the most part we can consider them equivalent.

Both Documents and Library both contain a few standard subdirectories, but we also have the ability to create custom subdirectories to better organize our files.

documents and library subdirectories

At run time the app might request access to additional containers such as the iCloud Container.

Saving Content

In order to save something to the sandbox we need to do two things.

  • Find where the folder is within the sandbox
  • Write to the folder

To achieve that we can use a couple different tools

  • FileManager - to get the path to the folder within the sandbox
  • String - to write or read text files
  • Data - to write or read binary files

Example Writing & Reading From The Sandbox

Within the following example we create an instance of FileManager. This gives us the functionality to find folders, copy files, remove files etc. We'll use it to get a path to our Documents folder within our sandbox.

FileManager will return an array of urls, which makes sense because there are actually more than one Documents folder - one for each app installed. This class method in Mark 1 works by returning all of them, which we will filter by adding the second parameter to fm.urls(for: in:) in Mark 2

We now have an array with just one url which we'll pop off in Mark 3. This is a url to the Documents folder but we want a url to the new file we are going to create so we append the file name at the end of the url.

At this point we're ready to save some data to the file. We could use String or Data, both work in similar ways but we'll use String to write to our file starting with the do block of code on Mark 4. Then we read back the data we just saved staring on Mark 5 just to make sure all went as planned.

Below is the base concept - not a very practical way to save or retrieve any significant amount of data containing several objects, versions of the data, or displaying it in views. But there are frameworks like Core Data that build on top of this concept to handle more complex interactions.

func sandboxPlayground() {  
    let fm = FileManager.default // Mark 1
    let urls = fm.urls(for: .documentDirectory, in: .userDomainMask) // Mark 2
    let url = urls.last?.appendingPathComponent("file.txt") // Mark 3

    do { // Mark 4
        try "Hi There!".write(to: url!, atomically: true, encoding: String.Encoding.utf8)
    } catch {
        print("Error while writing")
    }

    do { // Mark 5
        let content = try String(contentsOf: url!, encoding: String.Encoding.utf8)

        if content == "Hi There!" {
            print("yay")
        } else {
            print("oops")
        }
    } catch {
        print("Something went wrong")
    }
}

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {  
    // Override point for customization after application launch.

    sandboxPlayground()
    return true
}