Mac OS X不使用輪詢來(lái)監(jiān)控進(jìn)程的生命周期

做mac應(yīng)用開(kāi)發(fā)與IOS一個(gè)很大的不同,是多進(jìn)程,一個(gè)應(yīng)用中存在多個(gè)的進(jìn)程。很多時(shí)候我們都有監(jiān)控進(jìn)程的需求,下面就介紹多種OSX監(jiān)控進(jìn)程的方式,總有一種適合你。

簡(jiǎn)介

做一段時(shí)間的Mac OS X開(kāi)發(fā)之后,你將不可避免的遇到需要?jiǎng)?chuàng)建協(xié)作進(jìn)程的情況,例如:

  • 在寫(xiě)一個(gè)應(yīng)用程序中,你可能會(huì)想要將一些代碼封裝到一個(gè)獨(dú)立的輔助進(jìn)程中去。或許你想要將一些不可靠的代碼放到一個(gè)獨(dú)立的進(jìn)程中去,這樣,這個(gè)進(jìn)程崩潰了也不會(huì)影響主程序的運(yùn)行。又或者你可能想要訪問(wèn)某些不是線程安全的API,而不會(huì)鎖定應(yīng)用程序的用戶界面。
  • 您可能正在編寫(xiě)一套合作應(yīng)用程序。也許你正在編寫(xiě)一個(gè)文字處理器,并且想要調(diào)用一個(gè)單獨(dú)的公式編輯器服務(wù)。
  • 如果您正在編寫(xiě)一個(gè)守護(hù)進(jìn)程,則可能需要與可訪問(wèn)每個(gè)用戶狀態(tài)的各種代理程序進(jìn)行交互。

一旦你有多個(gè)進(jìn)程,就不可避免的遇到進(jìn)程生命周期的問(wèn)題:也就是說(shuō),一個(gè)進(jìn)程需要知道另一個(gè)進(jìn)程是否在運(yùn)行。這篇文檔描述了多種可以在進(jìn)程啟動(dòng)或者終止時(shí)通知你的方法。它分為兩個(gè)主要部分。監(jiān)控一個(gè)你自己?jiǎn)?dòng)的進(jìn)程,監(jiān)控一個(gè)不是你自己?jiǎn)?dòng)的進(jìn)程。最后,進(jìn)程的序列號(hào)中包含了一些本文中討論的進(jìn)程序列號(hào)的基礎(chǔ)API的重要信息。

首先,讓我們談?wù)撘粋€(gè)提供了大量關(guān)鍵優(yōu)勢(shì)的替代方法。

重要提示:所有本文中討論的技術(shù)都會(huì)在事件發(fā)生變化時(shí)通知你。通過(guò)輪詢進(jìn)程列表可以獲得相同的信息,但輪詢通常是一個(gè)壞主意(它消耗CPU時(shí)間,減少電池壽命,增加你設(shè)置進(jìn)程的工作量,并且還會(huì)增加響應(yīng)事件的延遲。)

面向服務(wù)的替代方案

監(jiān)控進(jìn)程生命周期的最常見(jiàn)原因之一是該進(jìn)程為您提供一些服務(wù)。例如,一個(gè)電影轉(zhuǎn)碼應(yīng)用程序,該程序通常會(huì)將實(shí)際的轉(zhuǎn)碼工作放到一個(gè)子進(jìn)程中去執(zhí)行,主進(jìn)程負(fù)責(zé)監(jiān)控子進(jìn)程的工作狀態(tài),一旦子進(jìn)程意外退出了,主進(jìn)程可以重新啟動(dòng)它。

你可以通過(guò)重新構(gòu)思你的方法來(lái)避免這個(gè)需求。與其明確的管理你的輔助進(jìn)程狀態(tài),還不如將其重新定義為應(yīng)用程序所需要的服務(wù),然后通過(guò)launchd來(lái)管理該服務(wù),它將負(fù)責(zé)啟動(dòng)和終止提供該服務(wù)的進(jìn)程的所有細(xì)節(jié)。

關(guān)于面向服務(wù)的更全面的討論,可以閱讀launchd相關(guān)的文檔。

監(jiān)控自己?jiǎn)?dòng)的進(jìn)程

有許多種不同的方式監(jiān)控自己?jiǎn)?dòng)的進(jìn)程,每種技術(shù)都各有利弊,閱讀下面的內(nèi)容,以選擇一個(gè)最適合自己情況的。

