IOS 多線程 RUNLOOP 機制 (三)


四,配置Run Loop源---配置源的過程就是源的創建調用過程
配置過程分爲以下幾個階段---定義/創建(一個源)---安裝(將輸入源安裝到所在Run Loop中)---註冊(將輸入源註冊到客戶端,協調輸入源的客戶端)---調用(通知輸入源,開始工作)
4-1,定義自定義輸入源
創建自定義輸入源需要定義以下內容
1)輸入源要處理的信息
2)使感興趣的客戶端知道如何和輸入源交互的調度例程
3)處理其他任何客戶發送請求的例程
4)使輸入源失效的取消例程
Figure 3-2 Operating a custom input source
上圖的處理流程:主線程(Main Thread)發起任務(Task)給工作線程(Worker Thread),主線程會給命令緩衝區(send command-->Command Buffer),通知輸入源(signal source-->Input Source),並喚醒工作線程(Wake Up-->Worker Thread)。工作線程收到喚醒命令,Run Loop會調用輸入源的處理程序,由它來執行命令緩衝區中相應的命令。
注:因爲主線程和輸入源所在工作線程都可以訪問命令緩衝區(Command Buffer),因此這些訪問必須使同步的
1)定義輸入源(The custom input source object definition
下面代碼中,定義了RunLoopSource對象,它管理命令緩衝區,並以此來接收其他線程的消息。RunLoopContext對象是一個用來傳遞RunLoopSource對象(RunLoopSource* source)和“run loop引用”(CFRunLoopRef runLoop)給程序主線程的一個容器
======
// Listing 3-3 The custom input source object definition

@interface RunLoopSource : NSObject

{

    CFRunLoopSourceRef runLoopSource;

    NSMutableArray* commands;

}


- (id)init;

- (void)addToCurrentRunLoop;

- (void)invalidate;


// handler method

- (void)sourecFired;

// Client interface for registering commands to process

- (void)addCommand:(NSInteger)command withData:(id)data;

- (void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runLoop;


@end


// These are the CFRunLoopSourceRef callback functions.

void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);

void RunLoopSourcePerformRoutine (void *info);

void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);


// RunLoopContext is a container object used during registration of the input source.

@interface RunLoopContext : NSObject

{

    CFRunLoopRef runLoop;

    RunLoopSource* source;

}


@property (readonlyCFRunLoopRef runLoop;

@property (readonly) RunLoopSource* source;


- (id)initWithSource:(RunLoopSource*)src andLoop:(CFRunLoopRef)loop;

@end

===當將輸入源附加到run loop時,調用這個協調調度例程,將源註冊到客戶端(可以理解爲其他線程)

// Listing 3-4 Scheduling a run loop source

//當source添加進runloop的時候,調用此回調方法 <== CFRunLoopAddSource(runLoop, source, mode);

void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)

{

    RunLoopSource* obj = (RunLoopSource*)info;

    AppDelegate* del = [AppDelegate sharedAppDelegate];

    RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];

    [del performSelectorOnMainThread:@selector(registerSource:) withObject:theContext waitUntilDone:NO];

}

===在輸入源被告知(signal source)時,調用這個處理例程,這兒只是簡單的調用了 [obj sourceFired]方法

// Listing 3-5 Performing work in the input source 

//當sourcer接收到消息的時候,調用此回調方法(CFRunLoopSourceSignal(source);CFRunLoopWakeUp(runLoop);

void RunLoopSourcePerformRoutine (void *info)

{

    RunLoopSource* obj = (RunLoopSource*)info;

    [obj sourceFired];

}

===如果使用CFRunLoopSourceInvalidate/CFRunLoopRemoveSource函數把輸入源從run loop裏面移除的話,系統會調用這個取消例程,並且把輸入源從註冊的客戶端(可以理解爲其他線程)裏面移除

// Listing 3-6 Invalidating an input source == 

//當source 從runloop裏刪除的時候,調用此回調方法 <== CFRunLoopRemoveSource(runLoop, source, mode); 

void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)

{

    RunLoopSource* obj = (RunLoopSource*)info;

    AppDelegate* del = [AppDelegate sharedAppDelegate];

    RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];

    [del performSelectorOnMainThread:@selector(removeSource:) withObject:theContext waitUntilDone:YES];

}

