How does - [NSURLCache cachedResponseForRequest:] actually get the matching cache? - ios

How does -[NSURLCache cachedResponseForRequest:] works? Is it works like key-value pair where the key is the content of the NSURLRequest object (which includes the NSURL object, cache policy, time out interval, HTTPBody, HTTPHeaders)? If so then if any of the above field is different then we could not retrieve the cache.
For example, assuming the NSURLRequest has everything staying the same except the HTTPBody, which one request has the body of:
{
a = 1;
}
while another request has the body of:
{
a = 2;
}
Am I be able to get the cache of former request using the latter request?

IIRC, only the URL is taken into account. If header or body data dfferences matter to you, you'll need to implement a custom cache. The URL Loading System Programming guide should be of some help, should you decide to go down that path.

Related

Bypass Service-Worker caching

I have a progressive web-app, which speaks to an API. The calls to this api get cached by a service worker, which works great.
But now, I want to add a reload-button, which ideally forces the service worker to try to bypass the cache and update it if successful, also it should not return the cached result if a connection could not be made.
I am a bit unsure how to solve this. I am using the sw-toolbox.
All requests go through the fetch callback which receives a request object. Thus, before returning a cached response you can look for an additional header parameter (you need to include it into your request to API) to skip the logic returning cached response.
Based on your description, you are using the application cache. It can be accessed from the app fronted independent of the sw-tool box.
function onReloadButtonClicked(event) {
//Check for browser cache support
if ('caches' in window) {
//Update cache if network query is successful
caches.open('your_cache_name')
.then(function(cache) {
cache.add('your_url');
}).catch(function(err) {
// Do something with the error
});
}
}

UIWebView slow loading using NSUrlProtocol Xamarin.Forms

I'm working on an iOS application using Xamarin.Forms. This application is using UIWebView controller that shows a web application that is hosting on my server. Each time that I make a request I have to send a custom header in order to identify that this request comes to the mobile application and not from a browser, to do this I'm using an NSUrlProtocol object that overrides the method Request that inserts the custom header on each request.This is my code:
public override NSUrlRequest Request {
get {
NSMutableDictionary headers = null;
if (null == base.Request.Headers) {
headers = new NSMutableDictionary ();
} else {
headers = new NSMutableDictionary (base.Request.Headers);
}
headers.Add(NSObject.FromObject(AppVariables.headerVariable), NSObject.FromObject (AppVariables.appVersion));
NSMutableUrlRequest newRequest = (NSMutableUrlRequest)base.Request.MutableCopy ();
newRequest.Headers = headers;
return newRequest;
}
}
The problem that I have right now is that I noticed since I started using the NSUrlProtocol the loading time of the pages is increasing a lot. Right now the loading is taking 10 seconds, before this implementation the page took 3 seconds approximately.
Can anyone please point out some helpful direction to overcome this??
I don't see any reasons for the delay in response time when you're using custom headers. Like Andreas mentioned in the comments, I believe it has to do with your server code. I would recommend profiling your server code.
Do you see similar results when you send the requests (with custom headers) from Fiddler or cURL?
Just like #AndreasPaulsson and #prashant had mentioned, server might be the culprit. I would recommend testing the API with tools like Postman and check the response speed. I would also recommend you to check ModernHttpClient by Paul C Betts. In iOS the library uses NSUrlSession.

In iOS, how to read the multi part form data in the NSURLRequest originating from a WKWebView?

