Mac OSX 開發(fā)入門基礎系列之NSTask

Task(圖片來自網絡)

利用NSTask,我們可以在應用中調用外部程序或腳本并獲得它的<執(zhí)行狀態(tài)和結果
NSTask最為常用的一個場景是為命令行操作提供圖形化的界面

1. NSTask 與NSThread的不同

  • NSTask會創(chuàng)建隔離的可運行實體,但執(zhí)行權限受App沙盒限制
  • NSTask不與創(chuàng)建的它的進程共享內存空間
  • NSTask實例在運行時,環(huán)境條件不能改變,需要在運行之前進行配置
  • 一個NSTask實例只能運行一次,再次調用會報錯
  • NSTask默認是異步執(zhí)行,如果有同步需求,可調用waitUntilExit()方法

2. NSTask 在Swift 中與Objective-C中的不同

  • Objective-C中, 是NSTask類
  • Swift 中, 是Process類

3. NSTask 使用

我們通過創(chuàng)建一個簡單的克隆Git倉庫的工程來熟悉NSTask的使用
如果你比較捉急,可以提前從這里下載NSTaskDemo

  • 3.1 創(chuàng)建工程(本示例使用Swift,并默認你已經熟悉基本的OSX UI開發(fā)),并設置好UI界面,效果如下:


    UI界面
  • 3.2 打開ViewController.swift,設置控件的連線屬性以及方法:


    設置IBOutlet 和IBAction
  • 3.3 實現(xiàn)保存路徑選擇的方法selectPath
@IBAction func selectPath(_ sender: NSButton) {
        // 1. 創(chuàng)建打開文檔面板對象
        let openPanel = NSOpenPanel()
        // 2. 設置確認按鈕文字
        openPanel.prompt = "Select"
        // 3. 設置禁止選擇文件
        openPanel.canChooseFiles = true
        // 4. 設置可以選擇目錄
        openPanel.canChooseDirectories = true
        // 5. 彈出面板框
        openPanel.beginSheetModal(for: self.view.window!) { (result) in
            // 6. 選擇確認按鈕
            if result == NSModalResponseOK {
                // 7. 獲取選擇的路徑
                self.savePath.stringValue = (openPanel.directoryURL?.path)!
                // 8. 保存用戶選擇路徑(為了獲取訪問權限)
                UserDefaults.standard.setValue(openPanel.url?.path, forKey: kSelectedFilePath)
                UserDefaults.standard.synchronize()
            }
            // 9. 恢復按鈕狀態(tài)
            sender.state = NSOffState
        }
    }
  • 3.4 使用NSTask 調用shell,執(zhí)行git clone命令
@IBAction func startPull(_ sender: NSButton) {
        guard  let executePath = UserDefaults.standard.value(forKey: kSelectedFilePath) as? String else {
            print("no selected path")
            return
        }
        guard repoPath.stringValue != "" else {return}
        if isLoadingRepo {return}   // 如果正在執(zhí)行,則返回
        isLoadingRepo = true   // 設置正在執(zhí)行標記
        task = Process()     // 創(chuàng)建NSTask對象
        // 設置task
        task?.launchPath = "/bin/bash"    // 執(zhí)行路徑(這里是需要執(zhí)行命令的絕對路徑)
        // 設置執(zhí)行的具體命令
        task?.arguments = ["-c","cd \(executePath); git clone \(repoPath.stringValue)"]
        
        task?.terminationHandler = { proce in              // 執(zhí)行結束的閉包(回調)
            self.isLoadingRepo = false    // 恢復執(zhí)行標記
            print("finished")
            self.showFiles()   // 顯示clone的倉庫文件列表
        }
        captureStandardOutputAndRouteToTextView(task!)
        task?.launch()                // 開啟執(zhí)行
        task?.waitUntilExit()       // 阻塞直到執(zhí)行完畢
    }
 // 顯示目錄文檔列表
    fileprivate func showFiles() {
        guard  let executePath = UserDefaults.standard.value(forKey: kSelectedFilePath) as? String else {
            print("no selected path")
            return
        }
       let listTask = Process()     // 創(chuàng)建NSTask對象
        // 設置task
        listTask.launchPath = "/bin/bash"    // 執(zhí)行路徑(這里是需要執(zhí)行命令的絕對路徑)
        // 設置執(zhí)行的具體命令
       
        listTask.arguments = ["-c","cd \(executePath + "/" + (repoPath.stringValue as NSString).lastPathComponent); ls "]
    
        captureStandardOutputAndRouteToTextView(listTask)
        
        listTask.launch()                // 開啟執(zhí)行
        listTask.waitUntilExit()
        
    }
  • 3.5 使用NSPipe獲取NSTask 執(zhí)行的結果信息