2)安裝輸入源到Run Loop---分兩步首先初始化一個輸入源,然後將這個輸入源添加到當前Run Loop裏面

// List 3-7 Installing the run loop source

- (id)init

{

    /*

     // Setup the context.

     context.version = 0;

     context.info = self;

     context.retain = NULL;

     context.release = NULL;

     context.copyDescription = CFCopyDescription;

     context.equal = CFEqual;

     context.hash = CFHash;

     context.schedule = RunLoopSourceScheduleRoutine;

     context.cancel = RunLoopSourceCancelRoutine;

     context.perform = RunLoopSourcePerformRoutine;

     */

    CFRunLoopSourceContext context = {0selfNULLNULLNULLNULLNULL,

        &RunLoopSourceScheduleRoutine,

        &RunLoopSourceCancelRoutine,

        &RunLoopSourcePerformRoutine};

    runLoopSource = CFRunLoopSourceCreate(NULL0, &context);

    commands = [[NSMutableArray allocinit];

    return self;

}


- (void)addToCurrentRunLoop

{

    CFRunLoopRef runLoop = CFRunLoopGetCurrent();

    //Add the new CFRunLoopSourceRef to the indicated runloop, 並且回調RunLoopSourceScheduleRoutine函數

    CFRunLoopAddSource(runLoop, runLoopSourcekCFRunLoopDefaultMode);

}

3)協調輸入源的客戶端(將輸入源註冊到客戶端)

輸入源的主要工作就是將與輸入源相關聯的線程置於休眠狀態,直到有事件發生。要達到這個目的,首先客戶端(其他線程)知道有該輸入源信息,並且有辦法與之通信。

通知客戶端關於這個輸入源信息的方法之一,就是當該輸入源開始安裝到你的run loop上面後發送註冊請求給相關的客戶端,該輸入源可以註冊到任意數量的客戶端

也可以通過由代理將輸入源註冊到感興趣的客戶端

===下面顯示了應用委託(AppDelegate)定義的註冊方法及從應用委託(AppDelegate)中移除的方法

- (void)registerSource:(RunLoopContext *)sourceInfo

{

    NSMutableArray *sourceToPing;

    [sourceToPing addObject:sourceInfo];

}


- (void)removeSource:(RunLoopContext *)sourceInfo

{

    id objToRemove = nil;

    

    NSMutableArray *sourceToPing;

    

    for(RunLoopContext *context in sourceToPing)

    {

        if([context isEqual:sourceInfo])

        {

            objToRemove = context;

            break;

        }

    }

    

    if(objToRemove)

    {

        [sourceToPing removeObject:objToRemove];

    }

}

注:上面兩個函數分別在RunLoopSourceScheduleRoutine/RunLoopSourceCancelRoutine函數中被調用

4)通知輸入源---客戶端(其他線程)發數據到輸入源,分兩步首先發信號給輸入源(signal source),然後喚醒輸入源的run loop

===下面顯示了客戶端發送數據到輸入源的方法,在本例中這個方法被放在RunLoopSource對象裏面

// Listing 3-9 Waking up the run loop

- (void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runLoop

{

    //當手動調用此方法的時候,將會觸發 RunLoopSourceContextperformCallback

    CFRunLoopSourceSignal(runLoopSource);

    CFRunLoopWakeUp(runLoop);

}

注:1,輸入源就是一類事件(命令)處理機制。他是線程間的事件(命令)異步通訊機制,所以不能試圖通過這個機制實現進程間的通訊

2,因爲CFRunLoopWakeUp函數不是信號安全的,所以對run loop的喚醒,不能在應用信號處理例程(RunLoopSourcePerformRoutine)裏面使用。


4-2,配置定時源

配置源的過程其實是源在相關run loop的使用過程,包括定義(創建),安裝(添加到相關run loop),註冊(註冊到其他需要交互的線程),以及使用四個過程。但是因爲定時源在預設的時間點同步方式傳遞消息,是線程通知自己做某事情的一種方法,所以配置定時源,主要工作是創建,和安裝。其他兩個過程run loop自己就可以完成。

創建定時源有兩種方式

===一種是使用NSTimer對象創建,一種是使用CFRunLoopTimerRef對象創建,及將創建的事例安裝到Run Loop中

// Listing 3-10 Creating and scheduling timers using NSTimer

- (void)createAndScheduleTimerToRunLoopUsingNSTimer

{

    // get the current run loop

    NSRunLoop *myRunLoop = [NSRunLoop currentRunLoop];

    

    // create and schedule the first timer  === 可以配置到不同的run loop模式

    NSDate *futureDate = [NSDate dateWithTimeIntervalSinceNow:1.0];

    NSTimer *myTimer = [[NSTimer allocinitWithFireDate:futureDate interval:0.1 target:self selector:@selector(myDoFireTimer1:) userInfo:nil repeats:YES];

    [myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];

    

    // create and schedule the second timer === 只能在run loopNSDefaultRunLoopMode下有效

    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(myDoFireTimer2:) userInfo:nil repeats:YES];

}


// Listing 3-11 Creating and scheduling a timer using Core Foundation

- (void)createAndScheduleTimerToRunLoopUsingCFRunLoopTimerRef

{

    // get the current run loop

    CFRunLoopRef runLoop = CFRunLoopGetCurrent();

    CFRunLoopTimerContext context = {0NULLNULLNULLNULL};

    CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.10.300, &myCFTimerCallBack, &context);

    

    // add the CFRunLoopTimerRef to run loop kCFRunLoopCommonModes mode

    CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);

}