NSTask

NSTask可以輕松的啟動(dòng)一個(gè)幫助進(jìn)程并等待它結(jié)束。你可以同步等待(使用-[NSTask waitUntilExit]方法),也可以注冊(cè)一個(gè)通知,接收NSTaskDidTerminateNotification通知。代碼清單1展示了同步方式,代碼清單2展示了異步的方式。

清單1:同步使用NSTask

- (IBAction)testNSTaskSync:(id)sender
{
    NSTask *    syncTask;

    syncTask = [NSTask 
        launchedTaskWithLaunchPath:@"/bin/sleep" 
        arguments:[NSArray arrayWithObject:@"1"]
    ];
    [syncTask waitUntilExit];
}

清單2:異步使用NSTask

- (IBAction)testNSTaskAsync:(id)sender{
 task = [[NSTask alloc] init]; 
 [task setLaunchPath:@"/bin/sleep"];
 [task setArguments:[NSArray arrayWithObject:@"1"]];
 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskExited:) name:NSTaskDidTerminateNotification object:task ];
 [task launch]; 
 //在下面的-taskExited:中繼續(xù)執(zhí)行。
}
- (void)taskExited:(NSNotification *)note{
 // 收到通知!
 [[NSNotificationCenter defaultCenter] removeObserver:self name:NSTaskDidTerminateNotification object:task ];
 [task release]; 
 task = nil;
}

進(jìn)程死亡事件

如果您使用基于序列號(hào)的API啟動(dòng)應(yīng)用程序,則可以通過(guò)注冊(cè)kAEApplicationDiedApple事件來(lái)得知其終止。

重要提示:此事件僅適用于您啟動(dòng)的應(yīng)用程序。

清單3顯示了如何注冊(cè)和處理應(yīng)用程序死亡事件。

清單3:使用進(jìn)程死亡事件

- (IBAction)testApplicationDied:(id)sender
{
    NSURL *     url;
    static BOOL sHaveInstalledAppDiedHandler;

    if ( ! sHaveInstalledAppDiedHandler ) {
        (void) AEInstallEventHandler(
            kCoreEventClass, 
            kAEApplicationDied, 
            (AEEventHandlerUPP) AppDiedHandler, 
            (SRefCon) self, 
            false
        );
        sHaveInstalledAppDiedHandler = YES;
    }

    url = [NSURL fileURLWithPath:@"/Applications/TextEdit.app"];
    (void) LSOpenCFURLRef( (CFURLRef) url, NULL);

    // Execution continues in AppDiedHandler, below.
}

static OSErr AppDiedHandler(
    const AppleEvent *  theAppleEvent, 
    AppleEvent *        reply, 
    SRefCon             handlerRefcon
)
{
    SInt32              errFromEvent;
    ProcessSerialNumber psn;
    DescType            junkType;
    Size                junkSize;

    (void) AEGetParamPtr(
        theAppleEvent, 
        keyErrorNumber, 
        typeSInt32, 
        &junkType, 
        &errFromEvent, 
        sizeof(errFromEvent), 
        &junkSize
    );
    (void) AEGetParamPtr(
        theAppleEvent, 
        keyProcessSerialNumber, 
        typeProcessSerialNumber, 
        &junkType, 
        &psn, 
        sizeof(psn), 
        &junkSize
    );

    // You've been notified!

    NSLog(
        @"died %lu.%lu %d", 
        (unsigned long) psn.highLongOfPSN, 
        (unsigned long) psn.lowLongOfPSN, 
        (int) errFromEvent
    );

    return noErr;
}

重要提示:進(jìn)程死亡事件基于序列號(hào),這是一個(gè)具有一些重要后果的事實(shí)。有關(guān)詳細(xì)信息,請(qǐng)參閱過(guò)程序列號(hào)。

UNIX方式