In our WKWebView, we have a multi part form POST request which we need to inspect and conditionally handle.
Currently, we're trying this using the WKNavigationDelegate's webView:decidePolicyForNavigationAction:decisionHandler: method to gain access to the NSURLRequest. (navigationAction.request).
But when we inspect the request here, we can verify that it is the multi part form POST, however, the [request HTTPBody] returns nil.
Althrough i didn't found a docu for this, my shot would be that for security reasons the body in the request is empty, or assigned later than in
webView:decidePolicyForNavigationAction:decisionHandler:
Unfortunately it is the bug in WebKit :(( :
https://bugs.webkit.org/show_bug.cgi?id=140188
https://bugs.webkit.org/show_bug.cgi?id=145410
For some cases mentioned workaround from Florent Crivello can be used (https://bugs.webkit.org/show_bug.cgi?id=145410#c14):
NSString *javascriptPOSTRedirect = #"\
var form = document.createElement('form');\
form.method = 'POST';\
form.action = '<URL>';\
\
var input = document.createElement('input');\
input.type = 'text';\
input.name = '<key>';\
input.value = '<value>';\
form.appendChild(input);\
form.submit();";
[webView evaluateJavaScript:javascriptPOSTRedirect completionHandler:^(id _Nullable content, NSError * _Nullable error) {
// Your thing
}];
The request might contain a body stream. If so, and if you can modify the URL request, you could potentially read from that stream, then replace the request with a new one that uses a body data object.
Failing that, I think the only way to handle it would be to register a custom protocol handler that checks to see if it needs to handle it, and if so, handles it, and if not, either refuses it (if you can detect it without reading from the stream) or reissues it with some sort of tag that you can recognize (to avoid your protocol handler touching it the next time).

How return data from an HTTP request in Swift/Objective C

I'm trying to use Coinbase's API to get information about my online bitcoin wallet, and I'm trying to use Swift's NSURLSession object to do so. Perhaps I'm missing something obvious in the Apple docs, but after reading through the information about NSURLSession and NSURLSessionTask I still do not understand how to make an HTTP request and then return the body of the response so that the body can persist throughout the life of my app. As of now I only see the ability to use completion blocks which return void, or delegates which either return void themselves or use completion blocks which also return void. I want to use the data I get from the response later in the app, but because I'm using completion blocks I must handle the response data immediately after the response arrives.
To make it clear, I want to do something along the lines of the pseudocode function below:
func makeHTTPCall(urlString : String) -> String? {
create NSURLSession object
create request with proper headers and using the passed-in urlString
use the session object to send out the request
get the response object, extract the response body as a string, and return it
}
Then later, I could call something like this:
let myObject : MyObject = MyObject()
let respData : String = myObject.makeHTTPCall("https://coinbase.com/api/v1/account/balance")
This data is returning a JSON Object string, which is the String I want to persist beyond the life of the response and its completion block. How can I do this in either Swift or Objective C, since I'll be able to use either in Xcode 6?
EDIT: Two answers have been posted, but they miss the fundamental point of this question. I need to RETURN the data which I receive from the response. Both answers (and all other answers I've seen on SO) simply print the data received. I would like code that doesn't use a void-returning completion handler, but instead returns the data so that it can be used later in the lifecycle of the app. If there is anything unclear about my question, please tell me, though I don't see how this can be made clearer.
In the edit to your question, you say:
I need to RETURN the data which I receive from the response. Both answers (and all other answers I've seen on SO) simply print the data received. I would like code that doesn't use a void-returning completion handler, but instead returns the data so that it can be used later in the lifecycle of the app. If there is anything unclear about my question, please tell me, though I don't see how this can be made clearer.
I understand the appeal of this strategy, because it feels so intuitively logical. The problem is that your networking requests should always run asynchronously (e.g. use that completion handler pattern to which you allude).
While there are techniques making a function "wait" for the asynchronous request to complete (i.e. to make the asynchronous NSURLSession method behave synchronously or use one of the old synchronous network request methods), this is a really bad idea for a number of reasons:
If you do this from the main thread, it results in a horrible user experience (the app will be unresponsive while the request is in progress and the user won't know if the app is busy doing something or whether it's frozen for some unknown reason).
Again, if you do this from the main thread, you also risk having the iOS "watch dog" process kill your app (because if you block the main queue for more than a few seconds at the wrong time, particularly as the app comes to foreground, the OS will unceremoniously terminate your app). See Technical Q&A #1693 for a discussion on the problems of doing synchronous network requests.
We generally prefer the asynchronous network techniques because they offer more features unavailable with synchronous techniques (e.g. making requests cancelable, offer progress updates when using delegate-based network requests, etc.).
You really should use the completion handler pattern that those other questions suggest, and manage the changing state of the app in those handlers. In those situations where you absolutely cannot let the user proceed until some network request is done (e.g. you can't let the user buy something until you confirm their bitcoin balance, and you can't do that until they log in), then change the UI to indicate that such a request is in progress. For example, dim the UI, disable the controls, pop up an activity indicator view (a.k.a., a "spinner"), etc. Only then would you initiate the request. And upon completion of the request, you would restore the UI. I know it seems like a lot, but it's the right way to do it when the user absolutely cannot proceed until the prior request is done.
I'd also think long and hard as to whether it's truly the case that you absolutely have to force the user to wait for the prior network request to complete. Sometimes there are situations where you can let the user do/review something else while the network request is in progress. Yes, sometimes that isn't possible, but if you can find those sorts of opportunities in your app, you'll end up with a more elegant UX.
I know that problem and use this code for synchronous requests:
func synchronousRequest() -> NSDictionary {
//creating the request
let url: NSURL! = NSURL(string: "exampledomain/...")
var request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
var error: NSError?
var response: NSURLResponse?
let urlData = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)
error = nil
let resultDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(urlData!, options: NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary
return resultDictionary
}
What you are asking for is a synchronous network request. There are many ways to do this, such as...
NSData's init(contentsOfURL aURL: NSURL!)
NSURLConnection's synchronous request method
...etc.
These methods will block the current thread until they complete - which can be a potentially long time. Network requests can have very high timeouts, it may be several minutes before the device gives up. NSData's init with contents of URL will return NSData, not void, and does not execute asynchronously. It will block until it is complete, which is why it's recommended to not do these types of requests from the main thread. The UI will be frozen until it completes.
In general the use of synchronous networking methods is discouraged. Asynchronous network requests are greatly preferred for a number of reasons. Using an asynchronous method that takes a completion block as a parameter will not prevent you from using the returned data elsewhere in your application. The block is executed when the network request has finished (wether it succeeds or fails) and it is passed the data, response metadata, and error. You are free to do what you want with that data - nothing prevents you from persisting it, passing it off to another object, etc. Based on your comments it sounds like you want to take the data that was the result of the network request and set it as the value of a property somewhere - that is entirely doable using an asynchronous method that uses a block as a completion handler.
In objective-C you can use __block and get the data when the operation finishes:
__block NSData *myData;
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:urlString]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
myData = data;
}] resume];

Switch between different API hosts

I'm working on an app which primarily works with an API that will be installed in an internal system. The API is also accessible via the public internet. The client wants to allow users to enter both an internal and external (public internet) URL that the app will then connect to depending on availability of the internal and external URLs.
The app is basically done with the exception that it currently connects to the internal URL only for all it's API calls. I'm using AFNetworking with block-based completion/failure invocations for each API call.
Based on the logic that we have designed, the app will always check for the API's availability by querying for the server's current time. This is done by calling http://internal_url/api/time. If this API fails to return an appropriate respond, we'll switch to the external URL http://external_url/api/time and call the same API on that URL. If both fails, the app will inform the user accordingly and not perform any other queries to the API.
Without revealing too much, here's some code on how I the API calls are currently setup:
- (void)someAPIMethodCall:(NSDictionary *)parameters completionBlock:block failure:block {
// query /api/time and return the URL (internal/external) that is currently up
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:<url returned from above query>];
[client operationWithSuccess:block failure:block];
}
So my question would be: what is the best way to get the query /api/time method above to work? Obviously, this method needs to complete and return either the internal/external URL so that the subsequent actual API query could use. AFAIK, AFNetworking calls are block-based so it will return before the above /api/time returns. I've also thought of a separate class that uses NSURLConnection synchronously which will block the main-thread while it waits for the /api/time to return.
I'd like to tell you to simply use the same URL internally and externally (via DNS) but that's not what you want.
I think you're asking how to conditionally call the other url.
You want someAPIMethodCall to be asynchronous... so you don't want to block on the call to checking for the correct api to call.
Aside from caching the results so you don't have to do this every time, you simply want to call another block based method of your own that has a completion block which passes IN a parameter of the URL to call for your real query.
- (void)someAPIMethodCall:(NSDictionary *)parameters completionBlock:(void (^)(void))succesBlock failure((^)(void)):failureBlock {
[self callBlockWithMyApiUrl:^(NSString *apiUrl){
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:apiUrl];
[client operationWithSuccess:successBlock failure:failureBlock];
} onFailure:^{
failureBlock
}
}
- (NSString *)callBlockWithMyApiUrl:(NSString * (^)(void))success (void (^)(void))failure
{
// Your code to test for the working URI
// If you're doing it this way, I'd suggest caching the result.
// Subscribe to networking interface changes to dump the cache.
}

Resources