Relaunching Your Application

Relaunching Your Application,第1张

概述Relaunching Your Application Matt Patenaude Whether you're applying new preferences, installing a new version of your app, or perhaps something more unusual, there may come a time in your life where y @R_193_4404@ing Your Application

Matt Patenaude

Whether you're applying new preferences,installing a new version of your app,or perhaps something more unusual,there may come a time in your life where you say to yourself "I really wish I Could automatically restart my application."

Your first instinct would be to check the documentation for NSApplication to see if there was an easy-to-use -@R_193_4404@: method that would allow you to do this. Failing this — and sadly,this does fail — you might poke around in NSWorkspace,or even some of the LaunchServices APIs,hoPing to find something that will allow you to easily restart your application. Alas,despite the fact that the task does not seem complex,there is no built-in functionality in Mac OS X that allows you to restart the current application.

So let's write some.

The Background

The fatal problem with restarting an application is that once your application quits,it loses control: there is no way to perform operations from your application after your application has already exited (it makes perfect sense,it's just a bit inconvenIEnt). Therefore,in order to facilitate an application restart,some outsIDe agent needs to be there to launch the application again once the application has quit; we can't do this without help.

LaunchServices,particularly launchd,sounds like a good place to start,since it's a system daemon that facilitates the launching of apps. Unfortunately,LaunchServices is Apple's domain,and we can't just add features to it. For that reason,we will instead create our own one-shot daemon. Here's how it works.

When your application receives a @R_193_4404@ request,it will launch a small daemon within the application bundle. That daemon will then wait until the application quits,@R_193_4404@ the application,and then quit itself. relatively straightforward,right?

We'll be implementing this as a category on NSApplication,so we can @R_193_4404@ our application by simply doing this:

[NSApp @R_193_4404@:nil];
The Code

I'm going to assume you have an existing application to which you want to add @R_193_4404@ capabilitIEs,so we'll assume you've already got an Xcode project ready to go.

The first thing you'll want to do is add a new target for your @R_193_4404@ daemon. From the Project menu,choose "New Target...",and then under Cocoa,choose "Shell Tool." name your target "@R_193_4404@," then click Finish.

Expand the Targets group in the sIDebar,and double click the target for your application. Drag the @R_193_4404@ target into the "Direct DependencIEs" table vIEw on the General tab of the Info window. This will ensure that whenever you go to compile your application,the @R_193_4404@ daemon will be compiled as well.

Since we'll be using Cocoa code within our shell tool,we need to make sure our tool will link against the Cocoa framework. Expand the disclosure triangle next to your @R_193_4404@ target,and then the one next to "link Binary With librarIEs." If by some chance Cocoa.framework is already Listed there,you're all set,but most likely it won't be. From the Frameworks → linked Frameworks group (above),drag the Cocoa.framework entry down to the "link Binary With librarIEs" group under your @R_193_4404@ target. This will allow you to use Cocoa code. (Note that you can also drag in the framework from /System/library/Frameworks/Cocoa.framework,but this is a bit faster).

Finally,expand the Products group,and drag the @R_193_4404@ build product into the "copy Bundle Resources" group under your main application's target. This will tell Xcode to copy your @R_193_4404@ daemon executable into your main application's Resources directory after it's compiled. When all is saID and done,your targets should look a bit like this:

Now,we're going to create a new file called @R_193_4404@.m. For this,I use the Objective-C Class template,and just opt not to create a header file. Make sure this gets added into only the @R_193_4404@ target,like so:

Get rID of the implementation deFinition in your @R_193_4404@.m file,and instead,insert the template for a C main method. Be sure to import the Cocoa headers! When you're done,your @R_193_4404@.m file should look something like this:

//  @R_193_4404@.m#import <Cocoa/Cocoa.h>#pragma mark Main methodint main(int argc,char *argv[]){	}

Since this application will be running without a run loop,we need to create an autorelease pool,as is standard procedure with non-app Cocoa projects.

NSautoreleasePool *pool = [[NSautoreleasePool alloc] init];		// wait a sec,to be safesleep(1);// blah blah blah,more code![pool drain];

You'll notice I added a one-second sleep into the project. This is just to ensure that the application has indeed quit before we attempt to launch it again. Depending on how long your application takes to terminate ordinarily,you may want to increase this to two,three,or even more seconds.

We're going to assume that the path to the application we want to launch was passed in as the first command line parameter to our @R_193_4404@ daemon,so we'll obtain an Nsstring from that using Nsstring's +stringWithCString:enCoding: method. Once we've got this,we'll use NSWorkspace's -openfile: method to launch our application,making sure to call -stringByExpandingTildeInPath on our path in case we're provIDed a user-relative path.

Nsstring *appPath = [Nsstring stringWithCString:argv[1] enCoding:NSUTF8StringEnCoding];BOol success = [[NSWorkspace shareDWorkspace] openfile:[appPath stringByExpandingTildeInPath]];

Finally,we return,draining our autorelease pool and the process,and using the success of the application launch to return either 0 (success) or 1 (error).

[pool drain];return (success) ? 0 : 1;

All together,our @R_193_4404@ tool looks something like this (I also added in a log to the console if the application Failed to launch properly):

//  @R_193_4404@.m#import <Cocoa/Cocoa.h>#pragma mark Main methodint main(int argc,char *argv[]){	NSautoreleasePool *pool = [[NSautoreleasePool alloc] init];	// wait a sec,to be safe	sleep(1);	Nsstring *appPath = [Nsstring stringWithCString:argv[1] enCoding:NSUTF8StringEnCoding];	BOol success = [[NSWorkspace shareDWorkspace] openfile:[appPath stringByExpandingTildeInPath]];	if (!success)		NSLog(@"Error: Could not @R_193_4404@ application at %@",appPath);	[pool drain];	return (success) ? 0 : 1;}

Pretty straightforward,right?

The next order of business is creating our NSApplication category. For this,I once again use the Objective-C Class template (with header file,this time). Create a new file called NSApplication+@R_193_4404@.m,and make sure you add it to your main application target this time,not your @R_193_4404@ tool.

@H_404_135@

Modify your header to specify instead the interface for a category called @R_193_4404@,and add a signature for a method called -@R_193_4404@:. You may also consIDer adding a preprocessor constant (which I chose to call NSApplication@R_193_4404@Daemon) that maps to the name of your @R_193_4404@ tool,making it easy for you to change this in the future. You can,of course,opt not to do this,but it's at least somewhat good practice.

//  NSApplication+@R_193_4404@.h#import <Cocoa/Cocoa.h>#define NSApplication@R_193_4404@Daemon @"@R_193_4404@"@interface NSApplication (@R_193_4404@)- (voID)@R_193_4404@:(ID)sender;@end

Finally,switch over to NSApplication+@R_193_4404@.m,fix the implementation symbol (change from NSApplication_@R_193_4404@ to NSApplication (@R_193_4404@)),an implement the @R_193_4404@: method. Essentially all you need to do is get the path to the daemon,start it using NSTask (passing the path to the current application on the command line),and then terminate the application (using self,since we're implementing this within NSApplication). It's relatively trivial,so I'll just give you the final code block:

//  NSApplication+@R_193_4404@.m#import "NSApplication+@R_193_4404@.h"@implementation NSApplication (@R_193_4404@)- (voID)@R_193_4404@:(ID)sender{	Nsstring *daemonPath = [[NSBundle mainBundle] pathForResource:NSApplication@R_193_4404@Daemon ofType:nil];	[NSTask launchedTaskWithLaunchPath:daemonPath arguments:[NSArray arrayWithObject:[[NSBundle mainBundle] bundlePath]]];	[self terminate:sender];}@end

And for most basic purposes,that's really all there is to it! Calling the @R_193_4404@: method of NSApp will begin the @R_193_4404@ cycle.

Beefing it Up

Now while this will be sufficIEnt for most uses,this implementation does ignore a few concerns,particularly what happens if your application displays alerts or requires subsequent user interaction in order to terminate (IE,an unsaved changes warning for document-based applications). Since most users do not have ninja-like reflexes,it will likely take more than one second for them to respond,meaning the @R_193_4404@ will fail. Even if you increase the delay,it will most likely cause problems — in general,it's totally impractical to use the above solution if there exists a case in which your application would not quit immediately.

In order to combat this,we're going to take a cue from Sparkle: get the PID (process ID) of the application,and wait until that process ends before @R_193_4404@ing the application. This eliminates our need to use sleep(1),and also clears us for use in almost every use case.

The first order of business is to modify our @R_193_4404@ category to pass the process ID as the second parameter. All we need to do is replace this line…

[NSTask launchedTaskWithLaunchPath:daemonPath arguments:[NSArray arrayWithObject:[[NSBundle mainBundle] bundlePath]]];

… with this line.

[NSTask launchedTaskWithLaunchPath:daemonPath arguments:[NSArray arrayWithObjects:[[NSBundle mainBundle] bundlePath],[Nsstring stringWithFormat:@"%d",[[nsprocessInfo processInfo] processIDentifIEr]],nil]];

Now,in our @R_193_4404@ daemon,we can replace the arbitrary sleep() call with a loop which checks if the process ID exists,sleePing in one-second intervals until the condition changes. A working loop would look a little bit like this:

pID_t parentPID = atoi(argv[2]);ProcessSerialNumber psn;while (GetProcessForPID(parentPID,&psn) != procNotFound)	sleep(1);

If you're a bit shaky on the Process Manager functions,don't worry about it — everything I needed I learned from exploring the sparkle source,I can't honestly say I have a strong command of it myself. All you need to kNow is that we first convert the process ID into a native representation,then attempt to get a reference to the process based on PID; when that fails,(IE,making process != procNotFound false),the loop exits,and execution continues. When all is saID and done,the final @R_193_4404@.m looks like this:

//  @R_193_4404@.m#import <Cocoa/Cocoa.h>#pragma mark Main methodint main(int argc,char *argv[]){	NSautoreleasePool *pool = [[NSautoreleasePool alloc] init];	pID_t parentPID = atoi(argv[2]);	ProcessSerialNumber psn;	while (GetProcessForPID(parentPID,&psn) != procNotFound)		sleep(1);	Nsstring *appPath = [Nsstring stringWithCString:argv[1] enCoding:NSUTF8StringEnCoding];	BOol success = [[NSWorkspace shareDWorkspace] openfile:[appPath stringByExpandingTildeInPath]];	if (!success)		NSLog(@"Error: Could not @R_193_4404@ application at %@",appPath);	[pool drain];	return (success) ? 0 : 1;}

And the category file looks like this:

// NSApplication+@R_193_4404@.m#import "NSApplication+@R_193_4404@.h"@implementation NSApplication (@R_193_4404@)- (voID)@R_193_4404@:(ID)sender{	Nsstring *daemonPath = [[NSBundle mainBundle] pathForResource:NSApplication@R_193_4404@Daemon ofType:nil];	[NSTask launchedTaskWithLaunchPath:daemonPath arguments:[NSArray arrayWithObjects:[[NSBundle mainBundle] bundlePath],nil]];	[self terminate:sender];}

Our @R_193_4404@ daemon will Now wait until the main application finishes terminating before @R_193_4404@ing the application.

Conclusion

@R_193_4404@ing an application isn't difficult,it just requires a bit of ingenuity. Using the code snippets above,you'll be set to automatically @R_193_4404@ your application in just about any situation. The only time you may need to modify this code is if you're restarting in order to install an application update: in this case,you'll want to copy your @R_193_4404@ daemon to a temporary directory before launching it,and then instruct the daemon to delete itself before exiting (another tip from sparkle).

Per usual,I've packaged the code in this tutorial into an Xcode project and NSApplication category that you can use in your own application. I've also included a pre-compiled copy of the @R_193_4404@ daemon,so all you really need to do is add the @R_193_4404@ tool to your app's Resources directory,and import the NSApplication category — no need to bother with the @R_193_4404@ source/target/etc. unless you want to. Happy @R_193_4404@ing!

Download the Relaunch Daemon code

总结

以上是内存溢出为你收集整理的Relaunching Your Application全部内容,希望文章能够帮你解决Relaunching Your Application所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1058532.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-25
下一篇2022-05-25

发表评论

登录后才能评论

评论列表(0条)

    保存