I’m attempting to write an XPC service using my own type on the withReply signature. The type/class has Xcode’s “target membership” of both the main app and the XPC service. However I am getting incompatible reply block signature in the debug output even though the same class is being used in the withReply signature however the Xcode target differs as I will explain below.
Note: This is being done in Swift using this project to get me started. Except there they use NSData instead of a custom type.
Details
For the purposes of this question I’ll use the following as an example
- Custom class -
Tweet- This class conforms to theNSSecureCodingprotocol so that it can be passed between the main app and the XPC service - XPC Protocol -
TweetTransferwith one method requiredfunc take(_ count: Int, withReply: ((Tweet) -> Void))
and then all the usual XPC boilerplate where I export an object conforming to TweetTransfer. The XPC service appears to launch but then transfer between it and the main app fails with
XPCWorker[11069:402719] <NSXPCConnection: 0x61800010e220> connection from pid 11066 received an undecodable message
The full message is below[1] but the only difference between the “wire” and “local” is that argument one is
- wire -
_TtC17MainApp5Tweet - local -
_TtC23XPCWorker5Tweet
Where the Xcode target is different. Is that enough to throw it off? How then do I share code between an app and it's XPC service?
[1] Full error text
<NSXPCConnection: 0x61800010e220> connection from pid 11066 received an undecodable message (incompatible reply block signature (wire: <NSMethodSignature: 0x618000074ec0>
number of arguments = 2
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
type encoding (@) '@?'
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 1: -------- -------- -------- --------
type encoding (@) '@"_TtC17MainApp5Tweet"'
flags {isObject}
modifiers {}
frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
class '_TtC17MainApp5Tweet'
vs local: <NSMethodSignature: 0x610000074740>
number of arguments = 2
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
type encoding (@) '@?'
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 1: -------- -------- -------- --------
type encoding (@) '@"_TtC23XPCWorker5Tweet"'
flags {isObject}
modifiers {}
frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
class '_TtC23XPCWorker5Tweet'
)
Update
Some more info regarding the protocol, remoteObjectProxy connection and Tweet object. This is the protocol used for the XPC calls:
@objc(TweetTransfer)
protocol TweetTransfer {
func take(_ count: Int, withReply: replyType)
}
typealias replyType = ((Tweet) -> Void)
I'm using a type alias for convenience. And then the Tweet object is very simple and just for testing (although somewhat complicated by supporting NSSecureCoding):
final class Tweet: NSObject, NSSecureCoding {
var name: String
var text: String
static var supportsSecureCoding = true
init(name: String, text: String) {
self.name = name
self.text = text
}
init?(coder aDecoder: NSCoder) {
guard let name = aDecoder.decodeObject(forKey: "name") as? String else {
fatalError("Could not deserialise name!")
}
guard let text = aDecoder.decodeObject(forKey: "text") as? String else {
fatalError("Could not deseralise text!")
}
self.name = name
self.text = text
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(text, forKey: "text")
}
}
and finally the point at which we call the remoteObjectProxy
guard let loader = workerConnection.remoteObjectProxyWithErrorHandler(handler) as? TweetTransfer else {
fatalError("Could not map worker to TweetTransfer protocol!")
}
var tweets = [Tweet]()
loader.take(1) { tweet in
tweets.append(tweet)
}
remoteObjectProxy? As well as the header and implementation for yourTweetclass - A O