algal
algal

Reputation: 28094

How to retrofit Parse’s PFObject to an existing, complex, aggregate model class?

I have an existing model class which I would like to manage with Parse, ideally by changing its superclass from NSObject to PFObject. But this is a real-world model class, designed without Parse in mind, so it presents a number of challenges.

challenges

The challenges are:

  1. This class is in Swift.
  2. This class has stored properties and computed properties. (So Parse should only manage the stored properties, of course.)
  3. Some properties hold arrays of instances of other custom model classes
  4. Some of those custom classes themselves hold User objects, which should be managed by Parse.

Every one of these things seems to require special treatment to be handled correctly in Parse, and Parse’s documentation is brief on these issues.

existing model class hierarchy

To give an example, my class is something like this:

// I’d like to change this to be a PFUser subclass.
class User : NSObject { /* an ordinary user object  */ }

// And this to be a PFObject subclass
class Tango : NSObject {
  var user:User  // Presumably, a Parse "Pointer" to a PFUser
  var opponent:Participant
  var info:NSString?
  var watchers:NSArray // an array of Participants
  var duration:NSTimeInterval? // (notice this is an optional value type, in accessible from Objc)

   // various methods
}

// a custom class which contains a User
class Participant : NSObject
{
  var status:String
  var user:User // a Pointer to a PFUser?
  // etc..
}

key questions

So I have a few key questions here:

  1. Should all references to User be replaced with Pointers to User a subclass of PFUser?
  2. Do I need to replace Participant with a plain old Cocoa type like NSDictionary? Or is there a way to provide interconversion with NSDictionary only for the purposes of Parse’s internal serialization?
  3. Is there any provision for swift-only types like the Optional<NSTimeInterval> ? Or is this another case where there’s an extension point to provide inter-conversion with a data type Parse can handle?
  4. How to model the array of Participants? the User objects within the Participants?
  5. Given all these apparent difficulties with Tango, would I be wiser just to implement a method for converting an instance to/from plain old plist types (dictionaries, arrays, strings, numbers), and have a PFObject handle those?

I am not interested in maximal query flexibility or maximal performance. I am interested in the quickest thing which can work reliably, and which requires the minimum re-writing of existing code that depends on the existing class hierarchy. I am aware that the User / Tango relationship could be described as many-to-many, which is what PFRelation is for, but I have the impression it would require a major redesign to use it.

In other words, I am really hoping I can avoid replacing my entire model layer with a new class hierarchy designed as denormalized tables for for Parse's pleasure. I have existing code which works with the current structure, and I am trying to fit in Parse without breaking anything already there.

So are these key questions the right ones to retrofit this class for Parse? What is the right way to handle them?

Upvotes: 2

Views: 1211

Answers (3)

stone
stone

Reputation: 8662

My answer: Don't! It doesn't make sense to make your model classes dependent on a third-party backend provider.

Making model classes subclass Parse classes is a brittle design. If you want to switch from Parse to something else, you'll have to change your model classes again!

I'm looking at using Parse as a backend and I'm not planning to subclass. Instead I'll define a Data Access Layer protocol, with a Parse-based implementation, for getting data from/ sending data to the backend. That layer will handle converting things from PFObjects et cetera to my model objects and vice versa storing data in, and retrieving data from, Parse.

UPDATE: I asked one of the Parse iOS SDK developers about this and he suggested using the Parse REST API for this approach, since creating and destroying PFObjects is memory-intensive. He also pointed out that switching backend providers is pretty rare - which is true in my experience.

I suppose this removes some of the benefit of using Parse, but that's a tradeoff I'm willing to make. It still allows me to avoid writing server code. In your case this design might be less work since it will isolate the changes.

Upvotes: 1

Thomas Bouldin
Thomas Bouldin

Reputation: 3725

You should definitely check out the subclassing guide. A few things to note that are particularly important for your use case:

  1. If you subclass a built-in class (e.g. User : PFUser) then you only need to register that subclass. All the constructor methods return instancetype so you can call them on User and you'll get an actual User back. Similarly, any query for a Parse user class will return results deserialized as your type.
  2. If you subclass PFObject for your own types, you must also implement +parseClassName. This maps your class to the name you want to see in the data browser. This name must be consistent across all languages to make sure you're talking to the same data.
  3. The PFObject subclassing library was written to only implement @dynamic properties. If you @static it or define the method then PFObject leaves it alone. If you have an ivar with the same name, the property isn't saved to Parse, but we will instead create an accessor/mutator pair that respects the PFObject's internal lock that we take when merging in server data or preparing a save operation.
  4. PFObject can handle properties of any JSON serializable primitive, pointers to other PFObject subtypes, arrays/dictionaries thereof, and PFRelation pointers. I haven't played with Swift yet, so I don't know how it's going to interact with optional properties; I assume they're NSNumbers under the covers and PFObject handles NSNumbers as well as primitives (which are auto-boxed/unboxed).
  5. [Bonus] To make your subclasses self-contained you can add the following to their definition: // Note: +initialize is Objective-C's only non-polymorphic function; every class // must implement this directly. +(void) initialize { [self registerSubclass]; }

Upvotes: 2

weePee
weePee

Reputation: 905

There is a similar question on Google Groups, although I must say your question is more complex. We know that in Swift you have to declare you properties as @NSManaged public var. Step two would be to figure out if you can setup relationships with the built-in Users (PFUsers). Please give us your complete solution when you find the answers to all your questions.

See the following link for step 1 : Here

Additional Info: I have constructed my own PFObject. I have found that when I call saveInBackgroundWithBlock the block is never excecuted, although the object is actually save sucessfully. My PFObject FYI

public class PatientRemote:  PFObject, PFSubclassing {

    public override class func load() {
        self.registerSubclass()
    }
  public  class func parseClassName() -> String! {
        return "Patient"
    }

    @NSManaged public var firstname : NSString
    @NSManaged public var lastname : NSString
    @NSManaged public var birth : NSDate
    @NSManaged public var idnumber : NSString
    @NSManaged public var gender : NSString
    @NSManaged public var fileNumber : NSString
    @NSManaged public var patientPicture : NSString
    @NSManaged public var medicalAid: NSString
    @NSManaged public var option: NSString
    @NSManaged public var mainMember: NSString
    @NSManaged public var membershipNo: NSString
    @NSManaged public var dependant: NSString
    @NSManaged public var address1: NSString
    @NSManaged public var address2: NSString
    @NSManaged public var address3: NSString
    @NSManaged public var address4: NSString
    @NSManaged public var postalCode: NSString
    @NSManaged public var workNumber: NSString
    @NSManaged public var cellNumber: NSString
    @NSManaged public var localId: NSString        
}

Upvotes: 1

Related Questions