iOS Programming · · 10 min read

Advanced Parse Tips: PFObject Subclassing, Caching, Access Control and User Signup

Advanced Parse Tips: PFObject Subclassing, Caching, Access Control and User Signup

Welcome to the advanced Parse article! In this tutorial, we will be covering some advanced Parse techniques which you can use in your own apps to add new features.

We will cover the following:

  • Native PFObject subclasses
  • Efficient caching
  • Access control
  • User signup / login

We’ll discuss each technique individually. But before we move on, let me make it clear that this article assumes you have a basic understanding of Parse and how to create a new app, etc. If you need to brush up on your Parse basics, read about the introductory tutorial of Parse and learn how to host your own instance of Parse Server. Needless to mention, a working knowledge of the Swift language is also assumed. If you need to learn more about Swift, visit the Apple Developer website or check out our tutorials here.

Subclassing PFObject

When creating apps, we write a lot of code. Often times, we end up writing more code than we were originally expecting. When this is the case, as it usually is, every line counts. The goal is to make our code as concise as possible, without sacrificing its clarity and readability. Take, for example, a situation where we have a PFObject that represents an image in our app. This object contains a PFFile, which must be downloaded separately. The object is fetched in a query, and then the file associated with it is downloaded. Next, this file needs to be converted to a UIImage, which is then displayed within the app. This ends up being quite a lengthy process! Without properly subclassing PFObject, our code may look like this:

func someFunction() {
        let query = PFQuery(className: "MyClass")
        query.whereKey("identifier", equalTo: identifier)
        query.getFirstObjectInBackgroundWithBlock { (object, error) in
            if let _ = error { 
              // error handling 
            } else if let object = object, image = object["image"] as? PFFile {
                image.getDataInBackgroundWithBlock {(data, error) in
                    if let _ = error { 
                       // error handling
                    } else if let data = data, image = UIImage(data: data) {
                        myImageView?.image = image
                    }
                }
            }
        }
     }

Looking at the above code, there are two obvious issues. First of all, it’s really long. It’s nested several levels deep. Also, its intent is rather unclear. Someone, new to the code, may not immediately realize what exactly it does. There are also a few potential errors which may not be immediately obvious:

  • Line #2: when we initialize the query object, we need to provide a className string. It’s very easy to mistype the class name and end up with an invalid query, which we may spend a lot of time debugging. For example, it’s very easy to type MyXlass or myClass instead of MyClass.

  • Line #3: we may experience the same issue when we add constraints to the query. It’s very easy to mistype a key name.

  • Line #7: the same error again! When we access query["fileKey"], we can easily mistype fileKey and instead type something like fileKeu or FileKey.

All these issues are simple typographical errors. In most cases, they won’t even crash! We’ll just be left wondering why our code doesn’t work the way we expect it to. Surely, there’s a better way to avoid these issues.

By subclassing PFObject, we can extend and manipulate its functionality to better suit our needs. Let’s get started by taking a look at what our final solution could be:

MyClass.fetchImage(identifier: identifier) { (error, image) in
    if let _ = error { 
            // error handling
    } else if let image = image {
        myImageView?.image = image
    }
}

This is a huge improvement over the previous version! There is no room for any typing errors. Apparently, the code is much more clean and concise. Even a programmer without much knowledge about Parse can easily tell exactly what the code does.

Okay, now let’s talk about how it was done.

Parse includes support within its iOS SDK for creating subclasses of PFObject, allowing us to use the same object oriented behavior we’ve come to know and love with the Parse SDK. Although subclassing PFObject isn’t as simple as class MyClass: PFObject { }, it’s still very easy!

To start, add #import<Parse/PFSubclassing.h> to the Swift bridging header in your Xcode project. This imports the PFSubclassing protocol, which is required to create a PFObject subclass.

Next, go ahead and add a new file to your Xcode project, by pressing command + N. Choose empty Swift file and press next. Name it “MyClass”. Once the file has been created, add the following to it:

import Parse

class MyClass: PFObject { }

extension MyClass: PFSubclassing {
    static func parseClassName() -> String {
        return "MyClass"
    }
}

Almost done! Next, head over to your project’s app delegate, which is usually AppDelegate.swift. Update it to add the following:

func configureParse() {
    MyClass.registerSubclass()
}

Once you’ve added the above method, add a call to it within application(didFinishLaunchingWithOptions launchOptions:). Your app delegate now looks like this:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        //everything else you had in this method

        configureParse()

        return true
}

func configureParse() {
    MyClass.registerSubclass()
}

Believe it or not, that’s it! Your PFObject subclass is now ready to go! Let’s start by learning a few basics. When you subclass PFObject, a little extra work is required to add your own properties. When declaring additional properties in a PFObject subclass, you need to make them NSManaged so that the Parse SDK can read them properly. Let’s expand MyClass to add some custom properties:

import Parse

class MyClass: PFObject {
    @NSManaged var identifier: String?
    @NSManaged var image: PFFile?
}

extension MyClass: PFSubclassing {
    static func parseClassName() -> String {
        return "myClass"
    }
}

Just remember to make your properties NSManaged by writing @NSManaged var instead of var. In this case, we added two custom keys to MyClass. One of them is identifier and it’s of type String. The other is image, which is a PFFile. These properties can still be accessed using valueForKey() and subscripts like this:

let x = MyClass()
x["identifier"] //returns x.identifier
x["image"] //returns x.image

These changes make our code much more readable and concise. We can easily access the values stored in any instance of MyClass, and a cast from AnyObject to another data type is unnecessary. With PFObject subclassing, this actually solves two problems:

  1. We no longer have to worry about mistyping key names anymore. It’s not impossible to mistype a key name when accessing a property, because the compiler will complain that the property doesn’t exist.

  2. We no longer have to worry about casting values returned by valueForKey(_:) and subscripts anymore. For example, when we access x.image, we don’t need to cast it from AnyObject to PFFile. This avoids a lot of confusion. We also don’t need to worry about keeping track of what type each key in our object is.

Both of these changes make our code much easier to read, write, and maintain. Let’s talk about adding custom functionality to PFObject. This is easy. Inside of your custom PFObject subclass, just declare as many additional methods as you want! Let’s clean up our code for fetching an image.

import Parse

class MyClass: PFObject {
    @NSManaged var identifier: String?
    @NSManaged var image: PFFile?
}

extension MyClass {
    static func fetchImage(identifier identifier: String, completion: ((error: NSError?, image: UIImage))?) {
        let query = MyClass.query()
        query.whereKey("identifier", equalTo: identifier)
        query.getFirstObjectInBackgroundWithBlock {(object, error) in
            if let error = error { 
                            completion?(error: error, image: nil)
            } else if let object = object as? MyClass, image = object.image {
                image.getDataInBackgroundWithBlock {(data, error) in
                    if let error = error { completion?(error: error, image: nil) }
                    else if let data = data, image = UIImage(data: data) { completion?(error: nil, image: image) }
                }
            }
        }
    }
}

extension MyClass: PFSubclassing {
    static func parseClassName() -> String {
        return "myClass"
    }
}

Essentially, we just moved our code from its original location to inside MyClass. If you look closely, you’ll notice it’s essentially the same code, but in a different location. The real benefit comes from the fact that the method we just wrote is much more concise inside of a view controller, which was most likely its original location. Also, it helps us follow the MVC paradigm by keeping code related to our data model inside our data model. By writing this code inside the fetchImage() method, we can document the method, which makes its intent very clear. On top of this, it’s very unlikely that you use model code just once in your app. Most likely, we’ll need to use this method multiple times in multiple locations. By subclassing PFObject, we can avoid code duplication and create a very concise, documentable method instead of a long, meaningless block of code. Subclassing PFObject opens doors to many more of Parse’s features, which we’ll talk about next.

Efficient Caching

A lot of Parse’s users don’t take advantage of the awesome caching functionality Parse provides for free. In essence, caching stores information on the device’s disk when you run a query. This information can be used to greatly accelerate future queries, as they can be configured to load data directly from the disk, and then from the network. This makes your app run much, much faster. We’re going to learn about caching, how to use it, and when to use it.

Every time you run a Parse query, all of the returned results are stored on the device’s disk as they’re returned to you. Using this data wisely can provide tremendous increase to the speed of your app. The key is to rely on the cache information that won’t change much, or won’t change at all. Let’s look into this.

let query = PFQuery(className: "MyClass")
//constraints
query.cachePolicy = .NetworkOnly

In this example, we’ve configured the query as a network only query. This provides the exact same behavior as Parse’s default queries. However, we have several other options:

  • .IgnoreCache: This cache policy does two things. First, it tells the query to only load data from the network. Second, it doesn’t write data to the cache. Use this when you want to disable caching on a particular query.
  • .CacheOnly: This cache policy instructs the query to only load data from the cache. The network is ignored. Use this when you know that data is immutable. In other words, use this cache policy when you know, for a fact, that the data you’re loading hasn’t changed since the last time it was queried. Note that if there’s no cache available, this query will return an error.
  • .NetworkOnly: This cache policy is similar to .IgnoreCache in that it only loads data from the network. However, it writes data to the cache. Use this when you’re loading data that changes very frequently.
  • .CacheElseNetwork: This cache policy loads data from the cache. If this data is unavailable, it loads it from the network. Use this cache policy when you want to use .CacheOnly but would like a fallback if the cache isn’t available.
  • .NetworkElseCache: This cache policy attempts to load data from the network, but loads it from the cache if the network couldn’t be reached. Use this when you’re loading data that refreshes frequently, but you want to have a fallback in case the network is unavailable.
  • .CacheThenNetwork: This cache policy first loads data from the cache, but then loads it from the network as soon as possible. Use this when you want to provide a fast user experience and don’t mind displaying slightly outdated data until the network query is completed.

The key to Parse caching is knowing when to use it. For example, it’s safe to cache a user’s friends in a social networking app, because the user’s friends won’t change very frequently. It’s not, however, safe to cache a list of messages that a user has received, because these change very frequently. It’s best to display data like this using a cache policy like .NetworkElseCache just in case the network isn’t available.

Access Control

Some of the apps we make may store sensitive information. It’s best to protect such information. Even if your app doesn’t store any sensitive data, security is always a good thing. Parse makes this very easy with Access Control Lists, or ACLs. ACLs on Parse are powered by the PFACL class. PFRole can be used in conjunction with ACLs to provide a simple, easy to use way to secure data. Let’s take a look at an example:

let object = PFObject(className: "MyClass")
object["myKey"] = "myValue"

let acl = PFACL()

if let user = PFUser.currentUser() {
    acl.publicReadAccess = false
    acl.publicWriteAccess = false

    acl.setReadAccess(true, forUser: user)
    acl.setWriteAccess(true, forUser: user)

    acl.setReadAccess(true, forRoleWithName: "admin")
}

object.ACL = acl

//save your object as usual

Not bad, right? In this example, we added an ACL to object which specified that only the current user can modify object. We also said that the only users allowed to read object are the current user and those with the “admin” role. Let’s talk about what the ACL we just added did. First, it restricted write access for object to user. This means that nobody can make any changes to object except for user. We restricted read access to user and all users with the “admin” role. This means that only the people who have read access will see object in a query.

Using access control is very easy and it allows us to provide excellent security in our apps. Just remember to make sure that you configure your ACLs properly!

User Signup / Login

One of the things that Parse is very good at is authentication. Parse provides a class called PFUser, which is a subclass of PFObject, for managing and authenticating users in your app. PFUser provides quite a bit of powerful functionality which can be used to create robust applications that have support for multiple users. Creating a new user is easy! Let’s see how:

let user = PFUser()
user.username = "user"
user.password = "pass"
user.signUpInBackgroundWithBlock {(success, error) in
    if let _ = error { 
            // error handling
    } else { }
}

And that’s it! Just remember that it’s very important to make sure that you have username and password set before signing a user up. Logging in is similar:

PFUser.logInWithUsernameInBackground("", password: "") {(user, error) in
    if let _ = error { 
            // error handling
    } else if let user = user { }
}

To log out, just do this:

PFUser.logOutInBackgroundWithBlock { (error) in
    if let _ = error { 
            // error handling 
     }
}

And there you have it. Before concluding this article, we’re going to talk about one last thing. If a user isn’t signed into your app, you most likely want to show them a view controller for signing up or logging in. If they’re not, you probably want to take them straight to the main part of your app.

Let’s head back to AppDelegate.swift again. Add a new method, called configureRootViewController(). Then, add a call to it in application(didFinishLaunchingWithOptions: ). Your app delegate should now contain the following:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Everything else you had in this method

        configureParse()
        configureRootViewController()

        return true
}

func configureParse() {
    MyClass.registerSubclass()
}

func configureRootViewController() {

}

As you probably guessed, our code for checking whether or not the user is logged in will go inside configureRootViewController(). PFUser has a method called currentUser() which returns the currently signed in user (if anyone is signed in) or nil if no user is logged in. Here’s how to use that method:

func configureRootViewController() {
        let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()) //replace "Main" with the name of your Storyboard.

        if let _ = User.currentUser() {
            window?.rootViewController = storyboard.instantiateViewControllerWithIdentifier("main") //replace "main" with the identifier of the view controller you want to show to a logged in user
        }

        else {
            window?.rootViewController = storyboard.instantiateViewControllerWithIdentifier("splashScreen") //replace "splashScreen" with the identifier of the view controller you want to use as a fallback
        }
    }

Summary

In this article, you learned a lot about some of Parse’s more advanced features! We covered subclassing, caching, access control, and users. Hopefully, this knowledge will help you add some of Parse’s cooler features to your own apps.

Read next