Close main application if "helper" application closed - objective-c

I have a wrapper application, which opens an other application, let's call it helper app. This app is inside the wrapper application Resource folder.
I would like to close the main application, after the helper app was closed. How can I achieve this?
I would like to also know, how can I quit the wrapper app, after the helper opened.

Are you developing for OSX (10.6 and later ) ?
In this case use NSRunningApplication to terminate the wrapper application.
#implementation AppDelegate // Helper Application delegate
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
NSArray *apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:#"com.yourApplication.Launcher"];
if ([apps count])
[(NSRunningApplication *)[apps objectAtIndex:0] terminate];
}
#end
And you can launch your Helper synchronously with NSWorkspace :
- (void)launchHelper // Wrapper launch method
{
NSURL *helperURL = [[NSBundle mainBundle] URLForResource:#"Helper" withExtension:#"app"];
if (helperURL)
{
NSError *err = nil;
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:helprURL options:NSWorkspaceLaunchAllowingClassicStartup configuration:nil error:&err];
// here Helper 'main' have been called
}
}

short answer - you can't!
Every application work in sandbox mode. You can call other application but you lose control on your (of course you can use AppDelegate standard method to handle enter/back foreground/bacground actions but that's it).

Related

NSOperationQueue mainQueue do not update UICollectionViewCell

While using NSURLSession to download files and display them in UICollectionView I found a problem where cells do not update correctly from NSOperationQueue block. Download part and file store works correctly every time. But update cell progress or images works only on initial load of controller. If I navigate to root and then again to my Collection controller download works but cells no longer update. I found this interesting discussion but it did not help in my case.
More details, the controller that starts URL session and handles tasks gets loaded from storyboard segue. I noticed app is creating new controller instance every time. On 2nd navigation visit cells do not show progress or update but task/files are download correctly. It seem that my download tasks are getting copy of cells from some other controller instance. But it does not make sense from what I see in debugger, delegates are working correctly, task completes with success.
Here is code method that fail to update cells on 2nd visit to controller:
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
if (totalBytesExpectedToWrite == NSURLSessionTransferSizeUnknown) {
NSLog(#"Unknown transfer size");
}
else{
// Locate the FileDownloadInfo object in array based on my unique key in URL.
int index = [self getFileDownloadInfoIndexWithTaskURL:downloadTask.originalRequest.URL.lastPathComponent];
// Get singleton download object list from app delegate
NSMutableArray *globalFileDownloadData = [self getGlobalDownloadFileList];
FileDownloadInfo *fdi = [globalFileDownloadData objectAtIndex:index];
// Get the progress view of the appropriate cell and update its progress.
NSArray *arr = [self.fileDispCollection visibleCells];
for(FileItemCell* cell in arr){
if ([fdi.contentId isEqualToString:cell.testLbl.text]){
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// This code works when app start initial visit to controller
// but does not work on2nd 3rd or other revisit
// even cells found correctly, files also loaded saved
// the progress does not show and no update
cell.downloadProgress.hidden = NO;
cell.downloadProgress.progress = fdi.downloadProgress;
}];
}
}
}
}
I initialize my singleton session using this method:
- (NSURLSession *)backgroundSession {
//disptach_once ensure that multiple background sessions are not
//created in this instance of the application.
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
NSArray *URLs = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
self.docDirectoryURL = [URLs objectAtIndex:0];
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:#"com.Transport.demo"];
sessionConfiguration.HTTPMaximumConnectionsPerHost = 3;
sessionConfiguration.discretionary = NO;
session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
});
return session;
}
What can cause a 2nd copy of cells or simply collection cell not to respond to calls or data reload on 2nd navigation round? Hope somebody have a solution for this problem.
try to put you update UI code in the main thread like this:
dispatch_async(dispatch_get_main_queue(), ^{
//your update UI code
});
The answer turned out in my question. The storyboard segue was creating new controller that was creator and handler for URLSession download tasks. Session was singleton that kept initial controller stack and delegate. So new controller instance will start downloads but all responses will go to old controller instance.
Fix was simple not use performSegueWithIdentifier, instead create single controller instance, in this case is actually better solution.
if (!self.library){
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle: nil];
self.library = [storyboard instantiateViewControllerWithIdentifier:#"libraryScene"];
}
self.library.model = self.selectNode;
[self.navigationController pushViewController:self.library animated:YES];

Phonegap Cordova call javascript functions from Objective-C

