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
@objc func ReadAllSnapshotsRpc(then completion: @escaping (NSArray?, Error?) -> Void)
i.e. if you would return an opaque array of unknown what, but you return explicitly typed object, so everything else is on NSSecureCoding
as stated.
Update: the reason as error stated is in decoding - for decoding all class should be enumerated. Below is fixed variant:
public required init?(coder: NSCoder) {
guard
let partials = coder.decodeObject(of: [NSArray.self, PartialSnapshot.self], forKey: "partials") as? [PartialSnapshot]
else {
NSLog("ReadAllSnapshotsRpcReturnType: returning nil from init?()")
return nil
}
self.partials = partials
}