Mac OS X的BSD子系統(tǒng)有兩個(gè)開(kāi)啟新進(jìn)程的基本API:

  • fork和exec—這種技術(shù)起源于第一個(gè)UNIX系統(tǒng),使用fork創(chuàng)建一個(gè)進(jìn)程,是對(duì)當(dāng)前進(jìn)程的精確克隆,而exec(實(shí)際上是一個(gè)基于exec的例程簇)會(huì)使當(dāng)前進(jìn)程去啟動(dòng)運(yùn)行一個(gè)新的可執(zhí)行文件。

  • posix spawn—這個(gè)API就像fork和exec的組合。它是在Mac OS X 10.5中引入的。
    在上述兩種情況下,生成的進(jìn)程都是當(dāng)前進(jìn)程的子進(jìn)程。有兩種傳統(tǒng)的UNIX方法能知道子進(jìn)程的死亡:

  • 同步方法:使用一系列的等待例程(典型的:waitpid)

  • 異步方法:通過(guò)SIGCHLD信號(hào)(SIGCHLD,在一個(gè)進(jìn)程終止或者停止時(shí),將SIGCHLD信號(hào)發(fā)送給其父進(jìn)程,按系統(tǒng)默認(rèn)將忽略此信號(hào),如果父進(jìn)程希望被告知其子系統(tǒng)的這種狀態(tài),則應(yīng)捕捉此信號(hào)。)

在許多情況下同步等待是比較適用的方法,例如,如果父進(jìn)程在子進(jìn)程完成之前無(wú)法進(jìn)行,理所應(yīng)當(dāng)?shù)膽?yīng)該同步等待。清單4顯示了如何fork,然后exec ,再等待的示例。

清單4:Fork, exec, wait

extern char **environ;

- (IBAction)testWaitPID:(id)sender
{
    pid_t       pid;
    char *      args[3] = { "/bin/sleep", "1", NULL };
    pid_t       waitResult;
    int         status;

    // I used fork/exec rather than posix_spawn because I would like this 
    // code to be compatible with 10.4.x.

    pid = fork();
    switch (pid) {
        case 0:
            // child
            (void) execve(args[0], args, environ);
            _exit(EXIT_FAILURE);
            break;
        case -1:
            // error
            break;
        default:
            // parent
            break;
    }
    if (pid >= 0) {
        do {
            waitResult = waitpid(pid, &status, 0);
        } while ( (waitResult == -1) && (errno == EINTR) );
    }
}

另一方面,有些情況同步等待是一個(gè)非常糟糕的主意。例如,如果您正在應(yīng)用程序的主線程上面運(yùn)行,并且子進(jìn)程可能會(huì)執(zhí)行一個(gè)耗時(shí)操作,則不希望阻塞應(yīng)用程序的用戶界面等待該子進(jìn)程退出。
在這種情況下,您可以通過(guò)監(jiān)聽(tīng)SIGCHLD信號(hào)異步等待。

重要提示:如果你使用監(jiān)聽(tīng)SIGCHLD信號(hào)異步等待的方式,你仍然需要通過(guò)調(diào)用等待例程來(lái)獲取子進(jìn)程,否則將會(huì)導(dǎo)致僵尸進(jìn)程。

由于與信號(hào)處理程序相關(guān)聯(lián)的環(huán)境很復(fù)雜,可能監(jiān)聽(tīng)信號(hào)會(huì)很棘手。具體來(lái)說(shuō),如果你使用了一個(gè)信號(hào)處理程序(signalsigaction,那么你必須非常小心你在該處理程序中所做的工作。很少的函數(shù)能被信號(hào)處理程序安全的調(diào)用,例如,使用malloc分配內(nèi)存空間就是不安全的。

內(nèi)唄信號(hào)處理程序安全調(diào)用的函數(shù)(async-signal safe函數(shù))列在sigaction手冊(cè)頁(yè)上。
在大部分情況下,你必須采用額外的手段,將傳入信號(hào)重定向到更加合理的環(huán)境中。有兩種標(biāo)準(zhǔn)的做法:

  • sockets(套接字)—在此技術(shù)中,您將創(chuàng)建一個(gè)UNIX域套接字對(duì),并使用CFSocket將一端添加到您的循環(huán)中。當(dāng)信號(hào)到達(dá)時(shí),信號(hào)處理器將一個(gè)虛擬消息寫(xiě)入套接字。這將喚醒循環(huán),并允許您在安全的環(huán)境中處理信號(hào)。要查看此技術(shù)的演示,請(qǐng)查看示例代碼“CFLocalServer”中的InstallSignalToSocket例程。
  • kqueues —kqueue機(jī)制允許您收聽(tīng)信號(hào)而不安裝任何信號(hào)處理程序。所以你可以創(chuàng)建一個(gè)kqueue,指示它來(lái)監(jiān)聽(tīng)SIGCHLD信號(hào),然后將其包裝在一個(gè)CFFileDescriptor中并將其添加到你的runloop中。當(dāng)信號(hào)到達(dá)時(shí),與CFFileDescriptor關(guān)聯(lián)的回調(diào)例程運(yùn)行,您可以在安全的環(huán)境中處理信號(hào)。要查看此技術(shù)的演示,請(qǐng)查看示例代碼“PreLoginAgents”中的InstallHandleSIGTERMFromRunLoop例程。

重要提示: kqueue技術(shù)需要Mac OS X 10.5或更高版本,因?yàn)樗褂肅FFileDescriptor。

