Nstask or Equivalent for Iphone

NSTask or equivalent for iPhone

It wasn't removed in 3.0, it was never there. There is no way to run separate processes on the iPhone. GDAL appears to be under an MIT style license and has a library interface, so directly linking it into an iPhone app shouldn't have any legal or technical issues.

What is the basic difference between NSTimer, NSTask, NSThread and NSRunloop?

Each program runs in at least one thread. You can think of each thread as a separate process of program execution, each running parallel to the others.

If you have some kind of user interface, or other code that needs to listen to events (like network ports), you need a run loop. Every NSThread automatically gets its own run loop, and you very rarely have to concern yourself with them directly. The run loop is also in charge of creating and releasing autorelease pools.

[EDIT: See comments for more discussion about autorelease pools. The most important point to keep in mind is that new threads must take care of setting up an autorelease pool. For example, methods that are invoked with detachNewThreadSelector (see below) should have the following as their first and last lines:

   NSAutoreleasePool *pool = [ [ NSAutoreleasePool alloc ] init ];
[code here]
[pool release];

The same applies for threads spawned using other techniques.]

In the main thread, where all the UI handling is taking place, the run loop is very important, since it keeps the interface reactive. That's why you should never run code that's time consuming on the main thread: it will eat up all time on the thread and the run loop will not be allowed to run often enough, resulting in a locked or slow interface. If you need to perform time consuming calculations, or keep a task running in the background, you should create a new thread. Again, you probably don't have to think about the new run loop being created. An easy way of performing a method in a new thread:

[NSThread detachNewThreadSelector:@selector(theSelector) toTarget:self withObject:nil];

Inter-thread communication can be tricky, and you should be aware of the methods performSelector:onThread:withObject:waitUntilDone: and performSelectorOnMainThread:withObject:waitUntilDone:
(Great tips on sending NSNotifications across threads here.)

Timers are also handled by run loops. In contrast to run loops, you are likely to often be using timers directly in your program. The very easiest way of creating a timer is:

[self performSelector:@selector(aSelector) withObject:nil afterDelay:1.0];

but sometimes you want to create and manage NSTimer objects yourself, for example to be able to cancel and re-use a timer.

An NSTask is used to run another program as a subprocess of the current one. It's a bit similar to starting a separate thread, but if a subprocess crashes, your main program will keep running. Communication between programs is also very different from communication between several threads in the same process.

You tagged your question with "iphone", and on the iPhone you will never be using NSTasks.

NSOperations are used when you need to handle a larger amount of different tasks, placing them in queues and/or processing them in separate threads (although they don't have to run in separate threads). If your application needs to create just a few, specialized threads, then there is no reason to use the NSOperation class. But if you will routinely generate tasks (like communicating with a server) that must be kept track of, NSOperation and NSOperationQueue will come in handy.

How to get the log from system();?

I know this isn't exactly what you asked, but perhaps there's a better way.

If you want to run a command (like open com.apple.nike), I think using NSTask is actually the best way to do that programmatically. NSTask will allow you to run commands just like system(), but has good support for handling the standard output from those commands, without having to do file I/O on the system log file.

For example, here's an example of using NSTask to list directory contents (ls -altr), and capture the output in a NSString:

- (void) listDir {
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath: @"/bin/ls"];
[task setArguments: [[NSArray alloc] initWithObjects: @"-altr", nil]];

NSPipe *pipe= [NSPipe pipe];
[task setStandardOutput: pipe];

NSFileHandle *file = [pipe fileHandleForReading];

[task launch];

NSData *data = [file readDataToEndOfFile];

NSString *output = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(@"result: %@", output);
}

This will keep the output from your open command separate from any other stuff in the system log file.

NSTask is a private API on iOS, but as is the case with many APIs that exist on OS X, they actually are available on iOS (just don't assume Apple allows them in the App Store!).

To use it, you'll need to download the NSTask.h header, and include it in your project.

Here's an old version, but I bet it still probably works.

How can I run Command Line commands or tasks with Swift in iOS?

Please read the edit number 3

Ok, so basically I figured out by myself how to do this, so I'm going to post this answer in order to help people who might have the same problem.

Note this is only available for jailbroken devices

This works on both non-jailbroken and jailbroken iOS devices

The best way to achieve this I was able to find is to use a custom Objective-C header file which created the object NSTask and everything it needs.

Then, in order to use this code with Swift, you'll need to create a Bridging-Header, in which you'll need to import the NSTask.h for it to be exposed to Swift and being able to use it in your Swift code.

Once done this, just use the following function in your code whenever you want to run a task:

func task(launchPath: String, arguments: String...) -> NSString {
let task = NSTask.init()
task?.setLaunchPath(launchPath)
task?.arguments = arguments

// Create a Pipe and make the task
// put all the output there
let pipe = Pipe()
task?.standardOutput = pipe

// Launch the task
task?.launch()
task?.waitUntilExit()

// Get the data
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue)

return output!
}

And call it like this:

task(launchPath: "/usr/bin/dpkg", arguments: "-i", packageID, "control")

This will also return the value, so you can even display it by doing:

print(task(launchPath: "/usr/bin/echo", arguments: "Hello, World!"))

Which will print:

~> Hello, World!

Hope this solves the problem.

EDIT 1: I found out that as you are using a custom NSTask object, you are allowed to run the task, even on non-jailbroken devices. Tested on both iPad Mini 2 (iOS 12.1 ~> Jailbroken) and iPhone Xr (iOS 12.2 ~> not jailbroken).

