@@ -87,8 +87,15 @@ - (void)stopMocking
8787
8888- (void )stopMockingClassMethods
8989{
90- OCMSetAssociatedMockForClass (nil , mockedClass);
91- object_setClass (mockedClass, originalMetaClass);
90+ // Synchronize around mockedClass to try and prevent class methods on other
91+ // threads being called while the class is being torn down.
92+ // See prepareClassForClassMethodMocking and forwardInvocationForClassObject
93+ // for other locations that are synchronized on this.
94+ @synchronized (mockedClass)
95+ {
96+ OCMSetAssociatedMockForClass (nil , mockedClass);
97+ object_setClass (mockedClass, originalMetaClass);
98+ }
9299 originalMetaClass = nil ;
93100 /* created meta class will be disposed later because partial mocks create another subclass depending on it */
94101}
@@ -119,48 +126,54 @@ - (void)prepareClassForClassMethodMocking
119126 if (otherMock != nil )
120127 [otherMock stopMockingClassMethods ];
121128
122- OCMSetAssociatedMockForClass (self, mockedClass);
123129
124130 /* dynamically create a subclass and use its meta class as the meta class for the mocked class */
125131 classCreatedForNewMetaClass = OCMCreateSubclass (mockedClass, mockedClass);
126132 originalMetaClass = object_getClass (mockedClass);
127133 id newMetaClass = object_getClass (classCreatedForNewMetaClass);
128-
129134 /* create a dummy initialize method */
130135 Method myDummyInitializeMethod = class_getInstanceMethod ([self mockObjectClass ], @selector (initializeForClassObject ));
131136 const char *initializeTypes = method_getTypeEncoding (myDummyInitializeMethod);
132137 IMP myDummyInitializeIMP = method_getImplementation (myDummyInitializeMethod);
133138 class_addMethod (newMetaClass, @selector (initialize ), myDummyInitializeIMP, initializeTypes);
134139
135- object_setClass (mockedClass, newMetaClass); // only after dummy initialize is installed (iOS9)
136-
137- /* point forwardInvocation: of the object to the implementation in the mock */
138- Method myForwardMethod = class_getInstanceMethod ([self mockObjectClass ], @selector (forwardInvocationForClassObject: ));
139- IMP myForwardIMP = method_getImplementation (myForwardMethod);
140- class_addMethod (newMetaClass, @selector (forwardInvocation: ), myForwardIMP, method_getTypeEncoding (myForwardMethod));
141-
142- /* adding forwarder for most class methods (instance methods on meta class) to allow for verify after run */
143- NSArray *methodBlackList = @[
144- @" class" , @" forwardingTargetForSelector:" , @" methodSignatureForSelector:" , @" forwardInvocation:" , @" isBlock" ,
145- @" instanceMethodForwarderForSelector:" , @" instanceMethodSignatureForSelector:" , @" resolveClassMethod:"
146- ];
147- void (^setupForwarderFiltered)(Class , SEL ) = ^(Class cls, SEL sel) {
148- if ((cls == object_getClass ([NSObject class ])) || (cls == [NSObject class ]) || (cls == object_getClass (cls)))
149- return ;
150- if (OCMIsApplePrivateMethod (cls, sel))
151- return ;
152- if ([methodBlackList containsObject: NSStringFromSelector (sel)])
153- return ;
154- @try
155- {
156- [self setupForwarderForClassMethodSelector: sel];
157- }
158- @catch (NSException *e)
159- {
160- // ignore for now
161- }
162- };
163- [NSObject enumerateMethodsInClass: originalMetaClass usingBlock: setupForwarderFiltered];
140+ // Synchronize around mockedClass to try and prevent class methods on other
141+ // threads being called while the class is being set up.
142+ // See forwardInvocationForClassObject and stopMockingClassMethods for other
143+ // locations that are synchronized on this.
144+ @synchronized (mockedClass)
145+ {
146+ object_setClass (mockedClass, newMetaClass); // only after dummy initialize is installed (iOS9)
147+ OCMSetAssociatedMockForClass (self, mockedClass);
148+
149+ /* point forwardInvocation: of the object to the implementation in the mock */
150+ Method myForwardMethod = class_getInstanceMethod ([self mockObjectClass ], @selector (forwardInvocationForClassObject: ));
151+ IMP myForwardIMP = method_getImplementation (myForwardMethod);
152+ class_addMethod (newMetaClass, @selector (forwardInvocation: ), myForwardIMP, method_getTypeEncoding (myForwardMethod));
153+
154+ /* adding forwarder for most class methods (instance methods on meta class) to allow for verify after run */
155+ NSArray *methodBlackList = @[
156+ @" class" , @" forwardingTargetForSelector:" , @" methodSignatureForSelector:" , @" forwardInvocation:" , @" isBlock" ,
157+ @" instanceMethodForwarderForSelector:" , @" instanceMethodSignatureForSelector:" , @" resolveClassMethod:"
158+ ];
159+ void (^setupForwarderFiltered)(Class , SEL ) = ^(Class cls, SEL sel) {
160+ if ((cls == object_getClass ([NSObject class ])) || (cls == [NSObject class ]) || (cls == object_getClass (cls)))
161+ return ;
162+ if (OCMIsApplePrivateMethod (cls, sel))
163+ return ;
164+ if ([methodBlackList containsObject: NSStringFromSelector (sel)])
165+ return ;
166+ @try
167+ {
168+ [self setupForwarderForClassMethodSelector: sel];
169+ }
170+ @catch (NSException *e)
171+ {
172+ // ignore for now
173+ }
174+ };
175+ [NSObject enumerateMethodsInClass: originalMetaClass usingBlock: setupForwarderFiltered];
176+ }
164177}
165178
166179
@@ -184,15 +197,22 @@ - (void)setupForwarderForClassMethodSelector:(SEL)selector
184197- (void )forwardInvocationForClassObject : (NSInvocation *)anInvocation
185198{
186199 // in here "self" is a reference to the real class, not the mock
187- OCClassMockObject *mock = OCMGetAssociatedMockForClass ((Class )self, YES );
188- if (mock == nil )
189- {
190- [NSException raise: NSInternalInconsistencyException format: @" No mock for class %@ " , NSStringFromClass ((Class )self )];
191- }
192- if ([mock handleInvocation: anInvocation] == NO )
200+ // Synchronize around self to try and prevent the class from being torn
201+ // down while a method is being called on it.
202+ // See prepareClassForClassMethodMocking and stopMockingClassMethods for
203+ // other locations that are synchronized on this.
204+ @synchronized (self)
193205 {
194- [anInvocation setSelector: OCMAliasForOriginalSelector ([anInvocation selector ])];
195- [anInvocation invoke ];
206+ OCClassMockObject *mock = OCMGetAssociatedMockForClass ((Class )self, YES );
207+ if (mock == nil )
208+ {
209+ [anInvocation invoke ];
210+ }
211+ else if ([mock handleInvocation: anInvocation] == NO )
212+ {
213+ [anInvocation setSelector: OCMAliasForOriginalSelector ([anInvocation selector ])];
214+ [anInvocation invoke ];
215+ }
196216 }
197217}
198218
0 commit comments