4-2,配置基於端口的輸入源

iOS系統提供了基於端口的輸入源對象,用以線程或進程間通訊

1)配置NSMachPort對象---本地線程間通信,通過傳遞端口對象變量進行端口間通訊

基本機制:A線程(父線程)創建NSMachPort對象,並加入A線程的run loop。當創建B線程(輔助線程)時,將創建的NSMachPort對象傳遞到主體入口點,B線程(輔助線程)就可以使用相同的端口對象將消息傳回A線程(父線程)。

A,實現A線程(父線程)的代碼

===在下面代碼中,創建一個NSPort對象,並把這個端口對象安裝(add)到線程的Run Loop裏面,同時創建一個新的線程,並把NSPort對象作爲參數傳入到新線程的主體入口點

// Listing 3-12 Main thread launch sub-thread method

- (void)lanchThread

{

    NSPort *myPort = [NSPort port];

    if(myPort)

    {

        // This class handles incoming port message

        [myPort setDelegate:self];

        

        // install the port as an input source

        [[NSRunLoop currentRunLoopaddPort:myPort forMode:NSDefaultRunLoopMode];

        

        // Detach the thread.Let the worker release the port--加載一個子線程

        [NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort:) toTarget:[MyWorkerThread Class] withObject:myPort];

    }

}

