Use Object Capture with Combine in a macOS app

Hi! I noticed that in a macOS app, session.process will not run unless I add RunLoop.main.run() afterwards, which successfully creates the model but leaves the app unresponsive with the spinning color wheel.

Also, the sample code for the async/await version shows a session.outputs property that doesn't exist in Xcode. I'd appreciate any suggestions on how to fix this! Thank you.

session.output

//            .receive(on: DispatchQueue.global())

            .sink(receiveCompletion: { completion in

                var maybeError: Error? = nil

                if case .failure(let error) = completion {

                    print("Output: ERROR = \(String(describing: error))")

                    maybeError = error

                    Foundation.exit(maybeError != nil ? 0 : 1)

                }

            }, receiveValue: { output in

                DispatchQueue.main.async {

                    self.output = output

                }

                switch output {

                case .processingComplete:

                    // All requests are done so you can safely exit.

                    print("Processing is complete!")

                case .requestError(let request, let error):

                    print("Request \(String(describing: request)) had an error: \(String(describing: error))")

                case .requestComplete(let request, let result):

                    self.complete(request: request, result: result)

                case .requestProgress(let request, let fractionComplete):

                    self.progress(request: request, fractionComplete: fractionComplete)

                case .inputComplete:  // Data ingestion has finished.

                    print("Data ingestion is complete.  Beginning processing...")

                case .invalidSample(let id, let reason):

                    print("Invalid Sample! id=\(id)  reason=\"\(reason)\"")

                case .skippedSample(let id):

                    print("Sample id=\(id) was skipped by processing.")

                case .automaticDownsampling:

                    print("Automatic downsampling was applied!")

                default:

                    print("Output: unhandled message: \(output.localizedDescription)")

                }

            })

            .store(in: &subscriptions)

        

        reference = try! session.process(requests: [

            .modelFile(url: URL(fileURLWithPath: "model123.usdz"), detail: .reduced)

            RunLoop.main.run()

        ])
Answered by Engineer in 678379022

The API provided is a little different than the session video, using Combine Publisher, but the concepts of the message stream and messages are the same. You may need to refer to the Combine documentation for more help making a macOS app using Combine.

That said, you shouldn't call RunLoop.main.run() in an app since as you say this will block the main UI thread -- this line is for the command-line example apps only to keep the app from immediately terminating since process(requests:) returns immediately and the processing happens in the background with messages arriving on the publisher. Your macOS app will run this event loop itself.

For a macOS app you will need several differences from the command-line sample. We don't provide this as sample code , but I can provide a few pointers:

I'd recommend receive(on:) the main thread on the publisher sink. Also, probably you don't want to Foundation.exit an app, but rather set some UI element in your app.

Be sure the session is created in your app as state in the class and not as a local variable which might be deinitialized by ARC. Similarly, keeping the sink subscription around as in the store(in:) is also important to keep the sink from being deinitialized by ARC. Be sure you also keep the subscriptions object in the model state and not a local variable as well for the same reason.

You may refer to the Combine documentation for more information.

Accepted Answer

The API provided is a little different than the session video, using Combine Publisher, but the concepts of the message stream and messages are the same. You may need to refer to the Combine documentation for more help making a macOS app using Combine.

That said, you shouldn't call RunLoop.main.run() in an app since as you say this will block the main UI thread -- this line is for the command-line example apps only to keep the app from immediately terminating since process(requests:) returns immediately and the processing happens in the background with messages arriving on the publisher. Your macOS app will run this event loop itself.

For a macOS app you will need several differences from the command-line sample. We don't provide this as sample code , but I can provide a few pointers:

I'd recommend receive(on:) the main thread on the publisher sink. Also, probably you don't want to Foundation.exit an app, but rather set some UI element in your app.

Be sure the session is created in your app as state in the class and not as a local variable which might be deinitialized by ARC. Similarly, keeping the sink subscription around as in the store(in:) is also important to keep the sink from being deinitialized by ARC. Be sure you also keep the subscriptions object in the model state and not a local variable as well for the same reason.

You may refer to the Combine documentation for more information.

Thank you so much for your quick reply! Storing the session as a class property fixed it.

Use Object Capture with Combine in a macOS app
 
 
Q