NSXPCConnection pass an object by proxy - objective-c

The Daemons and Services Programming Guides tells that it is possible to return a proxy object through an open XPC connection, even as a reply block parameter.
Passing an Object By Proxy
Most of the time, it makes sense to copy objects and send them to the other side of a connection. However, this is not always desirable. In particular:
If you need to share a single instance of the data between the client application and the helper, you must pass the objects by proxy.
If an object needs to call methods on other objects within your application that you cannot or do not wish to pass across the connection (such as user interface objects), then you must pass an object by proxy—either the caller, the callee (where possible), or a relay object that you construct specifically for that purpose.
The downside to passing objects by proxy is that performance is significantly reduced (because every access to the object requires interprocess communication). For this reason, you should only pass objects by proxy if it is not possible to pass them by copying.
You can configure additional proxy objects similarly to the way you configured the remoteObjectInterface property of the initial connection. First, identify which parameter to a method should be passed by proxy, then specify an NSXPCInterface object that defines the interface for that object.
First questions come: how should the object to be passed by proxy be defined? As an object conforming to NSXPCProxyCreating protocol? Should remoteObjectProxy and remoteObjectProxyWithErrorHandler: method be implemented then?
An example follows, that is not clear at all to me. In particular I don't understand where should I call the NSXPCInterface method (setInterface:forSelector:argumentIndex:ofReply:) to whitelist the parameter as a proxy: in the XPC service code or in the host?
The first parameter to a method is parameter 0, followed by parameter 1, and so on.
In this case, the value NO is passed for the ofReply parameter because this code is modifying the whitelist for one of the parameters of the method itself. If you are whitelisting a class for a parameter of the method’s reply block, pass YES instead.
So the question is: can anybody provide me with a clear tutorial on how to return an object as a proxy in a block reply of a XPC method call?

