I have a pair of Swift Xcode targets that implement the XPC main app + service app pattern. I figured out how to pass custom classes back and forth, either as arguments to the remote object's methods or "return values" (i.e., arguments the completion handler/reply block). This all works well.
Now I am trying to add a new wrinkle: call the completion handler with an NSArray of my custom classes from the service to return them to the main app. I understand from Apple's docs (and this Obj-C answer) that I need to whitelist my custom class on the receiving end's NSXPCInterface. But I can't figure out how to do this in Swift. All the examples online and in docs are in Obj-C and I'm having trouble finding the correct equivalents in Swift.
Concretely, here's a simplified version of my custom classes:
@objc(PartialSnapshot) public class PartialSnapshot : NSObject, NSSecureCoding {
public var rawPaths: [String]
public static var supportsSecureCoding: Bool = true
public func encode(with coder: NSCoder) {
coder.encode(rawPaths, forKey: "rawPaths")
}
public required init?(coder: NSCoder) {
guard
let rawPaths = coder.decodeObject(of: [NSArray.self], forKey: "rawPaths") as? [String]
else {
NSLog("PartialSnapshot: returning nil from init?(coder: NSCoder)")
return nil
}
self.rawPaths = rawPaths
}
}
@objc(ReadAllSnapshotsRpcReturnType) public class ReadAllSnapshotsRpcReturnType : NSObject, NSSecureCoding {
var partials: [PartialSnapshot]
public static var supportsSecureCoding: Bool = true
public func encode(with coder: NSCoder) {
coder.encode(partials, forKey: "partials")
}
public required init?(coder: NSCoder) {
guard
let partials = coder.decodeObject(of: [NSArray.self], forKey: "partials") as? [PartialSnapshot]
else {
NSLog("ReadAllSnapshotsRpcReturnType: returning nil from init?()")
return nil
}
self.partials = partials
}
}
And my method signature on the remote object's protocol:
@objc func ReadAllSnapshotsRpc(then completion: @escaping (ReadAllSnapshotsRpcReturnType?, Error?) -> Void)
I think I know roughly what I need to do from this Objective-C answer. When I create my NSXPCConnection in the main app, I need to add a line like the third one below:
let connection = NSXPCConnection(machServiceName: SecurityProxyConstants.domain, options: .privileged)
connection.remoteObjectInterface = NSXPCInterface(with: SecurityProxyProtocol.self)
connection.remoteObjectInterface?.setClasses(ReadAllSnapshotsRpcReturnType.self, for: #selector(PartialSnapshot.self), argumentIndex: 0, ofReply: true)
However, I clearly don't have the syntax quite right and the many syntactical variations I have tried also aren't compiling. Can anyone point me in the right direction?
EDIT: This is the runtime error I am getting that leads me to believe I need to whitelist:
<NSXPCConnection: 0x600003f5b0c0> connection to service with pid 9679 named com.mycompany.MyApp.SecurityProxy: Exception caught during decoding of reply to message 'ReadAllSnapshotsRpcWithThen:', dropping incoming message and calling failure block.
Ignored Exception: Exception while decoding argument 0 (#1 of invocation):
<NSInvocation: 0x600001bdb740>
return value: {v} void
target: {@?} 0x0 (block)
argument 1: {@} 0x0
argument 2: {@} 0x0
Exception: value for key 'NS.objects' was of unexpected class 'PartialSnapshot' (0x10c7abe00) [/Users/mwg/Library/Developer/Xcode/DerivedData/MyApp-brumjpaxvoxfmjchpswlfnbsarho/Build/Products/Debug/MyApp.app].
Allowed classes are:
{(
"'NSArray' (0x7ff8485798a0) [/System/Library/Frameworks/CoreFoundation.framework]"
)}
You don't need whitelist in this case, here is by doc:
it would be needed if your published interface looks like
i.e. if you would return an opaque array of unknown what, but you return explicitly typed object, so everything else is on
NSSecureCodingas stated.Update: the reason as error stated is in decoding - for decoding all class should be enumerated. Below is fixed variant: