Objective-C: Multiple Views and Data Coming From An NSTask - objective-c

So, I managed to get NSTask to read asynchronously from a program, but I did it inside the class of a UIView in my storyboard. (Not an Obj-C expert)
My ideia is: I read the text from the program place it on a UITextView and then when there's more repeat the process via NSNotificationCenter
So far this is my code:
LView.m:
- (void)viewDidLoad
{
[super viewDidLoad];
NSPipe *out_pipe = [NSPipe pipe];
sshoutput = [out_pipe fileHandleForReading];
[sshoutput readInBackgroundAndNotify];
utilT = [[NSTask alloc] init];
[utilT setLaunchPath:#"/usr/bin/utilfc9"];
[utilT setArguments:[NSArray arrayWithObjects: #"-p", #"-f", #"log.txt", nil]];
[utilT setStandardOutput: out_pipe];
[utilT setStandardError: out_pipe];
[utilT launch];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(readPipe:) name:NSFileHandleReadCompletionNotification object:nil];
}
-(void)readPipe: (NSNotification *)notification
{
NSData *data;
NSString *new_input;
if( [notification object] != sshoutput ) { return };
data = [[notification userInfo] objectForKey:NSFileHandleNotificationDataItem];
new_input = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
self.log.text = [self.wifilog.text stringByAppendingFormat: #"\n%#", new_input];
if( utilT ) {
[sshoutput readInBackgroundAndNotify];
}
}
LView.h:
#import <UIKit/UIKit.h>
#import "NSTask.h"
NSTask *sshT;
NSFileHandle *sshoutput;
So far it works great, I get the data live without any issues.
But, how can I place this NSTask in a more "global" place like AppDelegate's application didFinishLaunchingWithOptions and then process the data and update multiple views in another classes? I tried and of course I can stuff like log.text = new_input inside AppDelegate because it's from another class, and including it does not solve the problem.
As you might noticed, I'm not interested in sending this to the AppStore. It's an app for myself to use on a jailbroken iPhone.
Thank you.

Quick way to do it is
In all of the Views that you want to recieve this same notification add the following
ReceiverView
-(void) viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(read:) name:#"ReadTest" object:nil];
}
//read function
-(void) read:(NSNotification*)notification
{ // Do something with the notification }
Now in LView.m
-(void)readPipe: (NSNotification *)notification
{
NSData *data;
NSString *new_input;
if( [notification object] != sshoutput ) { return };
data = [[notification userInfo] objectForKey:NSFileHandleNotificationDataItem];
new_input = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
self.log.text = [self.wifilog.text stringByAppendingFormat: #"\n%#", new_input];
if( utilT ) {
[sshoutput readInBackgroundAndNotify];
}
//Add the following
[[NSNotificationCenter defaultCenter] postNotificationName:#"ReadTest" object:notification]
}

Be carefull, new_input is allocated but not released => Memory Leak

Related

How do I run a binary from a Cocoa app?

I'm making a simple launcher for a binary so I can have it appear in my launchpad.
My initial thought was that running system("./myProgram"); would be sufficient, but it doesn't appear to actually do anything as the terminal instance it runs doesn't stay open after running the command, immediately shutting down whatever other tasks the program did.
So my question is, is there a way for me to do this that keeps it open indefinitely?
Edit: I want my launcher to close immediately after launching the program, so it would be less than ideal to rely on something that requires it to stay open
Edit: the following all work, but only when run from xcode, when running it stand-alone it doesn't launch the program at all
system("open /Applications/Utilities/Terminal.app myProgram");
system("open myProgram");
system("/bin/sh -c ./myProgram&");
system("./myProgram&");
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/bash"];
[task setArguments: #[#"-c", #"./myProgram"]];
[task launch];
NSTask does not give any errors, and it doesn't throw any exceptions either when the app runs
Literally every other aspect of the program works, it just won't launch, and it won't say why
Based on all the "feedback" here's what I got so far. And it still doesn't work unless I provide an absolute path (which is no good in case I want to move it later)
//
// AppDelegate.m
// DFLauncher
//
// Created by Electric Coffee on 11/02/15.
// Copyright (c) 2015 Electric Coffee. All rights reserved.
//
#import "AppDelegate.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#end
NSString *CURRENT_DIR;
NSString *FILE_PATH;
NSString *INIT_PATH = #"/data/init/init.txt";
NSString *VOLUME_ON = #"[SOUND:YES]";
NSString *VOLUME_OFF = #"[SOUND:NO]";
BOOL contains(NSString *a, NSString *b) {
return [a rangeOfString: b].location != NSNotFound;
}
NSData *replaceString(NSString *fileContents, NSString *from, NSString *to) {
return [[fileContents stringByReplacingOccurrencesOfString: from withString: to]
dataUsingEncoding: NSUTF8StringEncoding];
}
#implementation AppDelegate
- (void)applicationDidFinishLaunching: (NSNotification *)aNotification {
CURRENT_DIR = [[NSFileManager new] currentDirectoryPath]; //[[NSBundle mainBundle] bundlePath];
//NSLog(#"%#", CURRENT_DIR);
FILE_PATH = [CURRENT_DIR stringByAppendingString: INIT_PATH];
_fileContents = [NSString stringWithContentsOfFile: FILE_PATH
encoding: NSUTF8StringEncoding
error: NULL];
if (contains(_fileContents, VOLUME_OFF))
[_toggleMute setState: YES];
if (contains(_fileContents, VOLUME_ON))
[_toggleMute setState: NO];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
}
- (IBAction)playButtonClick: (id)sender {
//system("open /Applications/Utilities/Terminal.app df"); // doesn't quite work
//system("open /Applications/df_osx/df");
//system("/bin/sh -c /Applications/df_osx/df&");
//system("/Applications/df_osx/df&");
NSString *gamePath = [CURRENT_DIR stringByAppendingString: #"/df&"];
NSTask *task = [NSTask new];
[task setLaunchPath: #"/bin/bash"];
[task setArguments: #[#"-c", gamePath]];
NSError *error = task.standardError;
[task launch];
[NSAlert alertWithError: error];
//[NSApp terminate: self];
}
- (IBAction)folderButtonClick: (id)sender {
system("open .");
}
- (IBAction)quitButtonClick: (id)sender {
[NSApp terminate: self];
}
- (IBAction)mute: (id)sender {
NSData *result;
NSFileManager *fm = [NSFileManager defaultManager];
if ([sender state] == NSOffState)
result = replaceString(_fileContents, VOLUME_OFF, VOLUME_ON);
else
result = replaceString(_fileContents, VOLUME_ON, VOLUME_OFF);
[fm createFileAtPath: FILE_PATH contents: result attributes: nil];
}
#end
Hacky solution that works (but isn't elegant at all)
I had to replace
FILE_PATH = [CURRENT_DIR stringByAppendingString: INIT_PATH];
With
CURRENT_DIR = [[[[NSBundle mainBundle] bundlePath] stringByDeletingPathExtension] stringByDeletingLastPathComponent];
To get the correct path for the file, but it works now.

echo Does Not Work with NSTask and readInBackgroundAndNotify

I have the following Obj-C code and its log output. Can anyone tell me why I'm not getting any output from the NSFileHandle?
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[self performSelectorInBackground:#selector(startTask:) withObject:nil];
}
- (void) startTask: (id) sender
{
NSPipe *pipe = [[NSPipe alloc] init];
NSFileHandle *fh = pipe.fileHandleForReading;
[fh readInBackgroundAndNotify];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(output:) name:NSFileHandleReadCompletionNotification object:fh];
NSTask *echoTask = [[NSTask alloc] init];
echoTask.standardOutput = pipe;
echoTask.standardError = [[NSPipe alloc] init];
echoTask.launchPath = #"/bin/echo";
echoTask.arguments = #[#"hello world!"];
NSLog(#"launching...");
[echoTask launch];
[echoTask waitUntilExit];
NSLog(#"finished.");
}
- (void) output:(NSNotification *)notification
{
NSFileHandle *fh = notification.object;
NSLog(#"fh: %#", fh);
NSString *output = [[NSString alloc] initWithData:[fh readDataToEndOfFile] encoding:NSUTF8StringEncoding];
NSLog(#"output: '%#'", output);
}
#end
log:
2014-12-16 10:19:58.154 SubProcess2[14893:704393] launching...
2014-12-16 10:19:58.165 SubProcess2[14893:704393] fh: <NSConcreteFileHandle: 0x6080000e9e80>
2014-12-16 10:19:58.165 SubProcess2[14893:704393] output: ''
2014-12-16 10:19:58.166 SubProcess2[14893:704393] finished.
If I do it synchronously or using the approach in https://stackoverflow.com/a/16274541/1015200 I could get it to work.
Any other techniques and variations(such as launching task without performSelectorInBackground) have failed.
I really want to see if I can get it to work using the notification.
So if I can get any help that'd be great.
The data that has already been read is passed to the notification in the userInfo dictionary under the key NSFileHandleNotificationDataItem, you should be accessing that and not attempting to read further data. E.g. something like:
- (void) output:(NSNotification *)notification
{
NSString *output = [[NSString alloc]
initWithData:notification.userInfo[NSFileHandleNotificationDataItem]
encoding:NSUTF8StringEncoding];
NSLog(#"output: '%#'", output);
}
HTH

Async execution of shell command not working properly

So, this is my code :
- (void)runCmd:(NSString *)cmd withArgs:(NSArray *)args
{
NSLog(#"\nRunning ::\n\tCmd : %#\n\tArgs : %#",cmd, args);
[theSpinner start];
if (task)
{
[task interrupt];
}
else
{
task = [[NSTask alloc] init];
[task setLaunchPath:cmd];
[task setArguments:args];
[pipe release];
pipe = [[NSPipe alloc] init];
[task setStandardOutput:pipe];
NSFileHandle* fh = [pipe fileHandleForReading];
NSNotificationCenter* nc;
nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self];
[nc addObserver:self
selector:#selector(dataReady:)
name:NSFileHandleReadCompletionNotification
object:fh];
[nc addObserver:self selector:#selector(dataAvailable:) name:NSFileHandleDataAvailableNotification object:fh];
[nc addObserver:self
selector:#selector(taskTerminated:)
name:NSTaskDidTerminateNotification
object:task];
[task launch];
[fh readInBackgroundAndNotify];
}
}
- (void)dataAvailable:(NSNotification*)n
{
NSLog(#"Data Available : %#",n);
}
- (void)dataReady:(NSNotification*)n
{
NSData* d;
d = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem];
NSLog(#"Data Ready : %#",n);
if ([d length])
{
NSLog(#"Data Ready : %#",[[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding]);
}
}
- (void) taskTerminated:(NSNotification*)note
{
NSLog(#"Task Terminated : %#",note);
[task release];
task = nil;
[theSpinner stop];
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
[alert setMessageText:[NSString stringWithFormat:#"Command finished"]];
[alert runModal];
}
I've tried running a command (e.g. the php interpreter at /usr/bin/php) with arguments (e.g. the file to be interpreted by php test.php).
The thing is :
The script runs fine
BUT, I'm receiving a Data Ready and Task Terminated
notification BEFORE I've managed to get all the output. (I mean,
the dataReady: function fetches just the first part of the
output and the rest of it is nowhere to be found...)
I basically want to be reading, asynchronously, all output - WHILE the command is running.
Any Ideas? What am I doing wrong?
Thanks!
You use readInBackgroundAndNotify to schedule your reading. This method reads only one buffer full of data and notifies. You either need to call readInBackgroundAndNotify in your notification method again to read more data or you need to use readToEndOfFileInBackgroundAndNotify if you want to receive all the data at once.
There's a new API since 10.7, so you can avoid using NSNotifications.
task.standardOutput = [NSPipe pipe];
[[task.standardOutput fileHandleForReading] setReadabilityHandler:^(NSFileHandle *file) {
NSData *data = [file availableData]; // this will read to EOF, so call only once
NSLog(#"Task output! %#", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
// if you're collecting the whole output of a task, you may store it on a property
[self.taskOutput appendData:data];
}];
IMPORTANT:
When your task terminates, you have to set readabilityHandler block to nil; otherwise, you'll encounter high CPU usage, as the reading will never stop.
[task setTerminationHandler:^(NSTask *task) {
// do your stuff on completion
[task.standardOutput fileHandleForReading].readabilityHandler = nil;
[task.standardError fileHandleForReading].readabilityHandler = nil;
}];

Why is my value passed through NSNotifcationCenter not preserved?

I'm trying to send a CGPoint through an NSNotification like this
-(void)setPosition:(CGPoint)point
{
NSString *pointString = NSStringFromCGPoint(point);
NSDictionary *dict = [[NSDictionary alloc]
initWithObjectsAndKeys:#"p", pointString, nil];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"BownceSpriteDidSetPosition"
object:self
userInfo:dict];
[super setPosition:CGPointMake(point.x, point.y)];
}
And I've implemented the observer like this
-(void) init
{
if((self = [self init])){
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(setViewPointCenter:)
name:#"BownceSpriteDidSetPosition"
object:nil];
// I wondered wether 'object' should be something else???
// more code etc....
}
return self
}
-(void) setViewPointCenter:(NSNotification *)notification
{
NSString * val = [[notification userInfo] objectForKey:#"p"];
CGPoint point = CGPointFromString(val);
// trying to debug
NSString debugString = [NSString stringWithFormat:#"YPOS -----> %f", point.y];
NSLog(debugString);
CGPoint centerPoint = ccp(240, 160);
viewPoint = ccpSub(centerPoint, point);
self.position = viewPoint;
}
But it seems that CGPoint is empty, or (0,0) maybe. Either way, it's not having the desired effect, and the debugString is showing point.y to be 0.0.
From all the examples I've found, it looks to me like I'm doing it all right. But obviously I'm not. Can anyone nudge me in the right direction and point out my mistake?
You've got your objects and keys reversed in the dictionary. It should read
NSDictionary *dict = [[NSDictionary alloc]
initWithObjectsAndKeys:pointString,#"p", nil];
Yes, it's exactly backwards of the way you would expect it to be and this bites me about every third time I create a dictionary.
Your problem is here:
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:#"p", pointString, nil];
It should be:
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:pointString, #"p", nil];
"Objects" comes before "Keys" in the selector, so you list your items as ObjectA, KeyForObjectA, ObjectB, KeyForObjectB, etc.
You're also leaking this dictionary, since you alloc/init it, but never release it (I'm assuming you're not using garbage collection).
In new objective-c syntax is better to use:
NSDictionary *dict = #{#"p": [NSValue valueWithCGPoint:point]};
it easier to understend and it use NSValue instead of NSString.
There is also a problem with removing observer. In your code, you only use [[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(setViewPointCenter:) name:#"BownceSpriteDidSetPosition" object:nil]; but never call [[NSNotificationCenter defaultCenter] removeObserver:self];, whitch can produce nasty crash, that would be hard to debug. I sugest you using library https://github.com/AllinMobile/AIMObservers that prevents this kind of crash. You could rewrite your code in that way:
__weak __typeof(self) weakSelf = self;
self.observer = [AIMNotificationObserver observeName:#"BownceSpriteDidSetPosition" onChange:^(NSNotification *notification) {
NSValue *valueOfPoint = [notification userInfo][#"p"];
CGPoint point = [valueOfPoint CGPointValue];
CGPoint centerPoint = ccp(240, 160);
viewPoint = ccpSub(centerPoint, point);
//use weakSelf to avoid strong reference cycles
weakSelf.position = viewPoint;
}];

NSFileHandle readInBackgroundAndNotify does not work

Hi I'm using NSFileHandle's readInBackgroundAndNotify method to get notifications when a log file has been updated.
I have the following code:
- (void)startReading
{
NSString *logPath = [NSHomeDirectory() stringByAppendingPathComponent:#"Library/Logs/MyTestApp.log"];
NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:logPath];
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:#selector(getData:)
name:NSFileHandleReadCompletionNotification
object:fh];
[fh readInBackgroundAndNotify];
}
- (void) getData: (NSNotification *)aNotification
{
NSLog(#"notification received");
}
However the selector is never called and the notification is not received.
Add an NSLog to startReading to make sure that's getting called.
Log fh. My guess is that it's nil (most probably because you haven't created MyTestApp.log yet).