Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating objects with init(withoutDataWithObjectId:) causes crash in SwiftUI preview #1679

Open
4 tasks done
drbarto opened this issue Oct 28, 2022 · 4 comments
Open
4 tasks done
Labels
bounty:$20 Bounty applies for fixing this issue (Parse Bounty Program) type:bug Impaired feature or lacking behavior that is likely assumed

Comments

@drbarto
Copy link

drbarto commented Oct 28, 2022

New Issue Checklist

Issue Description

In a Swift project using the Parse pod, I create a bunch of dummy objects using init(withoutDataWithObjectId:) for use in tests and SwiftUI previews. When running the app or unit tests, these objects can be created without problem; however, when trying to create them in the context of a SwiftUI preview, the process crashes.

Steps to reproduce

To give an example, e.g. I have classes User and Project defined as follows:

class User: PFUser {
  @NSManaged var firstName: String?
  @NSManaged var lastName: String?
}

class Project: PFObject, PFSubclassing {
  class func parseClassName() -> String { "Project" }
  @NSManaged var name: String?
}

Sample code for creating dummy objects:

let user = User(withoutDataWithObjectId: "user_1")
user.firstName = "..."
user.lastName = "..."
user.email = "[email protected]"

let project = Project(withoutDataWithObjectId: "project_1")
project.name = "..."

Finally I want to provide the dummy objects to my UI code, so that it can be tested and previewed without a server connection:

struct UsersView_Previews: PreviewProvider {
  static var previews: some View {
    SomeFancyView(user: user, project: project)
  }
}

Actual Outcome

Executing the above "dummy object creation" code works fine when running the app or the unit tests.
However, when executing it in the context of the Xcode SwiftUI preview window, it crashes with these errors:

When executing User(withoutDataWithObjectId: "user_1"):

MyApp crashed due to an uncaught exception NSInvalidArgumentException.
Reason: Invalid class name. Class names cannot start with an underscore.

When executing project.name = "...":

MyApp crashed due to an uncaught exception NSInvalidArgumentException.
Reason: -[PFObject setName:]: unrecognized selector sent to instance 0x600001c74480.

I can avoid the 2nd issue with setName by replacing the call with project.setValue("...", forKey: "name").
However, I found no workaround for the 1st crash related to the classname.

Expected Outcome

The dummy objects can be created without crashing the process.

Environment

Client

  • Parse ObjC SDK version 1.19.4 (Cocoapods / Xcode 13.4.1)

Server

  • does not apply

Database

  • does not apply

Logs

none

@parse-github-assistant
Copy link

parse-github-assistant bot commented Oct 28, 2022

Thanks for opening this issue!

  • 🚀 You can help us to fix this issue faster by opening a pull request with a failing test. See our Contribution Guide for how to make a pull request, or read our New Contributor's Guide if this is your first time contributing.

@mtrezza mtrezza added type:bug Impaired feature or lacking behavior that is likely assumed bounty:$20 Bounty applies for fixing this issue (Parse Bounty Program) labels Oct 29, 2022
@eniok
Copy link

eniok commented Nov 11, 2022

Tested and confirmed it works in the SwiftUI Preview with the code below:

import SwiftUI
import Parse

class User: PFUser {
  @NSManaged var firstName: String?
  @NSManaged var lastName: String?
}

class Project: PFObject, PFSubclassing {
  class func parseClassName() -> String { "Project" }
  @NSManaged var name: String?
}

struct TestView: View {
    @State var user: User
    @State var project: Project
    
    var body: some View {
        VStack {
            Text(user.firstName ?? "Null")
            Text(user.lastName ?? "Null")
        }
    }
}

struct TestView_Previews: PreviewProvider {
    
    static var previews: some View {
        TestView(user: DataClass().user, project: DataClass().project)
    }
}

class DataClass {
    var user: User
    var project: Project
    
    init() {
        user = User(withoutDataWithObjectId: "user_1")
        user.firstName = "..."
        user.lastName = "..."
        user.email = "[email protected]"
        
        project = Project(withoutDataWithObjectId: "project_1")
        project.name = "..."
    }
}

It may be the case that the variables in your classes (User & Project) are declared as optionals therefore in breaks in the view when referenced.

  • Parse 1.19.4
  • Xcode 14.1

@drbarto
Copy link
Author

drbarto commented Nov 13, 2022

Hey @eniok,

thanks a lot for taking the time and testing this yourself!

To emulate your test environment, I upgraded to macOS 13.0.1 and Xcode 14.1 and tried out your example in a new, empty Xcode App project; I just added Cocoapods with the Parse pod, then added TestView with your code.

Unfortunately I still get crashes in the SwiftUI preview, for the same reasons as before. Since the code worked for you, there must be a difference in our setup. Would you mind sharing details about yours? I'm running on a MacBook Pro M1 Max with macOS 13.0.1, using Cocoapods 1.11.3 and Parse 1.19.4. (The M1 is my main suspect...)


I will list the runtime errors and workarounds again, with more detail:

  1. When using the User class, the preview crashes with Invalid class name. Class names cannot start with an underscore. As a workaround I could make the class name explicit by overriding User.parseClassName, which would work in the preview but crashes the app when launching it normally. (For now I settled with conditionally returning super.parseClassName() by default, and returning "User" only when in the context of a preview).

  2. The member access still fails: when setting properties in DataClass I get [PFObject setFirstName:]: unrecognized selector sent to instance; when reading values in SwiftUI I get [PFObject firstName]: unrecognized selector sent to instance. A possible workaround is to avoid the generated type-save members and use raw value access instead:

// When setting properties, this fails...
user.firstName = "..."
// ... and this works:
user.setValue("...", forKey: "firstName")`

// When reading properties in SwiftUI, this fails...
Text(user.firstName ?? "Null")
// ... and this works:
Text(user.value(forKey: "firstName") as? String ?? "Null")

Of course this is just a theoretical solution, in practice this is not usable -- it would mean to give up type safety when showing model values inside SwiftUI templates.


P.S. I my main project where the problem was first encountered, I could not run any previews since the upgrade to Xcode 14, even if no Parse classes were involved in the preview. The reason is that Xcode executes some parts of the main app when initiating a preview, including the hooks which I use to initialize Parse. I had to add "preview checks" (ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1", kudos) in all those locations, and avoid running anything Parse-related to make my plain SwiftUI previews work again.

@eniok
Copy link

eniok commented Nov 13, 2022

Hi @drbarto,

The system I tested it also has an M1 (M1 Pro on a Macbook Pro), the environment has the following versions:

  • Cocoapods 1.11.3
  • Parse 1.19.4
  • macOS 13.1
  • Xcode 14.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bounty:$20 Bounty applies for fixing this issue (Parse Bounty Program) type:bug Impaired feature or lacking behavior that is likely assumed
Projects
None yet
Development

No branches or pull requests

3 participants