3 * Copyright (c) 2013, Blue Static <http://www.bluestatic.org>
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.
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.
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
17 #import "BSProtocolThreadInvoker.h"
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
;
25 // Removes the observer added with |-addRunLoopObserver|. Can be called on any
26 // thread since CFRunLoop is threadsafe.
27 - (void)removeRunLoopObserver
;
29 // Enqueues the |invocation| for execution, and dequeues the first if not called
31 // *MUST* be called on the target thread.
32 - (void)dispatchInvocation
:(NSInvocation
*)invocation
;
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
;
39 @implementation BSProtocolThreadInvoker
{
40 // The fully qualified target of the invocation.
44 CFRunLoopRef _runLoop
;
47 // If executing an invocation from |-dispatchInvocation:|. Protects against
48 // reentering the target.
51 // The queue of work to be executed. Enqueues in |-dispatchInvocation:|.
52 NSMutableArray
* _invocations
;
54 CFRunLoopObserverRef _observer
;
57 @synthesize object
= _object
;
59 - (id)initWithObject
:(NSObject
*)object
60 protocol
:(Protocol
*)protocol
61 thread
:(NSThread
*)thread
63 return [self initWithObject
:object
66 modes
:@
[ NSRunLoopCommonModes
]];
69 - (id)initWithObject
:(NSObject
*)object
70 protocol
:(Protocol
*)protocol
71 thread
:(NSThread
*)thread
72 modes
:(NSArray
*)runLoopModes
74 if ((self = [super init
])) {
78 _modes
= [runLoopModes copy
];
79 _invocations
= [[NSMutableArray alloc
] init
];
81 [self performSelector
:@selector(addRunLoopObserver
)
92 [self removeRunLoopObserver
];
95 - (BOOL)conformsToProtocol
:(Protocol
*)protocol
97 return [_protocol isEqual
:protocol
];
100 - (NSMethodSignature
*)methodSignatureForSelector
:(SEL)aSelector
103 return [_protocol methodSignatureForSelector
:aSelector
];
104 return [_object methodSignatureForSelector
:aSelector
];
107 - (BOOL)respondsToSelector
:(SEL)aSelector
110 return [_protocol respondsToSelector
:aSelector
];
111 return [_object respondsToSelector
:aSelector
];
114 - (void)forwardInvocation
:(NSInvocation
*)invocation
116 if ([_object respondsToSelector
:[invocation selector
]]) {
117 [invocation retainArguments
];
118 [self performSelector
:@selector(dispatchInvocation
:)
120 withObject
:invocation
126 // Private /////////////////////////////////////////////////////////////////////
128 - (void)addRunLoopObserver
130 assert([NSThread currentThread
] == _thread
);
131 _runLoop
= CFRunLoopGetCurrent();
133 BSProtocolThreadInvoker
* __block weakSelf
= self;
134 _observer
= CFRunLoopObserverCreateWithHandler(
139 ^
(CFRunLoopObserverRef observer
, CFRunLoopActivity activity
) {
140 [weakSelf observedRunLoopEnter
];
142 for (NSString
* mode
in _modes
)
143 CFRunLoopAddObserver(_runLoop
, _observer
, (CFStringRef
)mode
);
146 - (void)removeRunLoopObserver
148 for (NSString
* mode
in _modes
)
149 CFRunLoopRemoveObserver(_runLoop
, _observer
, (CFStringRef
)mode
);
150 CFRelease(_observer
);
153 - (void)dispatchInvocation
:(NSInvocation
*)invocation
155 // |invocation| will be nil if dispatch was requested after entering a new
156 // pass of the run loop, to process deferred work.
158 [_invocations addObject
:invocation
];
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).
166 _isDispatching
= YES
;
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];
177 - (void)observedRunLoopEnter
179 // Don't do anything if there's nothing to do.
180 if ([_invocations count
] == 0)
183 // If this nested run loop is still executing from within
184 // |-dispatchInvocation:|, continue to wait for a the nested loop to exit.
188 // A run loop has started running outside of |-dispatchInvocation:|, so
189 // schedule work to be done again.
190 [self performSelector
:@selector(dispatchInvocation
:)