Mac OSX 開發入門基礎系列之NSTask

Task(圖片來自網絡)

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

1. NSTask 與NSThread的不同

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

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

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

3. NSTask 使用

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

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


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


    設置IBOutlet 和IBAction
  • 3.3 實現保存路徑選擇的方法selectPath
@IBAction func selectPath(_ sender: NSButton) {
        // 1. 創建打開文檔面板對象
        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. 恢復按鈕狀態
            sender.state = NSOffState
        }
    }
  • 3.4 使用NSTask 調用shell,執行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}   // 如果正在執行,則返回
        isLoadingRepo = true   // 設置正在執行標記
        task = Process()     // 創建NSTask對象
        // 設置task
        task?.launchPath = "/bin/bash"    // 執行路徑(這里是需要執行命令的絕對路徑)
        // 設置執行的具體命令
        task?.arguments = ["-c","cd \(executePath); git clone \(repoPath.stringValue)"]
        
        task?.terminationHandler = { proce in              // 執行結束的閉包(回調)
            self.isLoadingRepo = false    // 恢復執行標記
            print("finished")
            self.showFiles()   // 顯示clone的倉庫文件列表
        }
        captureStandardOutputAndRouteToTextView(task!)
        task?.launch()                // 開啟執行
        task?.waitUntilExit()       // 阻塞直到執行完畢
    }
 // 顯示目錄文檔列表
    fileprivate func showFiles() {
        guard  let executePath = UserDefaults.standard.value(forKey: kSelectedFilePath) as? String else {
            print("no selected path")
            return
        }
       let listTask = Process()     // 創建NSTask對象
        // 設置task
        listTask.launchPath = "/bin/bash"    // 執行路徑(這里是需要執行命令的絕對路徑)
        // 設置執行的具體命令
       
        listTask.arguments = ["-c","cd \(executePath + "/" + (repoPath.stringValue as NSString).lastPathComponent); ls "]
    
        captureStandardOutputAndRouteToTextView(listTask)
        
        listTask.launch()                // 開啟執行
        listTask.waitUntilExit()
        
    }
  • 3.5 使用NSPipe獲取NSTask 執行的結果信息

在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. 繼續等待新數據和通知
         self.outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
               }
            }
       }

4. NSTask 與 SandBox權限

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

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


    沙盒權限

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

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

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

5. 最終效果

運行效果

6. 同步方式獲取NSTask的執行結果

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

7. 小結

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

8. 后語

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容

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