I can answer my own question now: to return an object as a proxy in a block reply of a XPC method call, one should call the setInterface:forSelector:argumentIndex:ofReply: method both:
in the XPC service's endpoint, where the exportedInterface is declared
in the host, where the remoteObjectInterface is declared
I.e, common code:
// common (service/host) protocol definition
#protocol Service
#end
#protocol ServiceFactory
-(void)connectToNewService: (void (^)(id<Service>)reply;
#end
In the XPC Service:
// Implement the one method in the NSXPCListenerDelegate protocol.
-(BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection*)newConnection {
NSXPCInterface *serviceFactoryInterface =[NSXPCInterface interfaceWithProtocol:#protocol(ServiceFactory)];
NSXPCInterface *serviceInterface =[NSXPCInterface interfaceWithProtocol:#protocol(Service)];
// connection has to be returned as proxy, not as a copy
[serviceFactoryInterface setInterface: serviceInterface
forSelector: #selector(connectToNewService:)
argumentIndex: 0
ofReply: YES];
newConnection.exportedInterface = serviceFactoryInterface;
newConnection.exportedObject = self;
[newConnection resume];
return YES;
}
In the host code:
// in the host
- (void)openNewService
{
NSXPCConnection *xpcConnection = [[NSXPCConnection alloc] initWithServiceName:#"eu.mycompany.servicefactory"];
NSXPCInterface *serviceFactoryInterface =[NSXPCInterface interfaceWithProtocol:#protocol(ServiceFactory)];
NSXPCInterface *serviceInterface =[NSXPCInterface interfaceWithProtocol:#protocol(Service)];
// connection has to be returned as proxy, not as a copy
[serviceFactoryInterface setInterface: serviceInterface
forSelector: #selector(connectToNewService:)
argumentIndex: 0
ofReply: YES];
xpcConnection.remoteObjectInterface = serviceFactoryInterface;
[xpcConnection resume];
[[xpcConnection remoteObjectProxy] connectToNewService:^(id<Service> newService) {
// here a newService is returned as NSXPCDistantObject <Service>*
[xpcConnection invalidate];
}];
}

Related

ObjC Realm Threading issue

I have a Realm object (LFEMemory) which has a publish method.
When I call the publish method, I have to upload an image to AWS and then update the object with the URL returned by Amazon.
The problem is that when the block returns from AWS, my self LFEMemory object is no longer thread-safe. (In fact, it usually is during the usual running of the app but never if I'm using an App Extension).
I could fix this by removing the publish method from the realm object, and handling it in a controller object, which can fetch a new realm object on the new thread. But that means I need to create new realms everytime I call a block, which surely isn't a good practise.
How do most people handle this situation?
- (void)publishWithBlock:(ResultBlock)block {
FileUploadManager *manager = [[FileUploadManager alloc] init];
[manager uploadWithSuccess:^(NSString *filename) {
//self is no longer thread-safe and will cause a crash
self.media.path = filename;
} failure:^(NSError *error) {
block(NO, error);
};
}
You have various options you can explore:
1) if your object has a primary key (string or a number) you can store the id as a constant inside the method and use it to fetch back the object from any thread by using [Realm objectOfType:forPrimaryKey:]. docs
Don't be afraid to get a new realm from the different thread if that's what you need to do - that does not create "another" Realm or duplicate your file.
2) if you don't have a primary key you can simply create self.media on the main thread and whenever the upload has finished, switch again to the main queue and modify your object there - modifying a property or two on a Realm object will not harm at all your performance on the main thread.
Further - if you have access to your object (as in self.media) it already gives you access to the original Realm used to create / read the object via its realm property docs
Still - I'd go with using the primary key of the object to re-fetch a reference to the object I need if in doubt.

Inviting Peer to MCSEssion

This is my first time posting on stackoverflow, and I'm aware of the strict posting requirements. Please let me know if I'm not following any of the guidelines.
I'm currently writing an IOS (8.4) application in Xcode, using Objective-C. The goal is to use MCSessions in order to stream data between IOS devices. I'm currently struggling with the concept of sessions, despite reading numerous posts here and elsewhere that attempt to clarify the topic. Here are the resources I'm already aware of:
https://developer.apple.com/videos/play/wwdc2013-708/
https://medium.com/#phoenixy/using-multipeer-connectivity-120abacb9db
Here's my current understanding: At the most basic level, you have an advertiser, and a browser. The advertiser has a local session, which allows them to "advertise". When the browser sees an advertiser, the browser sends an invite to the advertiser to his (the browser's) local MCSession. Assuming this is all correct, here's where I'm getting confused. The advertiser can accept the invite, and in the process, passes his local session to the invitationHandler.
I have implemented the following logic in code, as shown below. However, in tracing MCSession state changes for both the advertiser and browser, a connection is attempted, but the final state is always didNotNonnect.
Code for sending invitation (Browser):
[self.broadcasterBrowser invitePeer:[broadcasterPeerIDs objectAtIndex:indexPath.row]
toSession: self.appDelegate.mpcHandler.session withContext:nil timeout:30.0 ];
Code for accepting invitation (Advertiser):
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser
didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void(^)(BOOL accept, MCSession *session))invitationHandler
{
ArrayInvitationHandler = [NSArray arrayWithObject:[invitationHandler copy]];
// ask the user
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:peerID.displayName
message:#"Would like to create a session with you"
delegate:self
cancelButtonTitle:#"Decline"
otherButtonTitles:#"Accept", nil];
[alertView show];
if (alertViewResult)
{
void (^invitationHandler)(BOOL, MCSession *) = [ArrayInvitationHandler objectAtIndex:0];
invitationHandler(YES, self.appDelegate.mpcHandler.session);
}
}
Any help is greatly appreciated!
Austin
I ran into a similar problem trying to use MPC. I created a custom class to handle all of the MPC connectivity details. While testing though, every time my advertiser would accept the invite, it would complain about wrong connection data and fail. I discovered that the problem was that I was vending out the MCPeerID object for my device from a class variable I created as below:
static var peerObject : MCPeerID {
return MCPeerID(displayName: deviceNameString)
}
lazy var sessionIVar = MCSession (peer: MyConnectivityClass.peerObject)
func startAdvertisingForConnectivity () {
advertiserService = MCNearbyServiceAdvertiser (peer: MyConnectivityClass.peerObject, discoveryInfo: nil, serviceType: "my-multipeer-connectivity-service-identifier")
}
Then when I got an invitation I would initialize a MCSession object using the "peerObject" computed property and return it in the invitation handler, like this:
func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: #escaping (Bool, MCSession?) -> Swift.Void) {
invitationHandler(true, sessionIVar)
}
I assumed that each time I called for "MyConnectivityClass.peerObject" it would give back an identical peerID because I was always initializing it with the same display name. It turns out that's not true. So when I was advertising I was using one peerID object and then when I was responding to the invitation, I was responding with a MCSession object that contained an entirely different peerID.
So the solution was to change the "MyConnectivityClass.peerObject" computed class property to a constant, or an Ivar, in my connection handler class. Like this:
let peerObject : MCPeerID = MCPeerID(displayName: deviceNameString)
Then the rest of the code just worked because no matter how many times I called for the MCPeerID object, it was always the same. Looking back I don't know why I started out with it the way I did. :-)
Then in my connectivity class I archived and stored the MCPeerID objects for both the browser and the advertiser so that I could have the advertiser automatically accept the invitation for trusted MCPeerIDs. That's not possible if you create the MCPeerID object each time you use it, even if you always initialize it with the same DisplayName.

WCF with multiple clients

I am trying to learn WCF with this example
http://www.codeproject.com/Articles/39143/C-WCF-Client-Server-without-HTTP-with-Callbacks-Ma
Also trying to extend the functionality on the server by adding mutual exclusion with multiple clients.
I am basically trying to have a global array of numbers and a function(which has been exposed with an Operationcontract) that can access this array.But only one client is allowed to access the array at a time.
Can someone point me in the right direction by adding a simple function with a mutual exclusion lock?
Depending on what exactly you want to do, how about putting a lock around the function accessing your array (maybe event put your array into a singleton).
Then you could have
class SingletonClassForYourArray {
object aLock = new object();
int yourArray;
private SingletonClassForYourArray instance;
public SingletonClassForYourArray GetInstance()
{
// normal singleton init of instance on demand
}
int [] YourArray
{
get
{
lock(aLock)
{
return yourArray;
}
}
}
}
This would be the easiest way to have only one client access the array. All clients without the lock will have to wait in turn (fairness not guaranteed). Be careful as this may result in timeouts if clients have to wait to long.

WCF Service and best practices surrounding clients and open/close methods

Having a WCF service and a Consumer I'm not really sure how to handle the Open and Close methods and the lifetime of my Client.
I created the client myself extending and implementing ClientBase and IMyService. Let's call it MyServiceClient
One place I use it for example is MembershipProvider. So I gave MembershipProvider a MyClient as member variable.
I would like to have it instanced once in the MembershipProvider (via IoC container) and then perhaps do a Open and Close call inside every method call in the client.
public bool ValidateUser(string username, string password)
{
this.Open();
bool b = Channel.ValidateUser(username, password);
this.Close();
return b;
}
Is this the right way to go about it. I don't really understand what's really happening when open/close is called and how having one instance of client affects the service (if at all).
One of the problems with using a single client (WCF proxy) instance is that when a fault occurs the proxy enters a faulted state, and it cannot be reused or Dispose-d, only Abort-ed and created anew. On the other hand, if you use/require Sessions on the service side you need the same proxy instance across multiple calls.
In any case, if you would like to use proxy now and worry about transport, sessions or faults later I suggest a wrapper like this that I use for my WCF proxies:
TResult ExecuteServiceMethod<TResult>(Func<MyService, TResult> method)
{
var proxy = new MyService(); //...Or reuse an existing if not faulted
try
{
return method(proxy);
}
catch(Exception e)
{
//...Handle exceptions
}
finally
{
//...Per-call cleanup, for example proxy.Abort() if faulted...
}
}
and you call your service methods like this:
var result = ExecuteServiceMethod((MyService s) => s.VerifyUser(username, password));
Replace MyService with your actual client type. This way you can later change your opening/closing/reusing strategy, add logging, etc. for all service calls by adding code before or after the line return method(client).

Detecting/Repairing NSConnection failure

I would like to use NSConnection/NSDistributedObject for interprocess communication. I would like the client to be able to handle the case where the server is only occasionally reachable.
How can I determine if sending a message to the NSConnection will fail or has failed? Currently if my server (the process that has vended the remote object) dies, the client will crash if it sends a selector to the remote object.
Ideally I'd like to have a wrapper for the remote object that can lazily instantiate (or reinstantiate) the connection, and return a default value in the case where the connection could not be instantiated, or the connection has failed. I don't really know the correct way to do this using objective c.
Here's some pseudocode representing this logic:
if myConnection is null:
instantiate myConnection
if MyConnection is null:
return defaultValue
try
return [myConnection someMethod]
catch
myConnection = null
return defaultValue
Unfortunately the only way to detect a connection failure is to use an exception handler, as there is no reliable way to "ask" a remote object if the connection is still valid. Thankfully, this is simple:
//get the distributed object
id <YourDOProtocol> remoteObject = (id <YourDOProtocol>)[NSConnection rootProxyForConnectionWithRegisteredName:#"YourRegisteredName" host:yourHost];
//call a method on the distributed object
#try
{
NSString* response = [remoteObject responseMethod];
//do something with response
}
#catch(NSException* e)
{
//the receiver is invalid, which occurs if the connection cannot be made
//handle error here
}
If your server is quiting gracefully then, I'm of the understanding, that it'll post an NSConnectionDidDieNotification as it's connection closes so you could register your client like this:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(connectionDidDie:) name:NSConnectionDidDieNotification object:remoteObject];
Perhaps your connectionDidDie:method could set a Boolean var that you can check prior to attempting sending the message.
Your DO could post a notification to say that it's started (although I think there are also system messages for that but I've only just started learning about DO's) and you could similarly register to be notified of it's startup.
I guess Rob's answer is a definite 'catch-all' and you wouldn't need to worry about the notification centre having not got through to the server in time.
I've been using the 'did die' notification it in my first DO app and I hope it helps you.
Todd.