NOTE: Even though this also works on non-jailbroken devices, your App will be rejected on the AppStore, as @ClausJørgensen said:

You're using private APIs, so it'll be rejected on the App Store. Also, Xcode 11 has some new functionality that will trigger a build failure when using certain private APIs.

I'd only recommend the usage of this for apps that won't be uploaded to the App Store, otherwise, try to achieve what you want without using commands, there sure will be any other way to do that.

EDIT 2: For this to work and avoid throwing an NSInternalInconsistencyException, you'll need to set the launchPath to the executable's full path instead of just the directory containing it.

You'll also need to set all the command arguments separated by commas.

Working method (24th March 2020) (ONLY FOR JAILBROKEN DEVICES)

There's an easier method, which is roughly recommended instead of the one above, for running CLI commands on jailbroken iOS devices.

The reason why you can't use NSTask on iOS devices is that your app is sandboxed, and there's no way to avoid that on stock iOS. That's where jailbreaking takes place: using theos' New Instance Creator (NIC) and creating a new instance with the type application, you can easily create unsandboxed applications.

Note that this process is for making jailbreak packages that are installed with the Debian packager (dpkg. If you've used Debian you'll probably be familiar with that). Packages are compiled to .deb files.

Once done that, you can easily use NSTask on your app without any problem, like this:

func task(launchPath: String, arguments: String...) -> NSString {
let task = NSTask.init()
task?.setLaunchPath(launchPath)
task?.arguments = arguments

// Create a Pipe and make the task
// put all the output there
let pipe = Pipe()
task?.standardOutput = pipe

// Launch the task
task?.launch()
task?.waitUntilExit()

// Get the data
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue)

return output!
}

And call it like this:

task(launchPath: "/usr/bin/dpkg", arguments: "-i", packageID, "control")

When you finished your app, just compile it using make:

make do #just compile the package
make package #compile the package and save it on the 'packages' folder
make package install #if theos and ssh are configured properly, compile the package and automatically install it on your iPhone.

NSTask real-time monitoring output

-(void)uploadData
{
setenv([@"PASSWORD" UTF8String], [mPassword UTF8String], 1);
[task setLaunchPath:executablePathRoot];
[task setArguments:array];
NSPipe *pipe = [NSPipe pipe];
NSPipe *errorPipe = [NSPipe pipe];
[task setStandardOutput:pipe];
[task setStandardError:errorPipe];
//keeps your log where it belongs
//[task setStandardInput:[NSPipe pipe]];

NSFileHandle *outFile = [pipe fileHandleForReading];
NSFileHandle *errFile = [errorPipe fileHandleForReading];

[task launch];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(terminated:)
name:NSTaskDidTerminateNotification
object:task];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(outData:)
name:NSFileHandleDataAvailableNotification
object:outFile];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(errData:)
name:NSFileHandleDataAvailableNotification
object:errFile];

[outFile waitForDataInBackgroundAndNotify];
[errFile waitForDataInBackgroundAndNotify];
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
while(!terminated)
{
if (![[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
{
break;
}
[pool release];
pool = [[NSAutoreleasePool alloc] init];
}
[pool release];

[self appendDataFrom:outFile to:output];
[self appendDataFrom:errFile to:error];
//[task waitUntilExit];
[task release];
}

-(void) outData: (NSNotification *) notification
{
NSLog(@"outData");
NSFileHandle *fileHandle = (NSFileHandle*) [notification object];
[self appendDataFrom:fileHandle to:output];
[fileHandle waitForDataInBackgroundAndNotify]; //Checks to see if data is available in a background thread.
}

-(void) errData: (NSNotification *) notification
{
NSLog(@"errData");
NSFileHandle *fileHandle = (NSFileHandle*) [notification object];
[self appendDataFrom:fileHandle to:output];
[fileHandle waitForDataInBackgroundAndNotify];
}

- (void) terminated: (NSNotification *)notification
{
NSLog(@"Task terminated");
[[NSNotificationCenter defaultCenter] removeObserver:self];
terminated =YES;
}

Run user defined command with NSTask

It'll be far easier for you to not re-invent the command line parsing wheel. But, of course, going down the route of executing arbitrary user entered code is a security nightmare (tempered by the fact that the user has access to the system and, thus, could probably just run Terminal directly).

Specifically, have NSTask wrap an invocation of one of the shells with the command line option to have it execute an arbitrary string.

sh -c "ls -alF"

This would allow you to pass the path to sh as your launch path, which is in a fixed location on every system. The @"-c" argument tells sh to parse the next argument as a script and, of course, the next argument is whatever the user entered.

Note, this will also give the user the ability to pipe stuff, too.

Is Process always unresolved in iOS app or Playground?

if #available() does a runtime check for OS versions.

if #available(macOS 10.0, *)

evaluates to true if the code is running on macOS 10.0 or later,
or on iOS/tvOS/watchOS with an OS which is at least the minimum deployment target.

What you want is a conditional compilation, depending on the platform:

#if os(macOS)
let process = Process()
#else
// ...
#endif

Objective-C, NSTask won't touch files

You can change a file's modification time without using NSTask. Use -[NSURL setResourceValue:forKey:error:] with the key NSURLContentModificationDateKey.

As to why your use of NSTask and touch is failing, perhaps you don't have permissions to modify the file's modification time. Check the console log to see if any error was reported from touch or redirect the task's standard error output to someplace else and check that.



Related Topics



Leave a reply



Submit