diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b2ca20a..40a1315c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ _You can download previous versions [here](https://github.com/AliSoftware/OHHTTPStubs/tags) and latest version [here](https://github.com/AliSoftware/OHHTTPStubs/zipball/master) (ZIP files generated by GitHub on the fly)_ +## [4.0.1](https://github.com/AliSoftware/OHHTTPStubs/releases/tag/4.0.1) + +* Fix threading in `NSURLProtocol` subclass calling `NSURLProtocolClient` callbacks from wrong thread. ([@nsprogrammer](https://github.com/nsprogrammer), [#96](https://github.com/AliSoftware/OHHTTPStubs/pull/96)) + ## [4.0.0](https://github.com/AliSoftware/OHHTTPStubs/releases/tag/4.0.0) — Improvements for Swift * Annotated the library with _nullability_ attributes to generate a better API when used in Swift diff --git a/OHHTTPStubs/Sources/OHHTTPStubs.m b/OHHTTPStubs/Sources/OHHTTPStubs.m index c18e67c2..4d36ae69 100644 --- a/OHHTTPStubs/Sources/OHHTTPStubs.m +++ b/OHHTTPStubs/Sources/OHHTTPStubs.m @@ -269,6 +269,8 @@ - (OHHTTPStubsDescriptor*)firstStubPassingTestForRequest:(NSURLRequest*)request @interface OHHTTPStubsProtocol() @property(assign) BOOL stopped; @property(strong) OHHTTPStubsDescriptor* stub; +@property(assign) CFRunLoopRef clientRunLoop; +- (void)executeOnClientRunLoopAfterDelay:(NSTimeInterval)delayInSeconds block:(dispatch_block_t)block; @end @implementation OHHTTPStubsProtocol @@ -298,6 +300,7 @@ - (NSCachedURLResponse *)cachedResponse - (void)startLoading { + self.clientRunLoop = CFRunLoopGetCurrent(); NSURLRequest* request = self.request; id client = self.client; @@ -354,16 +357,16 @@ - (void)startLoading if (((responseStub.statusCode > 300) && (responseStub.statusCode < 400)) && redirectLocationURL) { NSURLRequest* redirectRequest = [NSURLRequest requestWithURL:redirectLocationURL]; - execute_after(responseStub.requestTime, ^{ + [self executeOnClientRunLoopAfterDelay:responseStub.requestTime block:^{ if (!self.stopped) { [client URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:urlResponse]; } - }); + }]; } else { - execute_after(responseStub.requestTime,^{ + [self executeOnClientRunLoopAfterDelay:responseStub.requestTime block:^{ if (!self.stopped) { [client URLProtocol:self didReceiveResponse:urlResponse cacheStoragePolicy:NSURLCacheStorageNotAllowed]; @@ -386,16 +389,16 @@ - (void)startLoading } }]; } - }); + }]; } } else { // Send the canned error - execute_after(responseStub.responseTime, ^{ + [self executeOnClientRunLoopAfterDelay:responseStub.responseTime block:^{ if (!self.stopped) { [client URLProtocol:self didFailWithError:responseStub.error]; } - }); + }]; } } @@ -471,10 +474,10 @@ - (void) streamDataForClient:(id)client if (chunkSizeToRead == 0) { // Nothing to read at this pass, but probably later - execute_after(timingInfo.slotTime, ^{ + [self executeOnClientRunLoopAfterDelay:timingInfo.slotTime block:^{ [self streamDataForClient:client fromStream:inputStream timingInfo:timingInfo completion:completion]; - }); + }]; } else { uint8_t* buffer = (uint8_t*)malloc(sizeof(uint8_t)*chunkSizeToRead); NSInteger bytesRead = [inputStream read:buffer maxLength:chunkSizeToRead]; @@ -483,11 +486,11 @@ - (void) streamDataForClient:(id)client NSData * data = [NSData dataWithBytes:buffer length:bytesRead]; // Wait for 'slotTime' seconds before sending the chunk. // If bytesRead < chunkSizePerSlot (because we are near the EOF), adjust slotTime proportionally to the bytes remaining - execute_after(((double)bytesRead / (double)chunkSizeToRead) * timingInfo.slotTime, ^{ + [self executeOnClientRunLoopAfterDelay:((double)bytesRead / (double)chunkSizeToRead) * timingInfo.slotTime block:^{ [client URLProtocol:self didLoadData:data]; [self streamDataForClient:client fromStream:inputStream timingInfo:timingInfo completion:completion]; - }); + }]; } else { @@ -515,10 +518,13 @@ - (void) streamDataForClient:(id)client // Delayed execution utility methods ///////////////////////////////////////////// -static void execute_after(NSTimeInterval delayInSeconds, dispatch_block_t block) +- (void)executeOnClientRunLoopAfterDelay:(NSTimeInterval)delayInSeconds block:(dispatch_block_t)block { dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block); + dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + CFRunLoopPerformBlock(self.clientRunLoop, kCFRunLoopDefaultMode, block); + CFRunLoopWakeUp(self.clientRunLoop); + }); } @end