UNIX替代方案

處理SIGCHLD信號(hào)有許多陷阱。上一節(jié)描述了最深刻的一部分,但還有其他部分。當(dāng)你在寫(xiě)library code的時(shí)候,使用SIGCHLD是一件非常棘手的事情,因?yàn)镾IGCHLD由主程序本身控制,你的library code不能要求其被設(shè)置為某種方式。
有多種方法來(lái)避免SIGCHLD的這種混亂,一種方式就是創(chuàng)建一個(gè)域套接字對(duì),并且為了使子進(jìn)程具有引用一端的唯一描述符,父進(jìn)程具有另一端的描述符。當(dāng)子進(jìn)程中止的時(shí)候,系統(tǒng)關(guān)閉子進(jìn)程的描述符,這導(dǎo)致套接字的另一端指向文件的結(jié)尾(這意味著它變成可讀的了,但是當(dāng)你嘗試讀取的時(shí)候,返回的是0).當(dāng)父進(jìn)程監(jiān)控到文件結(jié)束的狀態(tài),就可以獲取子進(jìn)程信息。清單5展示了這種方案的示例。

清單5:使用套接字監(jiān)測(cè)子進(jìn)程的中止

- (IBAction)testSocketPair:(id)sender
{
    int                 fds[2];
    int                 remoteSocket;
    int                 localSocket;
    CFSocketContext     context = { 0, self, NULL, NULL, NULL };
    CFRunLoopSourceRef  rls;
    char *              args[3] = { "/bin/sleep", "1", NULL } ;

    // Create a socket pair and wrap the local end up in a CFSocket.

    (void) socketpair(AF_UNIX, SOCK_STREAM, 0, fds);

    remoteSocket = fds[0];
    localSocket  = fds[1];
    socket = CFSocketCreateWithNative(
        NULL, 
        localSocket, 
        kCFSocketDataCallBack, 
        SocketClosedSocketCallBack, 
        &context
    );
    CFSocketSetSocketFlags(
        socket, 
        kCFSocketAutomaticallyReenableReadCallBack | kCFSocketCloseOnInvalidate
    );

    // Add the CFSocket to our runloop.

    rls = CFSocketCreateRunLoopSource(NULL, socket, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
    CFRelease(rls);

    // fork and exec the child process.

    childPID = fork();
    switch (childPID) {
        case 0:
            // child
            (void) execve(args[0], args, environ);
            _exit(EXIT_FAILURE);
            break;
        case -1:
            // error
            break;
        default:
            // parent
            break;
    }

    // Close our reference to the remote socket. The only reference remaining 
    // is the one in the child. When that dies, the socket will become readable.

    (void) close(remoteSocket);

    // Execution continues in SocketClosedSocketCallBack, below.
}

static void SocketClosedSocketCallBack(
    CFSocketRef             s, 
    CFSocketCallBackType    type, 
    CFDataRef               address, 
    const void *            data, 
    void *                  info
)
{
    int             waitResult;
    int             status;

    // Reap the child.

    do {
        waitResult = waitpid( ((AppDelegate *) info)->childPID, &status, 0);
    } while ( (waitResult == -1) && (errno == EINTR) );

    // You've been notified!
}

監(jiān)控任一進(jìn)程

如果要監(jiān)控一個(gè)非自己?jiǎn)?dòng)的進(jìn)程,則可選用的方案比較少。但是,這些可以的方案就可以滿足我們大部分的需求。此外,你要根據(jù)自己的情況選擇正確的API,閱讀以下部分以讓你了解哪個(gè)API是適合你的。

NSWorkspace

NSWorkspace提供了一個(gè)非常簡(jiǎn)單的方式來(lái)監(jiān)控進(jìn)程的啟動(dòng)和退出。
要注冊(cè)這些通知,您必須:

