Bump project version to 212.1.
[macgdbp.git] / Source / BSProtocolThreadInvoker.m
1 /*
2 * MacGDBp
3 * Copyright (c) 2013, Blue Static <http://www.bluestatic.org>
4 *
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17 #import "BSProtocolThreadInvoker.h"
18
19 @interface BSProtocolThreadInvoker (Private)
20 // Installs a run loop observer on the target thread that is used to start
21 // dispatching messages again after falling out of a nested run loop.
22 // *MUST* be called on the target thread.
23 - (void)addRunLoopObserver;
24
25 // Removes the observer added with |-addRunLoopObserver|. Can be called on any
26 // thread since CFRunLoop is threadsafe.
27 - (void)removeRunLoopObserver;
28
29 // Enqueues the |invocation| for execution, and dequeues the first if not called
30 // reentrantly.
31 // *MUST* be called on the target thread.
32 - (void)dispatchInvocation:(NSInvocation*)invocation;
33
34 // Callback for the run loop whenever it begins a new pass. This will schedule
35 // work if any was previously deferred due to reentrancy protection.
36 - (void)observedRunLoopEnter;
37 @end
38
39 @implementation BSProtocolThreadInvoker {
40 // The fully qualified target of the invocation.
41 NSObject* _object;
42 Protocol* _protocol;
43 NSThread* _thread;
44 CFRunLoopRef _runLoop;
45 NSArray* _modes;
46
47 // If executing an invocation from |-dispatchInvocation:|. Protects against
48 // reentering the target.
49 BOOL _isDispatching;
50
51 // The queue of work to be executed. Enqueues in |-dispatchInvocation:|.
52 NSMutableArray* _invocations;
53
54 CFRunLoopObserverRef _observer;
55 }
56
57 @synthesize object = _object;
58
59 - (id)initWithObject:(NSObject*)object
60 protocol:(Protocol*)protocol
61 thread:(NSThread*)thread
62 {
63 return [self initWithObject:object
64 protocol:protocol
65 thread:thread
66 modes:@[ NSRunLoopCommonModes ]];
67 }
68
69 - (id)initWithObject:(NSObject*)object
70 protocol:(Protocol*)protocol
71 thread:(NSThread*)thread
72 modes:(NSArray*)runLoopModes
73 {
74 if ((self = [super init])) {
75 _object = object;
76 _protocol = protocol;
77 _thread = thread;
78 _modes = [runLoopModes copy];
79 _invocations = [[NSMutableArray alloc] init];
80
81 [self performSelector:@selector(addRunLoopObserver)
82 onThread:_thread
83 withObject:nil
84 waitUntilDone:NO
85 modes:_modes];
86 }
87 return self;
88 }
89
90 - (void)dealloc
91 {
92 [self removeRunLoopObserver];
93 }
94
95 - (BOOL)conformsToProtocol:(Protocol*)protocol
96 {
97 return [_protocol isEqual:protocol];
98 }
99
100 - (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
101 {
102 if (!_object)
103 return [_protocol methodSignatureForSelector:aSelector];
104 return [_object methodSignatureForSelector:aSelector];
105 }
106
107 - (BOOL)respondsToSelector:(SEL)aSelector
108 {
109 if (!_object)
110 return [_protocol respondsToSelector:aSelector];
111 return [_object respondsToSelector:aSelector];
112 }
113
114 - (void)forwardInvocation:(NSInvocation*)invocation
115 {
116 if ([_object respondsToSelector:[invocation selector]]) {
117 [invocation retainArguments];
118 [self performSelector:@selector(dispatchInvocation:)
119 onThread:_thread
120 withObject:invocation
121 waitUntilDone:NO
122 modes:_modes];
123 }
124 }
125
126 // Private /////////////////////////////////////////////////////////////////////
127
128 - (void)addRunLoopObserver
129 {
130 assert([NSThread currentThread] == _thread);
131 _runLoop = CFRunLoopGetCurrent();
132
133 BSProtocolThreadInvoker* __block weakSelf = self;
134 _observer = CFRunLoopObserverCreateWithHandler(
135 kCFAllocatorDefault,
136 kCFRunLoopEntry,
137 TRUE, // Repeats.
138 0, // Order.
139 ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
140 [weakSelf observedRunLoopEnter];
141 });
142 for (NSString* mode in _modes)
143 CFRunLoopAddObserver(_runLoop, _observer, (CFStringRef)mode);
144 }
145
146 - (void)removeRunLoopObserver
147 {
148 for (NSString* mode in _modes)
149 CFRunLoopRemoveObserver(_runLoop, _observer, (CFStringRef)mode);
150 CFRelease(_observer);
151 }
152
153 - (void)dispatchInvocation:(NSInvocation*)invocation
154 {
155 // |invocation| will be nil if dispatch was requested after entering a new
156 // pass of the run loop, to process deferred work.
157 if (invocation)
158 [_invocations addObject:invocation];
159
160 // Protect the target object from reentering itself. This work will be
161 // rescheduled when another run loop starts (including falling out of a
162 // nested loop and starting a new pass through a lower loop).
163 if (_isDispatching)
164 return;
165
166 _isDispatching = YES;
167
168 // Dequeue only one item. If multiple items are present, the next pass through
169 // the run loop will schedule another dispatch via |-observedRunLoopEnter|.
170 invocation = [_invocations objectAtIndex:0];
171 [invocation invokeWithTarget:_object];
172 [_invocations removeObjectAtIndex:0];
173
174 _isDispatching = NO;
175 }
176
177 - (void)observedRunLoopEnter
178 {
179 // Don't do anything if there's nothing to do.
180 if ([_invocations count] == 0)
181 return;
182
183 // If this nested run loop is still executing from within
184 // |-dispatchInvocation:|, continue to wait for a the nested loop to exit.
185 if (_isDispatching)
186 return;
187
188 // A run loop has started running outside of |-dispatchInvocation:|, so
189 // schedule work to be done again.
190 [self performSelector:@selector(dispatchInvocation:)
191 onThread:_thread
192 withObject:nil
193 waitUntilDone:NO
194 modes:_modes];
195 }
196
197 @end