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 retain
];
79 _invocations
= [[NSMutableArray alloc
] init
];
81 [self performSelector
:@selector(addRunLoopObserver
)
92 [self removeRunLoopObserver
];
94 [_invocations release
];
98 - (BOOL)conformsToProtocol
:(Protocol
*)protocol
100 return [_protocol isEqual
:protocol
];
103 - (NSMethodSignature
*)methodSignatureForSelector
:(SEL)aSelector
106 return [_protocol methodSignatureForSelector
:aSelector
];
107 return [_object methodSignatureForSelector
:aSelector
];
110 - (BOOL)respondsToSelector
:(SEL)aSelector
113 return [_protocol respondsToSelector
:aSelector
];
114 return [_object respondsToSelector
:aSelector
];
117 - (void)forwardInvocation
:(NSInvocation
*)invocation
119 if ([_object respondsToSelector
:[invocation selector
]]) {
120 [invocation retainArguments
];
121 [self performSelector
:@selector(dispatchInvocation
:)
123 withObject
:invocation
129 // Private /////////////////////////////////////////////////////////////////////
131 - (void)addRunLoopObserver
133 assert([NSThread currentThread
] == _thread
);
134 _runLoop
= CFRunLoopGetCurrent();
136 BSProtocolThreadInvoker
* __block weakSelf
= self;
137 _observer
= CFRunLoopObserverCreateWithHandler(
142 ^
(CFRunLoopObserverRef observer
, CFRunLoopActivity activity
) {
143 [weakSelf observedRunLoopEnter
];
145 for (NSString
* mode
in _modes
)
146 CFRunLoopAddObserver(_runLoop
, _observer
, (CFStringRef
)mode
);
149 - (void)removeRunLoopObserver
151 for (NSString
* mode
in _modes
)
152 CFRunLoopRemoveObserver(_runLoop
, _observer
, (CFStringRef
)mode
);
153 CFRelease(_observer
);
156 - (void)dispatchInvocation
:(NSInvocation
*)invocation
158 // |invocation| will be nil if dispatch was requested after entering a new
159 // pass of the run loop, to process deferred work.
161 [_invocations addObject
:invocation
];
163 // Protect the target object from reentering itself. This work will be
164 // rescheduled when another run loop starts (including falling out of a
165 // nested loop and starting a new pass through a lower loop).
169 _isDispatching
= YES
;
171 // Dequeue only one item. If multiple items are present, the next pass through
172 // the run loop will schedule another dispatch via |-observedRunLoopEnter|.
173 invocation
= [_invocations objectAtIndex
:0];
174 [invocation invokeWithTarget
:_object
];
175 [_invocations removeObjectAtIndex
:0];
180 - (void)observedRunLoopEnter
182 // Don't do anything if there's nothing to do.
183 if ([_invocations count
] == 0)
186 // If this nested run loop is still executing from within
187 // |-dispatchInvocation:|, continue to wait for a the nested loop to exit.
191 // A run loop has started running outside of |-dispatchInvocation:|, so
192 // schedule work to be done again.
193 [self performSelector
:@selector(dispatchInvocation
:)