16

The Parse documentation for adding properties and methods on PFObject subclasses conveniently skips the Swift syntax in their sample code listing just the Objective-C syntax:

https://parse.com/docs/ios_guide#subclasses-properties/iOS

// Armor.h
@interface Armor : PFObject<PFSubclassing>
+ (NSString *)parseClassName;
@property (retain) NSString *displayName;
@end

// Armor.m
@dynamic displayName;

Has anyone figured out a work around for Swift's lack of dynamic synthesizers in order to implement properties and methods with PFSubclassing? I want to be able to do something like:

class Armor : PFObject, PFSubclassing {
    class func parseClassName() -> String! {
        return "Armor"
    }
}

var armor = Armor()
armor.displayName = "Iron Clad"
nhgrif
  • 61,578
  • 25
  • 134
  • 173
user3806775
  • 169
  • 1
  • 3
  • 5
    By "conveniently skip", do you mean "not yet added since you can only use the language in a beta IDE"? – nhgrif Jul 05 '14 at 00:48
  • All you need to do is add `var displayName = "default"` in your class and you should be done... (also, no reason to make parseClassName return a `String!` - `String` is perfectly sufficient. – Grimxn Jul 05 '14 at 07:56

5 Answers5

22

I had this same problem. Had to add @NSManaged to the properties I wanted saved:

class Armor : PFObject, PFSubclassing {
    @NSManaged var displayName: String

    class func parseClassName() -> String! {
        return "Armor"
    }
}

var armor = Armor.object()
armor.displayName = "Iron Clad"

Hopefully this is resolved in the next update.

Prototypic
  • 220
  • 1
  • 4
  • 1
    Seems like a good temporary solution. The Swift docs say: `Like the @dynamic attribute in Objective-C, the @NSManaged attribute informs the Swift compiler that the storage and implementation of a property will be provided at runtime. However, unlike @dynamic, the @NSManaged attribute is available only for Core Data support.` Hopefully Parse releases an official solution soon. – Arman Aug 10 '14 at 22:25
16

The solution is to use computed properties instead of stored properties:

class Armor : PFObject, PFSubclassing {

    var displayName: String? {
        get {
            return self["displayName"] as? String
        }
        set {
            self["displayName"] = newValue
        }
    }


    class func parseClassName() -> String {
        return "Armor"
    }
}
Andrew Toth
  • 419
  • 4
  • 9
  • 2
    OP needs to accept this as the answer, as it worked for me as well. Thanks to this answer, I didn't have to waste a couple of hours figuring this out. Parse also needs to update their documentation and not rely on SO to document their approach to doing things in swift. – jpittman Sep 26 '14 at 18:15
  • 1
    You can still use the subscript syntax :) `self["displayName"] = newValue` – wfbarksdale Oct 21 '14 at 08:43
  • I needed to add this, override class func load() { self.registerSubclass() } in the class, and in my bridging header add #import – DogCoffee Mar 09 '15 at 22:27
  • I needed to add this, override class func load() { self.registerSubclass() } in the class, and in my bridging header add #import works perfect. – DogCoffee Mar 09 '15 at 22:27
  • Should `displayName` be of type `String?` since `objectForKey(..)` may return nil? – user Apr 20 '15 at 00:03
  • Edited with all your suggestions – Andrew Toth Apr 25 '15 at 20:55
  • So, someone founds any problem with this implementation? Because Parse documentation gives another way. – Damasio Dec 02 '15 at 12:54
6

Swift 1.2

First: Create a swift file and define the subclass. Don't forget to import Parse! (for example Armor)

import Foundation
import Parse


class Armor: PFObject, PFSubclassing {

    // MARK: - PFSubclassing

    override class func initialize() {
        struct Static {
            static var onceToken: dispatch_once_t = 0;
        }
        dispatch_once(&Static.onceToken) {
            self.registerSubclass()
        }
    }

    class func parseClassName() -> String {
        return "Armor"
    }


    // MARK: - Parse Core Properties

    @NSManaged var displayName: String?

}

Note: You can define your properties as optionals. Every "undefined" value inside the Parse Core Manager will be translated to "nil".

Second: Register all subclasses, inside your AppDelegate.swift.

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

    // MARK: - Parse Setup
    Parse.setApplicationId("YOUR_ID", clientKey: "YOUR_KEY")

    // MARK: - Parse Register Subclasses
    Armor.registerSubclass()

    return true
}
user3378170
  • 2,371
  • 22
  • 11
  • This conforms with the Parse documentation. Would you know if there is any side effect of using @Andrew Toth anshwer? – Damasio Dec 02 '15 at 12:58
  • If you register in AppDelegate, you can remove the static init register code. In my experience the static init code - even if it's in the official documentation - caused problems. So I would recommend explicitly registering subclasses in AppDelegate. – n13 Oct 07 '16 at 06:24
  • Do you remember what sort of problems you encountered? I just found this thread, trying to solve a problem because my program kept locking up somewhere in the middle of loginWithUsernameInBackground(). If I commented out the initialize() method that *seems* to have fixed the problem. – Michael Rogers Oct 19 '16 at 01:59
4

Prototypic is correct. As of right now it won't work with the dynamic attribute, but the code sample doesn't include the registerSubclass Parse recommends, so the code should include it like this:

class Armor : PFObject, PFSubclassing {
    @NSManaged var displayName: String

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

var armor = Armor.object()
armor.displayName = "Iron Clad"

(Note that "override" is required but missing from the Parse documentation.)

Timothy Tripp
  • 3,277
  • 1
  • 14
  • 8
1

I ran into the same issue, though I should note that your class definition should look more like this:

class Armor : PFObject, PFSubclassing {
    var displayName: String

    class func parseClassName() -> String! {
        return "Armor"
    }
}

var armor = Armor()
armor.displayName = "Iron Clad"

I tried a few different things without success. It doesn't look like the Parse SDK supports this yet, keep in mind that the current release of the Parse iOS SDK predates the swift announcement. Sounds like they're working on better swift support for an upcoming release though.

Instead, you can still create PFObject subclasses (as you did) and access data with .getObjectForKey("displayName"), or write your own class methods to access this data. The biggest missing piece is really just some convenience methods that the SDK usually creates for you.

Nick
  • 9,792
  • 7
  • 50
  • 60