章 19. Non-blocking I/O

GNUstep 支援 non-blocking I/O, 以避免在存取檔案或是網路時, 使用者介面無法被操作. 同時也免去使用多執行緒的麻煩. 在這個製做一個程式, 使用 Unix 的 find 指令來搜尋檔案. GNUstep-examples 中的 Finger 亦是個很好的參考, 本範例即以 Finger 為範本.

使用 Gorm 製做以下使用者介面, 可以改變自動調整大小的屬性以達到最好的效果.

圖形 19.1. Search 的使用者介面

Search 的使用者介面

繼承自 NSObject, 產生一個 Controller 類別及物件. 加入 "label" 和 "textView" outlets, 並連結上使用者介面. 加入 "searchAction:" action, 並將 NSTextField 連結到這個 action. 指定 Controller 為 NSOwner (NSApp) 的代理者, 存檔.

產生 Controller 的程式碼.

當使用者在 NSTextField 中按下 ENTER 鍵時, 會呼叫 -searchAction:. 因此在 -searchAction: 中使用 NSTask 來呼叫 find 來搜尋檔案. GNUstep 使用 NSTask 來執行一般 Unix 的指令, 並使用訊息傳回其結果, 以避免影響使用者操作. 以下是 -searchAction: 的程式碼:

Controller.m

- (void) searchAction: (id) sender
{
  NSString *file;
  NSArray *args;
  NSTask *task;

  file = [sender stringValue];
  args = [NSArray arrayWithObjects: NSHomeDirectory(), @"-name", file, @"-print", nil];

  ASSIGN(pipe, [NSPipe pipe]);
  task = [NSTask new];
  [task setLaunchPath: @"/usr/bin/find"];
  [task setArguments: args];
  [task setStandardOutput: pipe];
  fileHandle = [pipe fileHandleForReading];
  [[NSNotificationCenter defaultCenter] addObserver: self
                                        selector: @selector(taskEnded:)
                                        name: NSTaskDidTerminateNotification
                                        object: nil];
  [[NSNotificationCenter defaultCenter] addObserver: self
                                        selector: @selector(readData:)
                                        name: NSFileHandleReadCompletionNotification
                                        object: fileHandle];
  [fileHandle readInBackgroundAndNotify];
  [task launch];
}

在 NSTask 中設定好 Unix 指令的路徑及要傳入的參與. 將 NSTask 輸出導向 NSPipe, 再從 NSPipe 中取得 NSFileHandle, 如此便可以通過 NSFileHandle 取得 NSTask 的輸出. 接下來將 Controller 指定為訊息的接收者, 當 NSFileHandle 讀取完畢及 NSTask 結束時會收到相關訊息.

最後, 將 NSFileHandle 在背景中啟動, 並啟動 NSTask. 這是 NSFileHandle 及 NSTask 都不會干擾使用者介面的操作.

最後, 要處理相關訊息的傳入.

Controller.m

- (void) readData: (NSNotification *) not
{
  NSData *data = [[not userInfo] objectForKey: NSFileHandleNotificationDataItem]
;
  NSString *string = [[NSString alloc] initWithData: data
                                       encoding: [NSString defaultCStringEncoding]];
  [textView setString: string];
}

- (void) taskEnded: (NSNotification *) not
{
  [[NSNotificationCenter defaultCenter] removeObserver: self];
  [fileHandle closeFile];
}

當 NSTask 的結果全都出來了, 會通知 -readData:, 這是在 -searchAction: 中指定好的. 這時從 NSFileHandle 中取得其結果, 並放到 NSTextView 上. 當 NSTask 結束時, 將 Controller 從訊息中心中移除, 並關閉 NSFileHandle.

有關訊息的名稱及可用的關鍵字, 可以參考 NSFileHandle 及 NSTask 的檔頭. 程式碼在此: Search-src.tar.gz

NSFileHandle 除了可讀寫檔案之外, 亦可透過 BSD socket 來讀寫網路資料. 但是 OpenSTEP 中沒有產生 socket 的程式介面, 必需自行使用 C 程式庫來撰寫. GNUstep 提供了延伸功能來讀寫網路資料, 這部份可自行參考 NSFileHandle 的檔頭.

除了 NSFileHandle, NSURL 及 NSURLHandle 亦提供 non-blocking I/O 的方式來讀取特定 URL 的資料. 在 NSURL 中, 先製做一個接收訊息的物件 (client object), 再使用 -loadResourceDataNotifyingClient:usingCache: 在背景讀取資料. 該物件 (client object) 會自動收到訊息. 在 NSURLHandle, 也是先製做一個物件來接收訊息, 然後用 -loadInBackground 在背景取得 URL 資料. 這些大概是需要 non-blocking I/O 類別. 如果想要自行製做一個 non-blocking I/O 類別, 那要自行研究 GNUstep 的 run loop. 這就超過本書的範圍了.

這裡有一個 netclasses 類別, 專門用來處理網路介面的溝通, 適合做為伺服器程式的網路介面. 數個線上交談軟體都使用 netclasses 來處理網路的溝通. 不想使用 BSD socket 的人可以試試.