===NSPort是通過代理模式傳送消息,下面這段代碼是NSPortDelegate的代理方法,用以從B線程回傳消息((NSPortMessage *)message

#pragma mark

#define kCheckinMessage     100

#pragma mark NSPortDelegate Method

// This is the NSPort Delegate Method

// It handle responses from the worker thread(B線程)

- (void)handlePortMessage:(NSPortMessage *)message

{

    uint32_t mssgId = [message msgid];

    NSPort *distantPort = nil;

    

    if(message == kCheckinMessage)

    {

        // get the worker thread's communication port

        distantPort = [message sendPort];

        

        // Retain and save the worker port for later use

        [self storeDistantPort:distantPort];

    }

    else

    {

        // Handle other message

        ......

    }

}

B,B線程(輔助線程)的實現代碼

===下面是輔助線程的類方法,裏面創建了輔助線程實例,並把傳入的NSPort保存下來。同時,通過NSMachPort發送一個Check-In Message(NSPortMessage類型,在這個Message類型中設置了SendPort/ReceivePort,鏈接這兩個端口)給A線程。

// Listing 3-14 Launching the worker thread using Mach ports

+ (void)LaunchThreadWithPort:(id)inData

{

    NSAutoreleasePool *pool = [[NSAutoreleasePool allocinit];


    // set up the connection between this thread and the main thread

    NSPort *distantPort = (NSPort *)inData;

  

    MyWorkerThread *workObj = [[self alloc] init];

    [workObj sendCheckinMessage:distantPort];

    [distantPort release];


    // Let the run loop process things

    do

    {

        

    }while (![workerObj shouldExit]);

    

    [workObj release];

    [pool release];

}


// Listing 3-15 Sending the check-in message using Mach ports

- (void)sendCheckinMessage:(NSPort *)outPort

{

    // Retain and save the remote port for future use

    [self setRemotePort:outPort]; // set NSPort *remotePort;

    

    // create and configure the worker thread port

    NSPort *myPort = [NSMachPort port];

    [myPort setDelegate:self];

    [[NSRunLoop currentRunLoopaddPort:myPort forMode:NSDefaultRunLoopMode];

    

    // create the check-in message

    NSPortMessage *messageObj = [[NSPortMessage allocinitWithSendPort:outPort receivePort:myPort components:nil];

    if(messageObj)

    {

        // Finish configuring the message and send it immediately

        [messageObj setMsgId:setMsgid:kCheckinMessage];

        [messageObj sendBeforeDate:[NSDate date];

    }

}

===注:最終,B線程NSMachPort)端口傳遞消息給A線程的(NSPort)端口(handlePortMessage代理方法)。


2)配置NSMessagePort對象

===只能在一個設備內程序間通信,不能在不同設備間通信。將端口名稱註冊到NSMessagePortNameServer裏面,其他線程通過這個端口名稱從NSMessagePortNameServer來獲取這個端口對象。

// Listing 3-9 Waking up the run loop

- (void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runLoop

{

    //當手動調用此方法的時候,將會觸發 RunLoopSourceContextperformCallback

    CFRunLoopSourceSignal(runLoopSource);

    CFRunLoopWakeUp(runLoop);

    

    NSPort *localPort = [[NSMessagePort allocinit];

    

    // configure the port and add it to the current run loop

    [localPort setDelegate:self];

    [[NSRunLoop currentRunLoopaddPort:localPort forMode:NSDefaultRunLoopMode];

    

    // register the port using the specific name, and The name is unique

    NSString *localPortName = [NSString stringWithFormat:@"MyPortName"];

    // there is only NSMessagePortNameServer in the mac os x system

    //[[NSMessagePortNameServer sharedInstance] registerPort:localPort name:localPortName];

}

3)在Core Fundation中配置基於端口的源

CFMessagePortRef---實現本地設備內程序間通訊。CFMessagePort objects provide a communications channel that can transmit arbitrary data between multiple threads or processes on the local machine.

You create a local message port with CFMessagePortCreateLocal and make it available to other processes by giving it a name, either when you create it or later with CFMessagePortSetName. Other processes then connect to it using CFMessagePortCreateRemote, specifying the name of the port.

To listen for messages, you need to create a run loop source with CFMessagePortCreateRunLoopSource and add it to a run loop with CFRunLoopAddSource.

Important: If you want to tear down the connection, you must invalidate the port (using CFMessagePortInvalidate) before releasing the runloop source and the message port object.

Your message port’s callback function will be called when a message arrives. To send data, you store the data in a CFData object and call CFMessagePortSendRequest. You can optionally have the function wait for a reply and return the reply in another CFData object.

Message ports only support communication on the local machine. For network communication, you have to use a CFSocket object.

Functions by Task Creating a CFMessagePort Object

* CFMessagePortCreateLocal---監聽線程創建監聽端口,Returns a local CFMessagePort object. * CFMessagePortCreateRemote---工作線程用來獲取監聽線程的端口對象,Returns a CFMessagePort object connected to a remote port.

Configuring a CFMessagePort Object

* CFMessagePortCreateRunLoopSource * CFMessagePortSetInvalidationCallBack * CFMessagePortSetName

Using a Message Port

* CFMessagePortInvalidate * CFMessagePortSendRequest---工作線程給監聽端口發送請求

Examining a Message Port

* CFMessagePortGetContext * CFMessagePortGetInvalidationCallBack * CFMessagePortGetName * CFMessagePortIsRemote * CFMessagePortIsValid

Getting the CFMessagePort Type ID

* CFMessagePortGetTypeID

===主線程代碼,創建CFMessagePortRef本地端口作爲監聽端口,並註冊到當前run loop。MainThreadResponseHandler函數是消息回調函數,當有消息從工作線程傳遞過來時將調用這個函數

// Listing 3-17 Attaching a Core Foundation message port to a new thread

#define kThreadStackSize (8 *4096)


void MySpawnThread( void )

{

    // Create a local port for receiving responses.

    CFStringRef myPortName;

    CFMessagePortRef myPort;

    CFRunLoopSourceRef rlSource;

    CFMessagePortContext context = {0NULLNULLNULLNULL};

    Boolean shouldFreeInfo;

    

    // create a string for the port name

    myPortName = CFStringCreateWithFormat(NULLNULLCFSTR("com.myapp.MainThread"));

    // create the port

    myPort = CFMessagePortCreateLocal(NULL, myPortName, &MainThreadResponseHandler, &context, &shouldFreeInfo);

    if(myPort != NULL)

    {// The port was successfully created 

        // Now create a run loop source

        rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);

        if(rlSource != NULL)

        {

            // add the source to the current run loop

            CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);

            

            // once installed, these can be freed.

            CFRelease(myPort);

            CFRelease(rlSource);

        }

    }

    

    // create the thread and continue processing

    NSThread *thread = [[NSThread allocinitWithTarget:[MyThreadClass Classselector:@selector(ServerThreadEntryPoint:) object:@"com.myapp.MainThread"];

    [thread setStackSize:kThreadStackSize];

    [thread start];

}


// Listing 3-18 Receiving the checkin me

#define kCheckinMessage 100


CFDataRef MainThreadResponseHandler(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void*info)

{

    if(msgid == kCheckinMessage)

    {

        CFStringRef threadPortName;

        CFIndex bufferLength = CFDataGetLength(data);

        UInt8* buffer = CFAllocatorAllocate(NULL, bufferLength, 0);

        

        CFDataGetBytes(data, CFRangeMake(0, bufferLength), buffer);

        threadPortName = CFStringCreateWithBytes(NULL, buffer, bufferLength,kCFStringEncodingASCIIFALSE);

#if 0

        CFMessagePortRef messagePort;

        

        // You must obtain a remote message port by name for future reference

        messagePort = CFMessagePortCreateRemote(NULL, threadPortName);

        if(messagePort)

        {

            // Retain and save the thread's com port for future reference

            AddPortToListOfActiveThreads(messagePort);

            // Since the port is retained by the previous function, release it here

            CFRelease(messagePort);

        }

#endif

        // clean up

        CFRelease(threadPortName);

        CFAllocatorDeallocate(NULL, buffer);

    }

    else

    {

        // handle other messages

    }

    return NULL;

}

===工作線程代碼,獲取主線程端口,通過CFMessagePortSendRequest傳遞消息到主線程端口

// Listing 3-19 Setting up the thread structures

+ (void)ServerThreadEntryPoint:(NSString *)name

{

    // create the remote port to main thread

    CFMessagePortRef mainThreadPort;

    CFStringRef portName = (CFStringRef)name;

    mainThreadPort = CFMessagePortCreateRemote(NULL, portName);

    

#if 1

    // Create a port for the worker thread.

    CFStringRef myPortName = CFStringCreateWithFormat(NULLNULLCFSTR("com.MyApp.Thread-%d"),CFRunLoopGetTypeID());

    //  Store  the  port  in  this  thread’s  context  info  for  later  reference.

    CFMessagePortContext context = {0, mainThreadPort, NULLNULLNULL};

    

    Boolean shouldFreeInfo;

    Boolean shouldAbort = TRUE;

    CFMessagePortRef myPort = CFMessagePortCreateLocal(NULL, myPortName, &ProcessClientRequest, &context, &shouldFreeInfo);

    

    if(!shouldFreeInfo)

    {

        CFRunLoopSourceRef rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);

        if(rlSource)

        {

            // Add the source to the current run loop.

            CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);

            // Once installed, these can be freed.

            CFRelease(myPort);

            CFRelease(rlSource);

            

            // Package up the port name and send the check-in message.

            CFDataRef returnData = nil;

            CFDataRef outData;

            CFIndex stringLength = CFStringGetLength(myPortName);

            UInt8* buffer = CFAllocatorAllocate(NULL, stringLength, 0);

            CFStringGetBytes(myPortName, CFRangeMake(0,stringLength), kCFStringEncodingASCII0false, buffer, stringLength, NULL);

            outData = CFDataCreate(NULL, buffer, stringLength);

            

            CFMessagePortSendRequest(mainThreadPort, kCheckinMessage, outData, 0.10.0NULL, &returnData);

            

            // Clean up thread data structures.

            CFRelease(outData);

            CFAllocatorDeallocate(NULL, buffer);

            

            // Enter the run loop.

            CFRunLoopRun();

        }

    

    }

#endif

}

發佈了8 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章