I am facing some Objective-C lackiness of knowledge in my PhoneGap App.
I have a library that I need to implement in Objective-C. This Library has some callbacks that I receive in a delegate Class ( called CCController ) :
[MyLib sharedInstance].delegate = self;
This Class is instanciated in the AppDelegate.m like this :
CCController *myClass = [CCController alloc];
[myClass init];
Then, when my Lib sends events, the functions in my CCController are called.
I need, at this point, to call my Javascript functions with a parameter.
How can I implement this ?
I have tried calling a function in AppDelegate.m which contains this :
NSString* jsString = [NSString stringWithFormat:#"myJSFunction(\"%#\");", stringParameter];
[self.viewController.webView stringByEvaluatingJavaScriptFromString:jsString];
But without success, nothing is called in my JS...
How can I implement this ? Thanks for help :)
You have to create a plugin and put the native code there instead using it in the AppDelegate.m.
From the plugin class you can do this:
NSString* jsString = [NSString stringWithFormat:#"myJSFunction(\"%#\");", stringParameter];
[self.webView stringByEvaluatingJavaScriptFromString:jsString];
Plugin development guide

NSFilePresenter methods never get called

I'm trying to write a simple (toy) program that uses the NSFilePresenter and NSFileCoordinator methods to watch a file for changes.
The program consists of a text view that loads a (hardcoded) text file and a button that will save the file with any changes. The idea is that I have two instances running and saving in one instance will cause the other instance to reload the changed file.
Loading and saving the file works fine but the NSFilePresenter methods are never called. It is all based around a class called FileManager which implements the NSFilePresenter protocol. The code is as follows:
Interface:
#interface FileManager : NSObject <NSFilePresenter>
#property (unsafe_unretained) IBOutlet NSTextView *textView;
- (void) saveFile;
- (void) reloadFile;
#end
Implementation:
#implementation FileManager
{
NSOperationQueue* queue;
NSURL* fileURL;
}
- (id) init {
self = [super init];
if (self) {
self->queue = [NSOperationQueue new];
self->fileURL = [NSURL URLWithString:#"/Users/Jonathan/file.txt"];
[NSFileCoordinator addFilePresenter:self];
}
return self;
}
- (NSURL*) presentedItemURL {
NSLog(#"presentedItemURL");
return self->fileURL;
}
- (NSOperationQueue*) presentedItemOperationQueue {
NSLog(#"presentedItemOperationQueue");
return self->queue;
}
- (void) saveFile {
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
[coordinator coordinateWritingItemAtURL:self->fileURL options:NSFileCoordinatorWritingForMerging error:&error byAccessor:^(NSURL* url) {
NSString* content = [self.textView string];
[content writeToFile:[url path] atomically:YES encoding:NSUTF8StringEncoding error:NULL];
}];
}
- (void) reloadFile {
NSFileManager* fileManager = [NSFileManager defaultManager];
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
__block NSData* content;
[coordinator coordinateReadingItemAtURL:self->fileURL options:0 error:&error byAccessor:^(NSURL* url) {
if ([fileManager fileExistsAtPath:[url path]]) {
content = [fileManager contentsAtPath:[url path]];
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self.textView setString:[[NSString alloc] initWithData:content encoding:NSUTF8StringEncoding]];
});
}
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
#end
Note, reloadFile is called in applicationDidFinishLaunching and saveFile gets called every time the save button is click (via the app delegate).
The only NSFilePresenter method that ever gets called (going by the logs) is presentedItemURL (which gets called four times when the program starts and loads the file and three times whenever save is clicked. Clicking save in a second instance has no noticeable effect on the first instance.
Can anyone tell me what I'm doing wrong here?
I was struggling with this exact issue for quite a while. For me, the only method that would be called was -presentedSubitemDidChangeAtURL: (I was monitoring a directory rather than a file). I opened a technical support issue with Apple, and their response was that this is a bug, and the only thing we can do right now is to do everything through -presentedSubitemDidChangeAtURL: if you're monitoring a directory. Not sure what can be done when monitoring a file.
I would encourage anyone encountering this issue to file a bug (https://bugreport.apple.com) to encourage Apple to get this problem fixed as soon as possible.
(I realize that this is an old question, but... :) )
First of all, I notice you don't have [NSFileCoordinator removeFilePresenter:self]; anywhere (it should be in dealloc).
Secondly, you wrote:
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
You're right: it's the incorrect implementation! And you're wrong: it's not good enough, because it's essential for methods like accommodatePresentedItemDeletionWithCompletionHandler: which take a completion block as a parameter, that you actually call this completion block whenever you implement them, e.g.
- (void) savePresentedItemChangesWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler
{
// implement your save routine here, but only if you need to!
if ( dataHasChanged ) [self save]; // <-- meta code
//
NSError * err = nil; // <-- = no error, in this simple implementation
completionHandler(err); // <-- essential!
}
I don't know whether this is the reason your protocol methods are not being called, but it's certainly a place to start. Well, assuming you haven't already worked out what was wrong in the past three years! :-)

Global events, the Mac App Store, and the sandbox

I'm working on an app where using global key-down events will be a requirement for its operation. Additionally, I plan on distributing this strictly via the App Store. (It's a Mac app, not iOS.) I've gotten an example of listening for the global events working via addGlobalMonitorForEventsMatchingMask, but with caveats.
Note: I am making the choice to use the modern API's and not rely on the earlier Carbon hotkey methods. In the event that they are deprecated eventually, I don't want to have to figure this problem out later.
The principle issue is that the app has to be trusted in order for global events to be detected. Otherwise, accessibility has to be enabled for all apps. When I enable accessibility, events are detected successfully. This requirement is documented here, https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/MonitoringEvents/MonitoringEvents.html.
I would prefer that for my users, they will not have to enable accessibility. From other research I've done, you can get an application to be trusted by calling AXMakeProcessTrusted, then restarting the application.
In the code that I'm using, I do not get an authentication prompt. The app will restart, but is still not trusted (likely because I don't get an authentication prompt). Here's my code for this part:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
if (!AXAPIEnabled() && !AXIsProcessTrusted()) {
NSString *appPath = [[NSBundle mainBundle] bundlePath];
AXError error = AXMakeProcessTrusted( (CFStringRef)CFBridgingRetain(appPath) );
[self restartApp];
}
}
- (void)restartApp{
NSTask *task = [[NSTask alloc] init];
NSMutableArray *args = [NSMutableArray array];
[args addObject:#"-c"];
[args addObject:[NSString stringWithFormat:#"sleep %d; open \"%#\"", 3, [[NSBundle mainBundle] bundlePath]]];
[task setLaunchPath:#"/bin/sh"];
[task setArguments:args];
[task launch];
[NSApp terminate:nil];
}
Further, I've looked at the documentation for Authorization Service Tasks here https://developer.apple.com/library/archive/documentation/Security/Conceptual/authorization_concepts/03authtasks/authtasks.html#//apple_ref/doc/uid/TP30000995-CH206-BCIGAIAG.
The first thing that worries me that pops out is this info box, "Important The authorization services API is not supported within an app sandbox because it allows privilege escalation."
If this API is required to get the authentication prompt before restarting the app, it seems that I may not be able to get global events without the accessibility feature enabled.
In summary, my specific questions are:
Is there an error in my sample code about how to get the
authentication prompt to appear?
In order to get the authentication prompt to appear, am I required
to use the Authorization Services API?
Is it possible, or not possible, to have a sandboxed app that has
access to global events?
First of all, there is no way you can automatically allow an app to use accessibility API which would work in a sandbox environment and thus in app store. The recommended way is to simply guide users so they can easily enable it themselves. The new API call AXIsProcessTrustedWithOptions is exactly for that:
NSDictionary *options = #{(id) kAXTrustedCheckOptionPrompt : #YES};
AXIsProcessTrustedWithOptions((CFDictionaryRef) options);
Now, to your first and second question (just for the sake of completeness - again it won't work in sandbox):
The idea behind AXMakeProcessTrusted was that you actually create a new auxiliary application that you run as root from the main application. This utility then calls AXMakeProcessTrusted passing in the executable of the main application. Finally you have to restart the main app. The API call has been deprecated in OSX 10.9.
To spawn a new process as a root you have to use launchd using SMJobSubmit. This will prompt a user with an authentication prompt saying that an application is trying to install a helper tool and whether it should be allowed. Concretely:
+ (BOOL)makeTrustedWithError:(NSError **)error {
NSString *label = FMTStr(#"%#.%#", kShiftItAppBundleId, #"mktrusted");
NSString *command = [[NSBundle mainBundle] pathForAuxiliaryExecutable:#"mktrusted"];
AuthorizationItem authItem = {kSMRightModifySystemDaemons, 0, NULL, 0};
AuthorizationRights authRights = {1, &authItem};
AuthorizationFlags flags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
AuthorizationRef auth;
if (AuthorizationCreate(&authRights, kAuthorizationEmptyEnvironment, flags, &auth) == errAuthorizationSuccess) {
// this is actually important - if from any reason the job was not removed, it won't relaunch
// to check for the running jobs use: sudo launchctl list
// the sudo is important since this job runs under root
SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef) label, auth, false, NULL);
// this is actually the launchd plist for a new process
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man5/launchd.plist.5.html#//apple_ref/doc/man/5/launchd.plist
NSDictionary *plist = #{
#"Label" : label,
#"RunAtLoad" : #YES,
#"ProgramArguments" : #[command],
#"Debug" : #YES
};
BOOL ret;
if (SMJobSubmit(kSMDomainSystemLaunchd, (CFDictionaryRef) plist, auth, (CFErrorRef *) error)) {
FMTLogDebug(#"Executed %#", command);
ret = YES;
} else {
FMTLogError(#"Failed to execute %# as priviledged process: %#", command, *error);
ret = NO;
}
// From whatever reason this did not work very well
// seems like it removed the job before it was executed
// SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef) label, auth, false, NULL);
AuthorizationFree(auth, 0);
return ret;
} else {
FMTLogError(#"Unable to create authorization object");
return NO;
}
}
As for the restarting, this is usually done also using an external utility to which waits for a main application to finish and starts it again (by using PID). If you use sparkle framework you can reuse the existing one:
+ (void) relaunch {
NSString *relaunch = [[NSBundle bundleForClass:[SUUpdater class]] pathForResource:#"relaunch" ofType:#""];
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *pid = FMTStr(#"%d", [[NSProcessInfo processInfo] processIdentifier]);
[NSTask launchedTaskWithLaunchPath:relaunch arguments:#[path, pid]];
[NSApp terminate:self];
}
Another option is to hack the /Library/Application Support/com.apple.TCC/TCC.db sqlite database add the permissions manually using an auxiliary helper:
NSString *sqlite = #"/usr/bin/sqlite3";
NSString *sql = FMTStr(#"INSERT or REPLACE INTO access values ('kTCCServiceAccessibility', '%#', 1, 1, 1, NULL);", MY_BUNDLE_ID);
NSArray *args = #[#"/Library/Application Support/com.apple.TCC/TCC.db", sql];
NSTask *task = [NSTask launchedTaskWithLaunchPath:sqlite arguments:args];
[task waitUntilExit];
This however will disqualify the app from being app store. More over it is really just a hack and the db / schema can change any time. Some applications (e.g. Divvy.app used to do this) used this hack within the application installer post install script. This way prevents the dialog telling that an app is requesting to install an auxiliary tool.
Basically, MAS restrictions will require you to the route of having tge user turning on AX for all.
I found a potential solution on GitHub.
https://github.com/K8TIY/CW-Station
It has an auxiliary application which would be run at root to request access for the main application. It is a little outdated and is using some functions which have been deprecated so I am working on modernizing it. It looks like a good starting point.

iOS: Issue Importing Calls From Another File

I'm still new to Objective-C so I'm having a hard time with this. In my AppController, When a user clicks on one of the ads in my app, then closes the ad and returns to my app, I would like to destroy and recreate the ad (Long story as to why). For some reason though, my code isn't working. There are no errors or warnings, but it doesn't do what I intended it to do. This is what my code looks like:
#import "MoPubManager.h"
......
- (void)applicationWillEnterForeground:(UIApplication *)application
{
MoPubManager *obj = [[MoPubManager alloc] init];
if( obj.adView ) {
[[MoPubManager sharedManager] destroyBanner];
}
[obj.adView refreshAd];
}
_adView, destroy banner, and refresh ad are both in the MoPubManager file, so as you can see I imported the file and turned MoPubManager into an object. (obj.adView was originally just _adView in MoPubManager.h, but I had to switch it to obj.adView to avoid warnings.) I may just be using the wrong calls, I would post the MoPubManager.mm file where the calls originally are but its a full page or two
In the line:
MoPubManager *obj = [[MoPubManager alloc] init];
You are creating a new instance of the MoPubManager class.
Next you are checking whether the adView property of that newly instantiated class is not nil. Unless adView gets populated in the init method of MoPubManager, this will always be nil, so the destroyBanner method will not be called.
However, the destroyBanner method is being called on a sharedManager... indicating a singleton pattern in use. Therefore you should not be creating a new instance as this goes against the reason for using a singleton. (You only ever have one instance of a class when using a singleton - see this for more info)
Without seeing more code, it seems that you should be calling something like:
if( [MoPubManager sharedManager].adView ) {
[[MoPubManager sharedManager] destroyBanner];
}
[[MoPubManager sharedManager].adView refreshAd];
First, what subclass is MoPubManager... It compiles fine? The problem here seems to be that you are creating a new MoPubManager instance, but you are using a singleton to destroy it, and they don't have the same reference. You should use something diferent like:
MoPubManager *obj = [[MoPubManager alloc] init];
if( [MoPubManager sharedManager].adView ) {
[[MoPubManager sharedManager] destroyBanner];
}
[[MoPubManager sharedManager].adView refreshAd];
You are not using correctly the singleton pattern.
try this , maybe will help you
- (void)applicationWillEnterForeground:(UIApplication *)application
{
MoPubManager *obj = [MoPubManager sharedManager];
if( obj.adView ) {
[obj destroyBanner];
}
[obj.adView refreshAd];
}