1、獲得NSWorkspace的自定義通知中心 ,調(diào)用-[NSWorkspace notificationCenter]

2、添加NSWorkspaceDidLaunchApplicationNotification和NSWorkspaceDidTerminateApplicationNotification事件的觀察者

當(dāng)收到通知的時(shí)候,user info字典包含了受影響進(jìn)程的信息。NSWorkspace.h頭文件中列出了user info字典的key,以"NSApplicationPath"開(kāi)頭。
清單6顯示了如何使用NSWorkspace獲取應(yīng)用程序啟動(dòng)和終止的示例。

清單6:使用NSWorkspace獲取應(yīng)用程序啟動(dòng)和終止

- (IBAction)testNSWorkspace:(id)sender
{
    NSNotificationCenter *  center;

    NSLog(@"-[AppDelegate testNSWorkspace:]");

    // Get the custom notification center.

    center = [[NSWorkspace sharedWorkspace] notificationCenter];

    // Install the notifications.

    [center addObserver:self 
        selector:@selector(appLaunched:) 
        name:NSWorkspaceDidLaunchApplicationNotification 
        object:nil
    ];
    [center addObserver:self 
        selector:@selector(appTerminated:) 
        name:NSWorkspaceDidTerminateApplicationNotification 
        object:nil
    ];

    // Execution continues in -appLaunched: and -appTerminated:, below.
}

- (void)appLaunched:(NSNotification *)note
{
    NSLog(@"launched %@\n", [[note userInfo] objectForKey:@"NSApplicationName"]);

    // You've been notified!
}

- (void)appTerminated:(NSNotification *)note
{
    NSLog(@"terminated %@\n", [[note userInfo] objectForKey:@"NSApplicationName"]);

    // You've been notified!
}

重要提示: NSWorkspace基于序列號(hào),這是一個(gè)具有一些重要后果的事實(shí)。有關(guān)詳細(xì)信息,請(qǐng)參閱過(guò)程序列號(hào)

Carbon Event Manager

Carbon Event Manager 發(fā)送大量的與進(jìn)程管理相關(guān)的事件,具體來(lái)說(shuō),當(dāng)應(yīng)用程序啟動(dòng)的時(shí)候,發(fā)送kEventAppLaunched事件,當(dāng)應(yīng)用程序中止時(shí),發(fā)送kEventAppTerminated事件,你可以像任何其他Carbon事件一樣注冊(cè)這些事件。清單7顯示了一個(gè)例子。

調(diào)用事件處理程序時(shí),kEventParamProcessID參數(shù)將包含受影響的進(jìn)程的ProcesSerialNumber。

重要提示:當(dāng)您的應(yīng)用程序收到該kEventAppTerminated事件時(shí),終止應(yīng)用程序可能已經(jīng)退出。因此,您無(wú)法獲取有關(guān)該應(yīng)用程序的信息GetProcessInformation。如果您需要有關(guān)終止應(yīng)用程序的信息,則必須提前緩存。

清單7:使用Carbon事件來(lái)獲取應(yīng)用程序啟動(dòng)和終止

- (IBAction)testCarbonEvents:(id)sender
{
    static EventHandlerRef sCarbonEventsRef = NULL;
    static const EventTypeSpec kEvents[] = {
        { kEventClassApplication, kEventAppLaunched },
        { kEventClassApplication, kEventAppTerminated }
    };

    if (sCarbonEventsRef == NULL) {
        (void) InstallEventHandler(
            GetApplicationEventTarget(),
            (EventHandlerUPP) CarbonEventHandler,
            GetEventTypeCount(kEvents),
            kEvents,
            self,
            &sCarbonEventsRef
        );
    }

    // Execution continues in CarbonEventHandler, below.
}

static OSStatus CarbonEventHandler(
    EventHandlerCallRef inHandlerCallRef, 
    EventRef            inEvent, 
    void *              inUserData
)
{
    ProcessSerialNumber psn;

    (void) GetEventParameter(
        inEvent, 
        kEventParamProcessID, 
        typeProcessSerialNumber, 
        NULL, 
        sizeof(psn), 
        NULL, 
        &psn
    );
    switch ( GetEventKind(inEvent) ) {
        case kEventAppLaunched:
            NSLog(
                @"launched %u.%u", 
                (unsigned int) psn.highLongOfPSN, 
                (unsigned int) psn.lowLongOfPSN
            );
            // You've been notified!
            break;
        case kEventAppTerminated:
            NSLog(
                @"terminated %u.%u", 
                (unsigned int) psn.highLongOfPSN, 
                (unsigned int) psn.lowLongOfPSN
            );
            // You've been notified!
            break;
        default:
            assert(false);
    }
    return noErr;
}