在Swift中,NSPipe 被改名為Pipe

     extension ViewController{
      fileprivate func captureStandardOutputAndRouteToTextView(_ task:Process) {
     //1. 設置標準輸出管道
     outputPipe = Pipe()
     task.standardOutput = outputPipe
     
     //2. 在后臺線程等待數據和通知
     outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
     
     //3. 接受到通知消息
     NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: outputPipe.fileHandleForReading , queue: nil) { notification in
         
         //4. 獲取管道數據 轉為字符串
         let output = self.outputPipe.fileHandleForReading.availableData
         let outputString = String(data: output, encoding: String.Encoding.utf8) ?? ""
         if outputString != ""{
             //5. 在主線程處理UI
             DispatchQueue.main.async(execute: {
                 let previousOutput = self.showInfoTextView.string ?? ""
                 let nextOutput = previousOutput + "\n" + outputString
                 self.showInfoTextView.string = nextOutput
                 // 滾動到可視位置
                 let range = NSRange(location:nextOutput.characters.count,length:0)
                 self.showInfoTextView.scrollRangeToVisible(range)
             })
         }            
         //6. 繼續(xù)等待新數據和通知
         self.outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
               }
            }
       }

4. NSTask 與 SandBox權限

在NSTaskDemo示例工程中,開啟了App 的沙盒權限,

  • 開啟網絡訪問權限
  • 開啟了用戶選擇文件的讀寫權限


    沙盒權限

在osx 系統(tǒng)中 ,沙盒有個規(guī)則:在App運行期間通過NSOpenPanel用戶手動打開的任意位置的文件,把這個這個路徑保存下來,后面都是可以直接用這個路徑繼續(xù)訪問文件,但當App退出后再次運行,這個路徑默認是不可以訪問的

關于OSX的沙盒機制,推薦學習這篇文檔[Cocoa開發(fā)之沙盒機制及訪問Sandbox之外的文件

推薦文檔的補充說明: 永久訪問用戶授權的url,可以不必在.entitlements文件中填寫對應的key與value
(測試環(huán)境osx 10.12.5 ,Xcode 8.3.3)

5. 最終效果

運行效果

6. 同步方式獲取NSTask的執(zhí)行結果

func execCmd(cmd: String, arguments: [String]) -> String{
    let task = Process()     // 創(chuàng)建NSTask 實例
    task.launchPath = cmd        // 需要執(zhí)行的命令
    task.arguments = arguments    // 命令參數
    let output = Pipe()                  // 創(chuàng)建輸出實例
    task.standardOutput = output          // 設置輸出
    task.launch()                                     // 執(zhí)行命令
    task.waitUntilExit()                          // 等待退出
    let data = output.fileHandleForReading.readDataToEndOfFile()      // 獲取執(zhí)行結果數據
    return String(data: data, encoding: String.Encoding.utf8) ?? ""     // 返回結果
}

7. 小結

NSTask為我們提供了可以在一個應用中,調用另一個應用<的可能.其中比較普遍的一個使用場景是我們可以在自己的App中,調用強大的Shell命令,或者執(zhí)行自己寫的腳本來實現(xiàn)一些輔助功能
NSPipe用來輔助我們獲取NSTask的輸出結果,用來展示UI信息

8. 后語

關于NSTask的使用并不十分復雜,但如果想實現(xiàn)強大的需求,最好有一些必備的Shell編程知識,另外值得注意就是沙盒權限問題,文中的一下疑問或者意見,大家可以寫在評論區(qū)進行討論,最后希望大家周末愉快~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 15,664評論 4 61
  • 星期六 今天,我和姐姐一起去超市買做壽司的材料。我很高興,因為我姐姐要教我做壽司了,材料分別是:火腿腸、肉松、紫菜...
    龍之塵閱讀 177評論 0 0
  • 依賴 gethub鏈接地址 布局 adapter
    凱玲之戀閱讀 534評論 0 0
  • 作為一名商科學子,對投行offer心馳神往再正常不過。門檻高、薪水高、工作強度大,基本詮釋了小朋友們對投行工作的初...
    木木丹閱讀 21,601評論 2 9
  • 那歷史是給普通人看的嗎?那不過是洗腦工具。 有了文學,就不一樣了。 杜工部《三吏三別》就是活生生的歷史,比任何史書...
    邊城v浪子閱讀 246評論 0 0

友情鏈接更多精彩內容