16
16
17
17
#import " MTRMockCB.h"
18
18
19
- #import < CoreBluetooth/CoreBluetooth.h>
19
+ #import " MTRDefines_Internal.h"
20
+
20
21
#import < XCTest/XCTest.h>
21
22
#import < objc/runtime.h>
22
23
#import < os/log.h>
24
+ #import < stdatomic.h>
23
25
24
26
NS_ASSUME_NONNULL_BEGIN
25
27
@@ -147,11 +149,6 @@ static void InterceptClassMethod(__strong os_block_t * inOutCleanup, Class cls,
147
149
@throw [NSException exceptionWithName: NSInternalInconsistencyException reason: reason userInfo: nil ];
148
150
}
149
151
IMP originalImp = method_getImplementation (method);
150
- if (imp_getBlock (originalImp)) {
151
- NSString * reason = [NSString stringWithFormat: @" +[%@ %@ ] was already intercepted" ,
152
- NSStringFromClass (cls), NSStringFromSelector (sel)];
153
- @throw [NSException exceptionWithName: NSInternalInconsistencyException reason: reason userInfo: nil ];
154
- }
155
152
156
153
// Try to add the method first, in case it came from a super class.
157
154
// Note we need to pass the meta-class to class_addMethod().
@@ -169,16 +166,29 @@ static void InterceptClassMethod(__strong os_block_t * inOutCleanup, Class cls,
169
166
// inherited implementation; this is good enough for our purposes here.
170
167
method_setImplementation (method, originalImp);
171
168
imp_removeBlock (newImp); // otherwise the block might leak
172
- if (nextCleanup) {
173
- nextCleanup ();
174
- }
169
+ (void ) block; // keep an obvious reference to avoid `leaks` false positives before cleanup
170
+ nextCleanup ();
175
171
};
176
172
}
177
173
178
174
- (instancetype )init
179
175
{
180
176
self = [super init ];
181
177
_log = os_log_create (" com.csa.matter" , " mock" );
178
+
179
+ static atomic_flag sInitialized = ATOMIC_FLAG_INIT;
180
+ if (atomic_flag_test_and_set (&sInitialized )) {
181
+ os_log_error (_log, " CoreBluetooth mocking is already enabled" );
182
+ return nil ;
183
+ }
184
+
185
+ mtr_weakify (self);
186
+ _invalidate = ^{
187
+ mtr_strongify (self);
188
+ self->_invalidate = nil ;
189
+ atomic_flag_clear (&sInitialized );
190
+ };
191
+
182
192
_queue = dispatch_queue_create (" mock.cb" , DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
183
193
dispatch_queue_set_specific (_queue, (__bridge void *) self, @YES , nil ); // mark our queue
184
194
@@ -191,7 +201,8 @@ - (instancetype)init
191
201
// Replace implementations of class methods we need to mock. We don't need to intercept
192
202
// any instance methods directly, because we're returning a mock object from `alloc`.
193
203
InterceptClassMethod (&_invalidate, CBCentralManager.class , @selector (alloc ), ^id NS_RETURNS_RETAINED (void ) {
194
- return [[MTRMockCBCentralManager alloc ] _initWithMock: self ];
204
+ mtr_strongify (self);
205
+ return self ? [[MTRMockCBCentralManager alloc ] _initWithMock: self ] : nil ;
195
206
});
196
207
InterceptClassMethod (&_invalidate, CBCentralManager.class , @selector (supportsFeatures: ), ^BOOL (CBCentralManagerFeature features) {
197
208
return [MTRMockCBCentralManager supportsFeatures: features];
0 commit comments