The Receptionist design pattern solves the general problem of redirecting an event that occurs in an application’s execution context to another execution context for processing. It is a hybrid model. Although it does not appear in the Gang of Four book, it incorporates elements of the Command, Memo, and Proxy design patterns described in that book. It is also a variation of the Trampoline model (nor appears in the book). In this form, an event is initially received by a trampoline object, which is immediately returned or redirected to a target object for processing.
The Receptionist Design Pattern in Practice
A KVO message calls observeValueForKeyPath: ofObject: change: context: method implemented by an observer. If the change to the property occurs on a secondary thread, observeValueForKeyPath: ofObject: change: context: code executes on the same thread. There is a central object in this form, the receptionist, that acts as a threaded mediator. As shown in the illustration, a receptionist object is specified as the observer property property model property. The receptionist implements observeValueForKeyPath: ofObject: change: context: to redirect notifications received on a sub thread to another execution context in the main activity queue, in this case. When the property changes, the receptionist receives a KVO notification. The receptionist immediately adds a block operation to the main activity queue, the block containing the code specified by the client that updates the user interface appropriately.
Bouncing updates KVO into the main activity queue
You define a receptionist class so that it has the necessary elements to add itself as an observer of an attribute and then convert a KVO message into an update task. Therefore, it must know the object it is observing, the properties of the object it is observing, what update task to perform and the queue to execute it. The code below shows the original declaration of the RCRecellectist class and its instance variables.
Declaring the receptionist class
1 2 3 4 5 6 7 8 9 | @interface <span class="token class-name">RCReceptionist</span> <span class="token punctuation">:</span> <span class="token builtin">NSObject</span> <span class="token punctuation">{</span> id observedObject <span class="token punctuation">;</span> <span class="token builtin">NSString</span> <span class="token operator">*</span> observedKeyPath <span class="token punctuation">;</span> <span class="token builtin">RCTaskBlock</span> task <span class="token punctuation">;</span> <span class="token builtin">NSOperationQueue</span> <span class="token operator">*</span> queue <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
The RCTaskBlock object variable is a block object of the following type of declaration
1 2 | typedef void <span class="token punctuation">(</span> <span class="token operator">^</span> <span class="token builtin">RCTaskBlock</span> <span class="token punctuation">)</span> <span class="token punctuation">(</span> <span class="token builtin">NSString</span> <span class="token operator">*</span> keyPath <span class="token punctuation">,</span> id object <span class="token punctuation">,</span> <span class="token builtin">NSDictionary</span> <span class="token operator">*</span> change <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
These parameters are similar to the parameters of the observeValueForKeyPath: ofObject: change: context method, Next the parameter class declares a single factory class method in which an RCTaskBlock object is a parameter:
1 2 3 4 5 | <span class="token operator">+</span> <span class="token punctuation">(</span> id <span class="token punctuation">)</span> receptionistForKeyPath <span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token builtin">NSString</span> <span class="token operator">*</span> <span class="token punctuation">)</span> path object <span class="token punctuation">:</span> <span class="token punctuation">(</span> id <span class="token punctuation">)</span> obj queue <span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token builtin">NSOperationQueue</span> <span class="token operator">*</span> <span class="token punctuation">)</span> queue task <span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token builtin">RCTaskBlock</span> <span class="token punctuation">)</span> task <span class="token punctuation">;</span> |
Factory class method to create a receptionist object
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token operator">+</span> <span class="token punctuation">(</span> id <span class="token punctuation">)</span> receptionistForKeyPath <span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token builtin">NSString</span> <span class="token operator">*</span> <span class="token punctuation">)</span> path object <span class="token punctuation">:</span> <span class="token punctuation">(</span> id <span class="token punctuation">)</span> obj queue <span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token builtin">NSOperationQueue</span> <span class="token operator">*</span> <span class="token punctuation">)</span> queue task <span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token builtin">RCTaskBlock</span> <span class="token punctuation">)</span> task <span class="token punctuation">{</span> <span class="token builtin">RCReceptionist</span> <span class="token operator">*</span> receptionist <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token builtin">RCReceptionist</span> <span class="token keyword">new</span> <span class="token punctuation">]</span> <span class="token punctuation">;</span> receptionist <span class="token operator">-</span> <span class="token operator">></span> task <span class="token operator">=</span> <span class="token punctuation">[</span> task copy <span class="token punctuation">]</span> <span class="token punctuation">;</span> receptionist <span class="token operator">-</span> <span class="token operator">></span> observedKeyPath <span class="token operator">=</span> <span class="token punctuation">[</span> path copy <span class="token punctuation">]</span> <span class="token punctuation">;</span> receptionist <span class="token operator">-</span> <span class="token operator">></span> observedObject <span class="token operator">=</span> <span class="token punctuation">[</span> obj retain <span class="token punctuation">]</span> <span class="token punctuation">;</span> receptionist <span class="token operator">-</span> <span class="token operator">></span> queue <span class="token operator">=</span> <span class="token punctuation">[</span> queue retain <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token punctuation">[</span> obj addObserver <span class="token punctuation">:</span> receptionist forKeyPath <span class="token punctuation">:</span> path options <span class="token punctuation">:</span> <span class="token builtin">NSKeyValueObservingOptionNew</span> <span class="token operator">|</span> <span class="token builtin">NSKeyValueObservingOptionOld</span> context <span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">[</span> receptionist autorelease <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Note that the code copies the block object instead of retaining it. Because the block can be created on the stack, it must be copied to the heap so it exists in memory when the KVO message is sent.
Finally, the parameter class implements the observeValueForKeyPath: ofObject: change: context:
1 2 3 4 5 6 7 | <span class="token operator">-</span> <span class="token punctuation">(</span> void <span class="token punctuation">)</span> observeValueForKeyPath <span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token builtin">NSString</span> <span class="token operator">*</span> <span class="token punctuation">)</span> keyPath ofObject <span class="token punctuation">:</span> <span class="token punctuation">(</span> id <span class="token punctuation">)</span> object change <span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token builtin">NSDictionary</span> <span class="token operator">*</span> <span class="token punctuation">)</span> change context <span class="token punctuation">:</span> <span class="token punctuation">(</span> void <span class="token operator">*</span> <span class="token punctuation">)</span> context <span class="token punctuation">{</span> <span class="token punctuation">[</span> queue addOperationWithBlock <span class="token punctuation">:</span> <span class="token operator">^</span> <span class="token punctuation">{</span> <span class="token function">task</span> <span class="token punctuation">(</span> keyPath <span class="token punctuation">,</span> object <span class="token punctuation">,</span> change <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
This code simply lists the task in the given activity queue, passes the task block to the observed object, the main path for the changed attribute and the dictionary containing the new value. The task is encapsulated in an NSBlockOperation object that executes the task on the queue.
The client object provides the block code that updates the user interface when it creates a receptionist object, Note that when it creates the receptionist object, the client switches into the active queue that the block will be executed, in the field. This merge is the main manipulation queue.
Creating a receptionist object
1 2 3 4 5 6 7 8 | <span class="token builtin">RCReceptionist</span> <span class="token operator">*</span> receptionist <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token builtin">RCReceptionist</span> receptionistForKeyPath <span class="token punctuation">:</span> @ <span class="token string">"value"</span> object <span class="token punctuation">:</span> model queue <span class="token punctuation">:</span> mainQueue task <span class="token punctuation">:</span> <span class="token operator">^</span> <span class="token punctuation">(</span> <span class="token builtin">NSString</span> <span class="token operator">*</span> keyPath <span class="token punctuation">,</span> id object <span class="token punctuation">,</span> <span class="token builtin">NSDictionary</span> <span class="token operator">*</span> change <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin">NSView</span> <span class="token operator">*</span> viewForModel <span class="token operator">=</span> <span class="token punctuation">[</span> modelToViewMap objectForKey <span class="token punctuation">:</span> model <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token builtin">NSColor</span> <span class="token operator">*</span> newColor <span class="token operator">=</span> <span class="token punctuation">[</span> change objectForKey <span class="token punctuation">:</span> <span class="token builtin">NSKeyValueChangeNewKey</span> <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token punctuation">[</span> <span class="token punctuation">[</span> <span class="token punctuation">[</span> viewForModel subviews <span class="token punctuation">]</span> objectAtIndex <span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">]</span> setFillColor <span class="token punctuation">:</span> newColor <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span> <span class="token punctuation">;</span> |
When to use the Receptionist Pattern
You can apply the Front Desk design template whenever you need to move your work to another execution context for processing. When you observe the message or deploy the block handler or respond to the action message and you want to ensure that your code executes in the appropriate execution context, you can implement the Receptionist form to redirect the public. The work must be done for the execution of that context. With the Receptionist template, you can even perform some filters or combine incoming data before you exit a task to process the data. For example, you can collect data into batches and then, within that period of time, send those batches elsewhere for processing.
A common situation in which a helpful Receptionist model is key-value observing. In key-value observing, changes to the value of a descriptive attribute model object are communicated to the observer through KVO messages. However, model object changes may occur on a background thread. This leads to thread mismatches, because changes to the model object model state often result in updates to the user interface and these must occur on the main thread. In this case, you want to redirect KVO messages to the main stream. where updates to the app user interface may occur.