kqueues

NSWorkspace和Carbon事件只能在單個(gè)GUI登錄上下文中工作。如果您正在編寫(xiě)一個(gè)不在GUI登錄上下文中運(yùn)行的程序(也許是守護(hù)程序),或者您需要監(jiān)視與運(yùn)行時(shí)不同的上下文中的進(jìn)程,則需要考慮替代方法。 kqueue NOTE_EXIT
事件是一個(gè)不錯(cuò)的選擇。您可以使用它來(lái)檢測(cè)進(jìn)程何時(shí)退出,無(wú)論它運(yùn)行的是哪個(gè)上下文。與NSWorkspace和Carbon事件不同,您必須準(zhǔn)確指定要監(jiān)視的進(jìn)程; 否則任何進(jìn)程的中止都無(wú)法得到通知。
清單8是一個(gè)簡(jiǎn)單的例子,說(shuō)明如何使用kqueue來(lái)監(jiān)視特定進(jìn)程的終止。

清單8:使用kqueue監(jiān)視特定進(jìn)程

static pid_t gTargetPID = -1;
    // We assume that some other code sets up gTargetPID.

- (IBAction)testNoteExit:(id)sender
{
    FILE *                  f;
    int                     kq;
    struct kevent           changes;
    CFFileDescriptorContext context = { 0, self, NULL, NULL, NULL };
    CFRunLoopSourceRef      rls;

    // Create the kqueue and set it up to watch for SIGCHLD. Use the 
    // new-in-10.5 EV_RECEIPT flag to ensure that we get what we expect.

    kq = kqueue();

    EV_SET(&changes, gTargetPID, EVFILT_PROC, EV_ADD | EV_RECEIPT, NOTE_EXIT, 0, NULL);
    (void) kevent(kq, &changes, 1, &changes, 1, NULL);

    // Wrap the kqueue in a CFFileDescriptor (new in Mac OS X 10.5!). Then 
    // create a run-loop source from the CFFileDescriptor and add that to the 
    // runloop.

    noteExitKQueueRef = CFFileDescriptorCreate(NULL, kq, true, NoteExitKQueueCallback, &context);
    rls = CFFileDescriptorCreateRunLoopSource(NULL, noteExitKQueueRef, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
    CFRelease(rls);

    CFFileDescriptorEnableCallBacks(noteExitKQueueRef, kCFFileDescriptorReadCallBack);

    // Execution continues in NoteExitKQueueCallback, below.
}

static void NoteExitKQueueCallback(
    CFFileDescriptorRef f, 
    CFOptionFlags       callBackTypes, 
    void *              info
)
{
    struct kevent   event;

    (void) kevent( CFFileDescriptorGetNativeDescriptor(f), NULL, 0, &event, 1, NULL);

    NSLog(@"terminated %d", (int) (pid_t) event.ident);

    // You've been notified!
}

進(jìn)程序列號(hào)(Process Serial Numbers)

Mac OS X具有許多用于進(jìn)程管理的高級(jí)API,可以按進(jìn)程序列號(hào)(ProcessSerialNumber)進(jìn)行處理。這些包括啟動(dòng)服務(wù),進(jìn)程管理器和NSWorkspace。這些API都有三個(gè)重要的功能:

  • 它們?cè)趩蝹€(gè)GUI登錄會(huì)話的上下文中工作。例如,如果您使用NSWorkspace來(lái)觀察正在啟動(dòng)和終止的應(yīng)用程序,那么只會(huì)在同一個(gè)GUI登錄會(huì)話中運(yùn)行的應(yīng)用程序被通知。

  • 他們只看到連接到窗口服務(wù)器的進(jìn)程。
    例如,如果您使用NSTask來(lái)運(yùn)行BSD命令行工具,如find,那么基于NSWorkspace的觀察者將不會(huì)被通知該工具的啟動(dòng)或終止。

  • 它們通常不能在GUI登錄上下文之外運(yùn)行的進(jìn)程(例如,守護(hù)程序)使用

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容