Wireframe

(Swift) Process를 이용한 외부 프로세스 이용

파이썬의 Subprocess 모듈을 사용하는 것과 비슷하게, Objective-C에서는 NSTask를 사용하여 프로세스 내에서 다른 외부 프로세스를 실행할 수 있다. Swift로 넘어오면서 NSTask는 브릿징을 통해서 그대로 사용 가능했지만, API를 좀 더 모던하게 개선한 Process라는 별도의 클래스가 새롭게 정의되었다. Process를 사용하여 외부 프로세스의 출력 결과를 가져오는 방식은 .standardOutput 프로퍼티를 이용하여 Pipe를 통해 데이터를 받을 수 있다.

외부 프로세스가 실행되는 동안 .waitUntilExit()를 통해서 프로세스 흐름을 블럭하고 기다리는 방법도 있지만, GUI를 사용하는 경우에는 UI가 멈춰버리기 때문에 .terminationHandler를 사용하여 서브 프로세스 실행이 완료되었을 때, 런루프가 지정한 적절한 시점에 콜백을 실행할 수도 있다.

파이프를 통한 결과 획득

Process 객체를 생성하고 실행할 때 standardOutput, standardInput 등의 속성에 Pipe 객체를 지정해주면, 해당 프로세스의 표준 입출력을 프로그램에서 사용할 수 있다. 프로세스는 생성한 후에 launchPatharguments 를 지정하여 실행할 명령의 경로와 인자를 전달한다. 그런다음 실행(launch)한 후에 .waitUntilExit()를 통해서 해당 프로세스가 종료될때까지 기다렸다가 파이프에 들어온 데이터를 읽으면 된다.

import Foundation

let task = Process()
let pipe = Pipe()
task.launchPath = "/bin/ls"
task.standardOutput = pipe
try? task.run()
task.waitUntilExit()
// FileHandle.availableData: Data (Data? 타입이 아님)
if case let data = pipe.fileHandleForReading.availableData, data.count > 0 {
    String(data:data, encoding:.utf8)?.split(separator:"\n").forEach{ print($0) }
}

파이프를 한 번 읽고나면 파이프는 닫히기 때문에, waitUntilExit()를 호출해서 프로세스가 완전히 끝날 때까지 대기해야한다. GUI에서는 이 메소드가 블럭하면서 UI가 프리징될 수 있기 때문에, 핸들러를 통한 처리 방식을 사용할 것이 권장된다.

핸들러를 통한 결과 획득

Process.terminationHandler 프로퍼티는 프로세스의 실행이 완료되었을 때 실행될 클로저로, 이 프로퍼티에 적절한 클로저를 지정하면 따로 블럭킹하지 않고 적절한 시점에 런루프를 통해서 결과를 처리하는 코드를 실행할 수 있다. 이 방식은 non-blocking이므로 GUI 상에서 사용한다면 더 어울리는 방식이라 할 수 있겠다.

참고로 .standardOutput은 따로 지정하지 않아도 자동으로 Pipe로 생성되지만, 프로퍼티 자체의 타입은 Any? 이므로 캐스팅하여 사용해야 한다.

import Foundation 

let task = Process()
task.executableURL = URL(fileURLWithPath:"/bin/ls")
task.terminationHandler = { (proc: Process) in 
    // Process.standardOutput의 타입은 Any? 이므로 Pipe로 캐스팅해야함
    if let data = (proc.standardOuput as? Pipe)?.fileHandleForReading.availableData,
       data.count > 0
    {
        let s = String(data:data, encoding:.utf8)!
        let filenames = s.split(separator:"\n")
        filenames.forEach{ print($0) }
    }
}

try? task.run()
task.waitUntilExit()
            

Process의 클래스 메소드 사용하기

Process를 사용하여 외부 프로세스를 실행할 때, 코드블럭(클로저)를 통해서 콜백을 처리할 수 있고, .standardOutput의 파이프 객체는 자동으로 생성된다. 따라서 클래스 메소드 run() 을 사용하여 다음과 같이 간단히 처리하는 것도 가능하다.

import Foundation

try? Process.run(URL(fileURLWithPath:"/bin/ls"), arguments:[]){ (proc) in
    if let data = (proc.standardOutput as? Pipe)?
                    .fileHandleForReading
                    .availableData, 
       data.count > 0
    {
        String(data:data, encoding:.utf8)?
          .split(separator:"\n")
          .forEach{ print($0) }
    }
}
Exit mobile version