In a iOS app, I want to use a file which have an following function using Process:
public func system(_ body: String) throws {
if #available(macOS 10.0, *) {
let process = Process()
...
} else {
fatalError()
}
}
Then, I got a fallowing error even though I applied Availability Condition and I don't evoke this function: Use of unresolved identifier 'Process'.
I tried a similar code in Playground, and I got the same error.
I learned we cannot use Process in iOS Apps with a regular way by this question: How to execute terminal commands in Swift 4? , and I have a solution that I separate these codes with files by each using platforms. But I want to use this single file if I can.
Please give me your another solution for my ideal.
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
Even though you already solved this problem, just for you to know, I want to tell you that actually, Process() (or CommandLine() in Swift 3.0 or newer) is available for iOS, but you'll need to use a custom Objective-C header file which creates the object Process()/CommandLine(), or rather 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 file for it to be exposed to Swift and being able to use it in your Swift code.
Once done this, use NSTask() instead of Process():
let process = NSTask() /* or NSTask.init() */
Or 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!
}
As you can see, NSTask() would be the equivalent to Process() in this case.
And call it like this:
task(launchPath: "/usr/bin/echo", arguments: "Hello World")
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!
For this to work and not throwing an NSInternalInconsistencyException, you'll need to set the launchPath to the executable's full path instead to just the directory containing it.
You'll also need to set all the command arguments separated by commas.
Tested on both iPad Mini 2 (iOS 12.1 ~> Jailbroken) and iPhone Xr (iOS 12.2 ~> not jailbroken).
NOTE: Even though this works both on non-jailbroken and 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.
If your app is targeting jailbroken iOS devices and will be uploaded to a third-party store like Cydia, Zebra, Thunderbolt or Sileo, then this would work correctly.
Hope this helps you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With