Closed Amindv1 closed 7 years ago
Thanks for the issue.
It has been tested… but separately, as you usually don't both stub and record what you stub, but rather record first and stub later.
But you're right in that it should be mentioned in the README that you should not use both at the same time but rather one after the other, so the README should be more clear about that.
So the reason I have a two in one is that I try to automate everything. I attempt to record, if the file name for the request doesn't exist then I do record the request, otherwise if it does exist I stub the request based off of the file. This requires me to register both.
I see.
I think you should still be able to register OHHTTPStubs
manually (I don't remember of SWHttpTrafficRecorder
offers an API to register the NSURLProtocol
manually but OHHTTPStubs
has setEnabled:
and setEnabled: forSession:
methods that should allow you to add OHHTTPStubs
' internal NSURLProtocol
manually even after SWHttpTrafficRecorder
did set itself up and added is own, making them both intercept the requests (I indeed haven't tested this use case of supporting them both at the same time but it's worth a try as it should theoretically work if you do it this way)
I thought that only one NSURLProtocol
can be registered at a time? I don't think I understand your proposed solution. Can you show me what you mean? This would save me a lot of heartache, I was considering merging the two pods into one so I can merge their protocols and use them at the same time.
No you can register as many as you want. Let me find a Mac in a minute so I can give you an example
Ok, so, basically my proposed solution is for you to call OHHTTPStubs.sharedInstance.setEnabled(true, forSessionConfiguration: sessionConfig)
(assuming you use a custom NSURLSession
build from an NSURLSessionConfiguration
, and not NSURLConnection
nor the shared NSURLSession
) before creating your NSURLSession
with it.
You can find this method's implementation here. As you can see, this method doesn't replace the supported NSURLProtocol
s of an NSURLSession
, but instead just inserts OHHTTPStubs
's own NSURLProtocol
at the top of the sessionConfig.protocolClasses
array.
This insertion is automatically done on the two NSURLSessionConfiguration
's defaultSessionConfiguration
and ephemeralSessionConfiguration
presets (thanks to some swizzling).
If you use SWHttpTrafficRecorder
, it also inserts its own NSURLProtocol
in this array (see here). But for this to work you'll have to:
NSURLSessionConfiguration
and set it up both for OHHTTPStubs
AND for SWHttpTrafficRecorder
before you create your NSURLSession(configuration: …)
initializer, because when you create your NSURLSession
from a configuration, the configuration is copied, so any further changes on the NSURLSessionConfiguration
you used after creating the NSURLSession
won't affect that NSURLSession
retroactively (this is explained in Apple's doc on NSURLSession
)OHHTTPStubs
an SWHttpTrafficRecorder
did set up their NSURLProtocol
in the right order and on the same NSURLSessionConfiguration
So, now that I've had time to re-read the implementation of both pods, I don't see why this wouldn't work:
pod 'OHHTTPStubs'
pod in your Podfile
. If you only have pod 'OHHTTPStubs/Swift'
for example, this subspec doesn't contain the NSURLSession
-supporting subspec by default (this might change in the future, but for now the Swift subspec only contains the Swift helpers, not the other subspecs. See the dedicated section in the README about thisimport Foundation
// Start with the default configuration
let config = URLSessionConfiguration.default
// If you indeed have the `OHHTTPStubs/NSURLSession` subspec listed in your `Podfile.lock`
// then OHHTTPStubs should have automatically injected its own `NSURLProtocol` at this point
// You can check this using some NSLog:
NSLog("protocol classes after OHHTTPStubs: \(config.protocolClasses)") // this should contain `OHHTTPStubsProtocol` among others
do {
// not sure about the translation of SWHttpTrafficRecorder API in Swift here, I'll let you adapt
let started = try SWHttpTrafficRecorder.sharedRecorder().startRecording(path:nil, forSessionConfiguration:config)
// At this point, SWHttpTrafficRecord should itself have added its own NSURLProtocol to the
// protocolClasses array, intercepting the traffic first, BEFORE anybody else (including OHHTTPStubs)
NSLog("Started: \(started)")
NSLog("protocolClasses after SWHTR: \(config.protocolClasses)")
} catch {
NSLog("Error while trying to start SWHttpTrafficRecorder")
}
// At this point, SWHttpTrafficRecorder is listed first in the protocolClasses array
// then OHHTTPStubs is next in line. This means that SWHTR will intercept requests first, then
// OHHTTPStubs will be given the ability to stub them next, then if the request passed thru both
// the request will finally hit the real world.
// If you want to make `OHHTTPStubs` intercept the requests BEFORE SWHttpTrafficRecorder
// Then you have to ask `OHHTTPStubs` to remove itself then re-insert itself at index 0
OHHTTPStubs.setEnabled(false, forSessionConfiguration: config) // remove first
OHHTTPStubs.setEnabled(true, forSessionConfiguration: config) // then reinsert, at index 0
// And ONLY then, create your NSURLSession, because if you create it before
// setting up the NSURLSessionConfiguration, changes in the configuration won't do anything
let session = URLSession(configuration: config)
let task = session.dataTask(with: yourURLHere)
task.resume()
What's the difference between adding the protocol like so:
[NSURLProtocol registerClass:OHHTTPStubsProtocol.class];
and like so:
NSMutableArray * urlProtocolClasses = [NSMutableArray arrayWithArray:sessionConfig.protocolClasses]; Class protoCls = OHHTTPStubsProtocol.class; if (enable && ![urlProtocolClasses containsObject:protoCls]) { [urlProtocolClasses insertObject:protoCls atIndex:0]; } else if (!enable && [urlProtocolClasses containsObject:protoCls]) { [urlProtocolClasses removeObject:protoCls]; } sessionConfig.protocolClasses = urlProtocolClasses;
?
If I register the class for NSURLProtocol does it register it for the default protocol, so any call to URLSessionConfiguration.default after adding a protocol will include the new protocol?
The difference is that one applies to global sessions/connections, the other to sessions built with a given session configuration.
[NSURLProtocol registerClass:…]
only applies to requests sent via NSURLConnection
or [NSURLSession sharedSession]
, and does not apply to requests sent via an NSURLSession
built from an NSURLSessionConfiguration
[NSURLSessionConfiguration setProtocolClasses:]
is used by requests sent via an NSURLSession
that has been created from that given NSURLSessionConfiguration
, and doesn't affect the NSURLSession
instances created by other NSURLSessionConfigurations
nor requests sent via NSURLSession
For more info, see:
For in-depth understanding, you can also find detailed information in Apple's API documentation on NSURLSession
/ NSURLSessionConfiguration
and the URL Loading System Programming Guide
Alright maybe I'm confused here but something doesn't seem right. I was assuming that the setEnabled method is what was registering the class under the default session configuration, which is what AFNetworking was using. But after some testing I don't know if this is the case, how are you making it so that you stub AFNetworking requests? The reason I don't think it's the registerClass function is because SWHttpTrafficRecorder uses that to register its own protocol class but when I do:
print("before: ", URLSessionConfiguration.default.protocolClasses)
print("did register AFRecordingProtocol.self: ", URLProtocol.registerClass(AFRecordingProtocol.self)) // This is my custom recording protocol looks exactly like SW's
print("after: ", URLSessionConfiguration.default.protocolClasses)
the before and after are the same:
before: Optional([OHHTTPStubsProtocol, _NSURLHTTPProtocol, _NSURLDataProtocol, _NSURLFTPProtocol, _NSURLFileProtocol, NSAboutURLProtocol]) did register AFRecordingProtocol.self: true after: Optional([OHHTTPStubsProtocol, _NSURLHTTPProtocol, _NSURLDataProtocol, _NSURLFTPProtocol, _NSURLFileProtocol, NSAboutURLProtocol])
As you can see, OHHTTPStubsProtocol is being registered but my custom protocol isn't listed. How is OHHTTPStubs making it so that the class is being registered with AFNetworking? You mentioned method swizzling but can you point me to where to start looking to see how this is happening? The session manager I'm using is an AFHTTPSessionManager and I pass it the default configuration:
[NSURLSessionConfiguration defaultConfiguration];
I make sure to setup the session configuration after I've added the protocol classes, so that isn't the issue either.
I'm using swift and objc interchangeably because my project has both and the code snippets come from different files.
Alright so I took a look at OHHTTPStubs+NSURLSessionConfiguration and see where the method swizzling is happening. Now I understand my error. I was thinking that the URLProtocol RegisterClass was what was adding OHHTTPStubs to the session configuration but this isn't the case, it's being 'swizzled' in. Thanks for pointing that out in your comment, it took me a couple of reads through to understand exactly what you meant and I also had to read through the implementation and on method swizzling. Here's the post I read if anyone else that stumbles upon this is as confused as I was:
Yup that's it.
Again, it's explained explicitly here ("Automatic Loading" paragraph) in the README as pointed out before.
Yea I read through that a bunch of times actually but since I'm a bit of a noob I didn't understand exactly what you meant by swizzling. I thought that the registerclass was what was swizzling the method because I didn't understand the concept of swizzling. I think all the new terminology threw me off too so I was confused when reading through the readme.
Again I really appreciate the help!
Ah right. Maybe we should put that NSHipster's link next to the "swizzling" mention in the README then.
Maybe that and mentioning where you're swizzling the sessions, what really did it for me is actually seeing where you guys swizzled the config.
I wanted to point out that this pod isn't compatible with the SWHttpTrafficRecorder as it points out on the readme. Both this pod and that pod try to register their own versions of NSURLProtocol which means one of them is going to lose, so either you don't record the file or you don't stub the response. I spent a good chunk of time trying to figure out why they weren't working together and thought this should be noted on your readme, as it makes it look like it's been tested and it should work when in reality it doesn't.