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"];
Related
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
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)
My app uses Core Data and NSOperationQueue. In keeping with Apple's guidelines, I'm creating a separate managed object context for each queue. In my case this is pretty simple: I have one background queue that does all the heavy work, and another on the main thread that just reads the data.
It would seem to make sense for me to do something like this:
On the background queue, create an operation that does a bunch of work on the managed object context.
Add a completion block to that operation that saves the managed object context.
But I read in the NSOperation documentation:
The exact execution context for your completion block is not guaranteed but is typically a secondary thread. Therefore, you should not use this block to do any work that requires a very specific execution context. Instead, you should shunt that work to your application’s main thread or to the specific thread that is capable of doing it.
Of course, it's essential that this save be carried out from the same thread that 'owns' the managed object context. But I'm not always clear on whether 'thread' refers to operation queues or not. (It's sometimes used in more or less specific ways.)
Is my 'completion block' strategy workable?
Do a little trick to solve this issue anywhere in code:
Create moc:
moc = [[NSManagedObjectContext alloc] init];
[moc setUndoManager:nil];
[moc setPersistentStoreCoordinator:coordinator];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(importerDidSave:) name:NSManagedObjectContextDidSaveNotification object:self.moc];
don't forget remove observer :
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.moc];
[moc release];
[super dealloc];
}
and, finally, check before u will merge changes, if it main thread:
- (void)importerDidSave:(NSNotification *)saveNotification {
NSLog(#"MERGE in client controller");
if ([NSThread isMainThread]) {
[self.mainMoc mergeChangesFromContextDidSaveNotification:saveNotification];
} else {
[self performSelectorOnMainThread:#selector(importerDidSave:) withObject:saveNotification waitUntilDone:NO];
}
}
I am a bit uncertain on how to do this:
I start a "worker-thread" that runs for the duration of my apps "life".
[NSThread detachNewThreadSelector:#selector(updateModel) toTarget:self withObject:nil];
then
- (void) updateModel {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
BackgroundUpdate *update = [[BackgroundUpdate alloc] initWithTimerInterval:5];
[[NSRunLoop currentRunLoop] run]; //keeps it going 'forever'
[update release];
[pool release];
}
Now the thread "wakes" up every 5 seconds(initWithTimerInterval) to see if
there are any tasks it can do. All the tasks in the BackGroundUpdate Class are only time dependent for now. I would like to have a few that were "event dependent". e.g. I would like to call the Background Object from my main thread and tell it to "speedUp", "slowDown", "reset" or any method on the object.
To do this I guess I need something like performSelectorOnThread but how to get a reference to the NSthread and the Background Object?
Direct answer: Instead of +[NSThread detachNewThreadSelector:toTarget:withObject:], use [[NSThread alloc] initWithTarget:selector:object:]. Don't forget to call -start!
Other thoughts:
Consider using NSOperation/NSOperationQueue instead. Easier and more efficient for most worker thread uses.
Consider whether you really need to do that periodic check on a background thread. Could you just do it on the main run loop, and then throw off work onto other threads as needed? Threads aren't free.
Consider whether polling is the best implementation, too. Look into NSCondition and/or NSConditionLock for more efficient ways to wake up threads when something happens (like adding work to a queue), no polling necessary.
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.