17
17
#import " MTRMockCB.h"
18
18
19
19
#import < CoreBluetooth/CoreBluetooth.h>
20
- #import < OCMock/OCMock.h>
21
20
#import < XCTest/XCTest.h>
21
+ #import < objc/runtime.h>
22
22
23
23
NS_ASSUME_NONNULL_BEGIN
24
24
@@ -116,13 +116,49 @@ - (void)openL2CAPChannel:(CBL2CAPPSM)PSM;
116
116
117
117
@implementation MTRMockCB {
118
118
@package
119
- id _classMock ;
119
+ os_block_t _invalidate ;
120
120
dispatch_queue_t _queue;
121
121
NSHashTable <MTRMockCBCentralManager *> * _managers;
122
122
NSMutableDictionary <NSUUID *, MTRMockCBPeripheralDetails *> * _peripherals;
123
123
CBManagerState _state;
124
124
}
125
125
126
+ static void InterceptClassMethod (__strong os_block_t * inOutCleanup, Class cls, SEL sel, id block)
127
+ {
128
+ Method method = class_getClassMethod (cls, sel); // may return an inherited method
129
+ if (!method) {
130
+ NSString * reason = [NSString stringWithFormat: @" +[%@ %@ ] does not exist" ,
131
+ NSStringFromClass (cls), NSStringFromSelector (sel)];
132
+ @throw [NSException exceptionWithName: NSInternalInconsistencyException reason: reason userInfo: nil ];
133
+ }
134
+ IMP originalImp = method_getImplementation (method);
135
+ if (imp_getBlock (originalImp)) {
136
+ NSString * reason = [NSString stringWithFormat: @" +[%@ %@ ] was already intercepted" ,
137
+ NSStringFromClass (cls), NSStringFromSelector (sel)];
138
+ @throw [NSException exceptionWithName: NSInternalInconsistencyException reason: reason userInfo: nil ];
139
+ }
140
+
141
+ // Try to add the method first, in case it came from a super class.
142
+ // Note we need to pass the meta-class to class_addMethod().
143
+ IMP newImp = imp_implementationWithBlock (block);
144
+ if (class_addMethod (object_getClass (cls), sel, newImp, method_getTypeEncoding (method))) {
145
+ method = class_getClassMethod (cls, sel); // look up again so we clean up the method we added
146
+ } else {
147
+ method_setImplementation (method, newImp);
148
+ }
149
+
150
+ os_block_t nextCleanup = *inOutCleanup;
151
+ *inOutCleanup = ^{
152
+ // This isn't 100% correct if we added an override of a super-class method, because
153
+ // there is no API for removing a method. Instead we directly point it at the
154
+ // inherited implementation; this is good enough for our purposes here.
155
+ method_setImplementation (method, originalImp);
156
+ if (nextCleanup) {
157
+ nextCleanup ();
158
+ }
159
+ };
160
+ }
161
+
126
162
- (instancetype )init
127
163
{
128
164
self = [super init ];
@@ -131,29 +167,30 @@ - (instancetype)init
131
167
_peripherals = [[NSMutableDictionary alloc ] init ];
132
168
_state = CBManagerStatePoweredOn;
133
169
134
- _classMock = OCMClassMock (CBCentralManager.class );
135
- OCMStub (ClassMethod ([_classMock alloc ]))
136
- .andCall (self, @selector (allocMockManager ));
137
- OCMStub (ClassMethod ([_classMock authorization ]))
138
- .andCall (MTRMockCBCentralManager.class , @selector (authorization ));
139
- OCMStub (ClassMethod ([_classMock supportsFeatures: 0 ])).ignoringNonObjectArgs ().andCall (MTRMockCBCentralManager.class , @selector (supportsFeatures: ));
170
+ // Replace implementations of class methods we need to mock. We don't need to intercept
171
+ // any instance methods directly, because we're returning a mock object from `alloc`.
172
+ InterceptClassMethod (&_invalidate, CBCentralManager.class , @selector (alloc ), ^id NS_RETURNS_RETAINED (void ) {
173
+ return [[MTRMockCBCentralManager alloc ] _initWithMock: self ];
174
+ });
175
+ InterceptClassMethod (&_invalidate, CBCentralManager.class , @selector (supportsFeatures: ), ^BOOL (CBCentralManagerFeature features) {
176
+ return [MTRMockCBCentralManager supportsFeatures: features];
177
+ });
178
+ InterceptClassMethod (&_invalidate, CBManager.class , @selector (authorization ), ^CBManagerAuthorization (void ) {
179
+ return [MTRMockCBCentralManager authorization ];
180
+ });
181
+
140
182
return self;
141
183
}
142
184
143
185
- (BOOL )isValid
144
186
{
145
187
__block BOOL valid;
146
188
dispatch_sync (_queue, ^{
147
- valid = (_classMock != nil );
189
+ valid = (_invalidate != nil );
148
190
});
149
191
return valid;
150
192
}
151
193
152
- - (id )allocMockManager
153
- {
154
- return [[MTRMockCBCentralManager alloc ] _initWithMock: self ];
155
- }
156
-
157
194
- (void )reset
158
195
{
159
196
dispatch_sync (_queue, ^{
@@ -166,10 +203,11 @@ - (void)reset
166
203
- (void )stopMocking
167
204
{
168
205
dispatch_sync (_queue, ^{
169
- if (_classMock) {
206
+ if (_invalidate) {
207
+ _invalidate ();
208
+ _invalidate = nil ;
209
+
170
210
NSArray <MTRMockCBCentralManager *> * managers = [_managers allObjects ];
171
- [_classMock stopMocking ];
172
- _classMock = nil ;
173
211
_managers = nil ;
174
212
_peripherals = nil ;
175
213
0 commit comments