I have a windowed app, and to add some functionality I need another app which launches at login and sync data to server if available.
I have tried with NSDistributionNotification but its practically useless in a sandboxed app. I looked up XPC and hoped it will work but I just dont know how to get it to work with the helper. So far I have done this using XPC.
Main App
NSXPCInterface *remoteInterface = [NSXPCInterface interfaceWithProtocol:#protocol(AddProtocol)];
NSXPCConnection *xpcConnection = [[NSXPCConnection alloc] initWithServiceName:#"com.example.SampleService"];
xpcConnection.remoteObjectInterface = remoteInterface;
xpcConnection.interruptionHandler = ^{
NSLog(#"Connection Terminated");
};
xpcConnection.invalidationHandler = ^{
NSLog(#"Connection Invalidated");
};
[xpcConnection resume];
NSInteger num1 = [_number1Input.stringValue integerValue];
NSInteger num2 = [_number2Input.stringValue integerValue];
[xpcConnection.remoteObjectProxy add:num1 to:num2 reply:^(NSInteger result) {
NSLog(#"Result of %d + %d = %d", (int) num1, (int) num2, (int) result);
}];
XPC Service
In main () ...
SampleListener *delegate = [[SampleListener alloc] init];
NSXPCListener *listener = [NSXPCListener serviceListener];
listener.delegate = delegate;
[listener resume];
// In delegate
-(BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
NSXPCInterface *interface = [NSXPCInterface interfaceWithProtocol:#protocol(AddProtocol)];
newConnection.exportedInterface = interface;
newConnection.exportedObject = [[SampleObject alloc] init];
[newConnection resume];
return YES;
}
// In Exported Object class
-(void)add:(NSInteger)num1 to:(NSInteger)num2 reply:(void (^)(NSInteger))respondBack {
resultOfAddition = num1 + num2;
respondBack(resultOfAddition);
}
This works fine, now I need to pass this result to Helper app. How Can I do this ? If XPC is not the answer here to communicate, then which one should I be using ? Any pointers please ?
Alright for anyone that has been struggling with this, I was finally able to 100% get communication working between two application processes, using NSXPCConnection
The key to note is that you can only create an NSXPCConnection to three things.
An XPCService. You can connect to an XPCService strictly through
a name
A Mach Service. You can also connect to a Mach Service
strictly through a name
An NSXPCEndpoint. This is what we're
looking for, to communicate between two application processes.
The problem being that we can't directly transfer an NSXPCEndpoint from one application to another.
It involved creating a machservice Launch Agent (See this example for how to do that) that held an NSXPCEndpoint property. One application can connect to the machservice, and set that property to it's own [NSXPCListener anonymousListener].endpoint
Then the other application can connect to the machservice, and ask for that endpoint.
Then using that endpoint, an NSXPCConnection can be created, which successfully established a bridge between the two applications. I have tested sending objects back and forth, and it all works as expected.
Note that if your application is sandboxed, you will have to create an XPCService, as a middle man between your Application and the Machservice
I'm pretty pumped that I got this working-- I'm fairly active in SO, so if anybody is interested in source code, just add a comment and I can go through the effort to post more details
Some hurdles I came across:
You have to launch your machservice, these are the lines:
OSStatus err;
AuthorizationExternalForm extForm;
err = AuthorizationCreate(NULL, NULL, 0, &self->_authRef);
if (err == errAuthorizationSuccess) {
NSLog(#"SUCCESS AUTHORIZING DAEMON");
}
assert(err == errAuthorizationSuccess);
Boolean success;
CFErrorRef error;
success = SMJobBless(
kSMDomainSystemLaunchd,
CFSTR("DAEMON IDENTIFIER HERE"),
self->_authRef,
&error
);
Also, every time you rebuild your daemon, you have to unload the previous launch agent, with these bash commands:
sudo launchctl unload /Library/LaunchDaemons/com.example.apple-samplecode.EBAS.HelperTool.plist
sudo rm /Library/LaunchDaemons/com.example.apple-samplecode.EBAS.HelperTool.plist
sudo rm /Library/PrivilegedHelperTools/com.example.apple-samplecode.EBAS.HelperTool
(With your corresponding identifiers, of course)
If you are searching on how to accomplish this in Swift. I wrote a tutorial on how to do this:
https://rderik.com/blog/creating-a-launch-agent-that-provides-an-xpc-service-on-macos/
You have to first create a Launch Agent (or a daemon, if you need more privileges) that exposes an XPC service. The XPC service will be registered as a mach service that your agent provides. So your Agent will have to create a listener like the following:
let listener = NSXPCListener(machServiceName: "com.rderik.exampleXPC" )
And to use that service from other client, you'll need to create aNSXPCConnection to that mach service. Like this:
let connection = NSXPCConnection(machServiceName: "com.rderik.exampleXPC")
Behind the scenes, a simplification of what happens is that your Agent will register your mach service to launchd. When your "client" wants to connect to to a mach service launchd will already have it register, so it will build the connection between the two.
I hope that helps.
I think I figured out how to do this. All you have to do is create a command line helper tool in Xcode, install it as a Launchd job (Either a daemon or an Agent depending on the privilege requirement). You can use the defined protocol to communicate with the helper tool. Refer to the below sample code from Apple to understand how it is done.
Sample Code from Apple:
https://developer.apple.com/library/mac/samplecode/EvenBetterAuthorizationSample/Listings/Read_Me_About_EvenBetterAuthorizationSample_txt.html#//apple_ref/doc/uid/DTS40013768-Read_Me_About_EvenBetterAuthorizationSample_txt-DontLinkElementID_17
Read the below link to understand what you really want, a Daemon or an Agent:
https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/DesigningDaemons.html#//apple_ref/doc/uid/10000172i-SW4-BBCBHBFB
Related
I am working on an OS X (Yosemite) app which downloads two types of csv data (call them type A and type B) from the internet asynchronously using the NSURLSession API .There are multiple requests for each type of csv. Each request is it's own dedicated session wrapped in a custom class. There is one base request class with a subclass for each type. (In hindsight maybe not an ideal design but irrelevant for my issue I think).
The app is constructed such that each type of csv data is downloaded in a sequential queue. Only one request of each type can be active at a time but both types can occur simultaneously and both use the main thread for delegate callbacks. All of this works fine usually.
The issue I am seeing is that sometimes with heavy traffic I get "cross hearing", i.e. I sometimes get a response back to a type B request that is reported as completed successfully but it contains a number of type B cvs lines and then some type A lines tagged on after - so I sometimes (rarely) get type A data in my type B requests. (or the other way around).
Basically it look like the "switching" logic in Apples API gets confused about which incoming packet belongs to what request/session. The two different request types goes to different URLs but they are related and it may be that they both in the end resolve to the same IP, I am not sure about that. I wonder if there may be something related to the packet headers if they come from the same server that makes it difficult to determine what request they belong to (I'm not good enough at the internet protocols to know if this is a sensible guess). If that is the case then the solution must be to ensure all requests are in one queue so that they cannot be active simultaneously, but I do not want to do that large architecture change before I am confident there is no other workaround.
I looked for similar questions and found this old question (Why is my data getting corrupted when I send requests asynchronously in objective c for iOS?) which appears to describe the exact same issue but unfortunately it has no answer. Other than that I found nothing similar so I guess I am doing something stupid here but it would be good to know why this issue occurs before I start changing the architecture to fix it.
Has anyone seen this before and know what the cause and workaround is?
I did not include any code as I felt there was no point given it appears to be an architecture issue and if I added code it would need to be a lot. However I will be happy to add whatever you suggest if that helps understand the question.
Edit:
The relevant (I hope) code added below. Note objects are one shot only. The parameters for the request are injected by the init method and the NSURLSession is used for a single task only. Hence the session is invalidated after launch and the NSMutableData array released after parsing of the data.
-(BOOL)executeRequest {
NSURLSessionConfiguration *theConfig = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *theSession = [NSURLSession sessionWithConfiguration:theConfig delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:self.queryURL cachePolicy: NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:BSTTIMEOUT];
NSURLSessionDataTask *theTask = [theSession dataTaskWithRequest:theRequest];
if(!theTask) {
return NO;
}
[theTask resume];
[theSession finishTasksAndInvalidate];
self.internetData = [NSMutableData dataWithCapacity:0];
return YES;
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.internetData appendData:data];
return;
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if((error)||(![self parseData]))
{
self.internetData = nil;
if(!error) {
NSDictionary *errorDictionary = #{ NSLocalizedDescriptionKey : #"Parsing of internet data failed", NSLocalizedFailureReasonErrorKey : #"Bad data was found in received buffer"};
error = [NSError errorWithDomain:NSCocoaErrorDomain code:EIO userInfo:errorDictionary];
}
NSDictionary* ui = [NSDictionary dictionaryWithObject:error forKey:#"Error"];
[[NSNotificationCenter defaultCenter] postNotificationName:[self failNotification] object:self userInfo:ui];
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:[self successNotification] object:self];
return;
}
First of all: You should not create a new session for each request. This are no sessions anymore. From the docs:
With the NSURLSession API, your app creates one or more sessions, each of which coordinates a group of related data transfer tasks. For example, if you are writing a web browser, your app might create one session per tab or window, or one session for interactive use and another session for background downloads. Within each session, your app adds a series of tasks, each of which represents a request for a specific URL (following HTTP redirects if necessary).
Second: Where do you store the session et al., so it is not deallocated?
Your main problem: Obviously you start new requests while requests are potentially running. But you have only one NSMutableData instance that receives the data in -URLSession:task:didReceiveData:: Many requests, one storage … Of course that mixes up.
I finally managed to track down my (stupid) error. For future reference the issue was caused by a failure to realise that the data coming back was not zero terminated.
Most of the data requested in my case is XML and the NSXMLParserclass wants a NSDatawithout extra trailing zeros so that works well.
But the requests which occasionally failed uses a CSV format where the data passes over a NSStringwhich is created by [NSString stringWithUTF8String] which expects a zero terminated c style string as input. This was the main culprit. Often it worked as it should. Sometimes it failed outright and sometimes it just did a buffer overrun and got some of the previous request data that was in the same memory area. These were the cases I noticed when posting the question.
Thus the solution is to switch to the use of [[NSString alloc] initWithData: encoding:NSUTF8StringEncoding] which works with non null-terminated NSDatabuffers.
I'm trying to implement the push functionality in Sinch.
I've set up my since client as such:
[_client setSupportMessaging:YES];
[_client setSupportPushNotifications:YES];
_client.delegate = self;
[_client start];
[_client startListeningOnActiveConnection];
Then on
- (void)clientDidStart:(id<SINClient>)client {
client.messageClient.delegate = self;
}
And in AppDelegate.h
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// get previously initiated Sinch client
id<SINClient> client = [SinchClient sharedClient].client;
[client registerPushNotificationData:deviceToken];
}
The other delegate methods work fine (messageSent, messageDelivered, etc), however, I can't seem to invoke
(void)message:(id<SINMessage>)message shouldSendPushNotifications:(NSArray *)pushPairs
for testing, I've setup two phones each running a SinchClient, and force quit the app on the one of the phones (assuming that should trigger the offline state) - still no dice though. I'm watching a breakpoint, but the delegate doesn't seem to be firing.
Any thoughts?
Thanks,
Charlie
Make sure that both phones have registered push data (won't work on a simulator for example). Otherwise, putting one of these devices in flight mode and try to send a message to that device should suffice to get the shouldSendPushNotification callback (make sure you are signed in as different user on the two devices).
If you don't receive the messageSent callback you won't receive the shouldSendPushNotification callback. Likewise, if you receive the messageDelivered event, you won't get the shouldSendPushNotification.
I'm currently using Apple's SimplePing in a Mac OS X application to ping a URL before transferring data, which works fine, but locks up my UI. I may not have looked in the right places, but how do I keep this from happening? I'm currently using the currentRunLoop, which I think is the problem, but I still want the user to be able to interact with the UI (e.g. cancel) during this action. How do I create a run loop for Simple Ping so my UI doesn't lock up?
SimplePing *localPing = [SimplePing simplePingWithHostName:pingHost];
[localPing setDelegate:self];
[localPing start];
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (localPing != nil);
This reason your UI is locked while using SimplePing is because ping utility take certain time to complete. And it seems that you are doing this in your main thread, resulting locking the UI interface white ping task is in process.
So you can use following code
-(void) ping:(NSString *) ip
{
SimplePing *localPing = [SimplePing simplePingWithHostName:pingHost];
[localPing setDelegate:self];
[localPing start];
}
and then call ping function in new thread like
[NSThread detachNewThreadSelector:#selector(ping:) toTarget:self. withObject:#"IP Address"];
I have a straight-forward, Mac OS X, Cocoa, Document-based application which uses the new 10.7 Autosave, Versions and Asychronous Saving APIs. I am fully using the NSDocument APIs to get all of Apple's Document-based application features for free.
In order to support the new Lion Autosave/Versions/AsyncSaving, I have overridden the following methods in my NSDocument subclass like so:
#implementation MyDocument
...
+ (BOOL)autosavesInPlace { return YES; }
- (BOOL)canAsynchronouslyWriteToURL:(NSURL *)URL ofType:(NSString *)type forSaveOperation:(NSSaveOperationType)op {
return YES;
}
I have also overridden -dataOfType:error: to help implement saving the document's data to disk:
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outErr {
NSData *data = nil;
if ([typeName isEqualToString:MY_SUPPORTED_TYPE_NAME]) {
data = makeSnapshotCopyOfMyDocumentData(); // assume return value is autoreleased
} else if (outErr) {
*outErr = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:nil];
}
// not sure this is doing much good, since i take no action after this.
[self unblockUserInteraction];
return data;
}
...
#end
See how I'm calling -unblockUserInteraction at the end there?
When supporting the new 10.7 AsyncSaving feature, Apple advises calling -unblockUserInteraction as early as possible (after making a snapshot copy of your document's data) in your -dataOfType:error: implementation. But Apple's example showed them doing much more work after calling -unblockUserInteraction.
However, considering I take no other action after this, I'm wondering if there's any point in calling -unblockUserInteraction there at all.
So my questions:
Considering I take no other action after it, is my call to -unblockUserInteraction doing any good?
Do the Apple Frameworks just call -unblockUserInteraction immediately after -dataOfType:error: returns anyway? Should I just leave it to them?
I just noticed a subtle wording difference between the NSDocument documentation and the comment in NSDocument.h:
Docs:
If saveToURL:ofType:forSaveOperation:completionHandler: is writing on
a non-main thread because
canAsynchronouslyWriteToURL:ofType:forSaveOperation: has returned YES,
but it is still blocking the main thread, this method unblocks the
main thread. Otherwise, it does nothing.
Header:
If -saveToURL:ofType:forSaveOperation:completionHandler: is writing on
a non-main thread because
-canAsynchronouslyWriteToURL:ofType:forSaveOperation: has returned YES, but is still blocking the main thread, unblock the main thread.
Otherwise, do nothing.
I assume the Header is more up to date.
I am working on an application that calls unblockUserInteraction after the last line that has to run on the main thread. (At least that's the way I understood it)
I think our code fits the scenario that Apple had in mind when designing the async saving part of NSDocument:
in fileWrapperOfType: we ...
create a QL preview for our file wrapper (that has to run on the
main thread) ...
unblockUserInteraction ...
... "long" running file saving task (involving compression)
I want to create a separate thread that runs its own window. Frankly, the documentation does not make sense to me.
So I create an NSThread with a main function. I start the thread, create an NSAutoreleasePool, and run the run loop:
// Global:
BOOL shouldKeepRunning = YES;
- (void)threadMain {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
// Load a nib file, set up its controllers etc.
while (shouldKeepRunning) {
NSAutoreleasePool *loopPool = [NSAutoreleasePool new];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
[loopPool drain];
}
[pool drain];
}
But since there is no registered port or observer, runUntilDate: exits immediately and CPU utilization goes to 100%.
All thread communication is handled by calls to performSelector:onThread:withObject:waitUntilDone:. Clearly, I am not using the API correctly. So, what am I doing wrong?
Much of AppKit is not thread-safe and will not work properly (1) when manipulated outside the main thread. You will find only pain and misery trying to ignore this fact.
What are you really trying to do that requires a different thread for this window? Are you merely trying to keep a responsive UI? If so, there're much better ways of doing it. See NSOperation / NSOperationQueue (where "units of work" and "queues" are the focus, not "this window shall run on this thread, etc.").
I'd recommend restating your question with your specific goal detailed clearly.
(1) For some classes, it takes a lot of careful work. For others, they are quite firmly off limits.