I have a subclass of NSURLProtocol,
The NSURLConnection works well.
And [NSURLSession sharedSession] works well too.
But, [NSURLSession sessionWithConfiguration:configuration] not work,
I called the [NSURLProtocol registerClass:NSURLProtocolSubclass.class];
what's error?
NSString * stringUrl = #"https://www.apple.com/";
NSURL * url = [NSURL URLWithString:stringUrl];
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];
urlRequest.HTTPMethod = #"GET";
NSURLSessionConfiguration * configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
configuration.timeoutIntervalForResource = 20.0;
//this can work, but I cann't edit.
//configuration.protocolClasses = #[NSURLProtocolSubclass.class];
//this can work.
NSURLSession * urlSession = [NSURLSession sharedSession];
//this not work.
//NSURLSession * urlSession = [NSURLSession sessionWithConfiguration:configuration];
//Task.
NSURLSessionDataTask * task = [urlSession dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse * httpUrlResponse = (NSHTTPURLResponse *)response;
NSLog(#"---- session: %ld", (long)httpUrlResponse.statusCode);
}];
[task resume];
The registerClass method registers a protocol with NSURLConnection and with the shared session only. Other sessions copy the global set of protocols at the time the session is created, IIRC, unless you provide a custom array, in which case it copies that array.
Ether way, if you need to add protocols while your app is running, I'm pretty sure you have to create a new session each time unless you use the shared session.
Related
In my application, I am performing POST using NSURLSession.
Steps followed:
Setting Header
Setting HTTPBody
Making POST request using NSURLSession.
The code is:
NSDictionary *parameters = #{ #"searchTerm": #"shampoo", #"sort": #"Most Reviewed" };
NSError *error = nil;
NSData *postData = [NSJSONSerialization dataWithJSONObject:parameters options:NSJSONWritingPrettyPrinted error:&error];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"SomeURL"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
[request setHTTPMethod:#"POST"];
[request setAllHTTPHeaderFields:headers];
request.HTTPBody = postData;
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"%#", error);
} else {
NSLog(#"Pass");
}
}];
[dataTask resume];
Now in custom NSURLProtocol class:
(BOOL)canInitWithRequest:(NSURLRequest *)request {
if ([request.HTTPMethod isEqualToString:#"POST"]) {
//here request.HTTPMethod is coming nil
//Whereas my requirement is to get request.HTTPMethod which got request parameter.
return YES;
}
return NO;
}
Thanks in advance.
IIRC, body data objects get transparently converted into streaming-style bodies by the URL loading system before they reach you. If you need to read the data:
Open the HTTPBodyStream object
Read the body data from it
There is one caveat: the stream may not be rewindable, so don't pass that request object on to any other code that would need to access the body afterwards. Unfortunately, there is no mechanism for requesting a new body stream, either (see the README file from the CustomHTTPProtocol sample code project on Apple's website for other limitations).
I noticed that my application is connecting to remote resources way slower than web browsers, or cURL and decided to do some digging.
I've been using NSURLSession sessionWithConfiguration:delegate: to handle server responses, and decided to try instead using NSURLSession dataTaskWithRequest:completionHandler:. The test methods are below:
-(void)connectWithDelegate:(NSString *)url
{
semaphore = dispatch_semaphore_create(0);
session = [NSURLSession sessionWithConfiguration: [NSURLSessionConfiguration defaultSessionConfiguration]
delegate: self
delegateQueue: nil];
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: url]
cachePolicy: NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval: 60.0];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest: req];
[dataTask resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
- (void)connectWithCompletionHandler:(NSString *)url
{
semaphore = dispatch_semaphore_create(0);
session = [NSURLSession sharedSession];
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: url]
cachePolicy: NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval: 60.0];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:req completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error){
if (!error)
dispatch_semaphore_signal(semaphore);
}];
[dataTask resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
Running these tests repeatedly with a range of different network conditions, the connectWithCompletionHandler: method consistently outperforms connectWithDelegate:. On slow connections it can be as much as 6 times faster to return.
The full test code can be found here.
Running this test over 20 iterations on a 5Mb/s link with 50 ms latency yields an average connection time of 0.6 seconds for connectWithCompletionHandler: and 2 seconds for connectWithDelegate:
What accounts for the difference in response time between these two methods...?
So i have this method in my app that returns BOOL if and update is available for my apps content
- (BOOL)isUpdateAvailable{
NSData *dataResponse=[NSData dataWithContentsOfURL:[NSURL URLWithString:#"url that returns json object"] ];
if(dataResponse!=nil){
NSError *error;
dicUpdates = [NSJSONSerialization JSONObjectWithData:dataResponse options:NSJSONReadingMutableContainers error:&error];
}
if(dicUpdates.count > 0) isUpdateAvailable = YES;
else isUpdateAvailable = NO;
return isUpdateAvailable;
}
I need a synchronous request for this, cause the next view controller will be dependent on the server response. However sometimes it takes a long time for the server to respond or the internet is really slow, i need to set a time out to prevent the app from 'being frozen'.
I previously used NSUrlconnection to accomplish this task, but it has been deprecated.
Also, I tried using NSURLSession, (been using it also to download updates in the background thread), but i just can figure out if it can be used for a synchronous request.
Any idea how to deal with this? i just need a synchronous method that returns a BOOL. Best regards.
We have to use NSURLRequest in NSURLSession to set timeout interval.
Check below code:
- (BOOL)isUpdateAvailable{
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"url that returns json object"] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:4]//timeout
completionHandler:^(NSData *dataResponse,
NSURLResponse *response,
NSError *error) {
// handle response
if(dataResponse!=nil){
NSError *error;
dicUpdates = [NSJSONSerialization JSONObjectWithData:dataResponse options:NSJSONReadingMutableContainers error:&error];
}
if(dicUpdates.count > 0) isUpdateAvailable = YES;
else isUpdateAvailable = NO;
return isUpdateAvailable;
}] resume];
}
I trying to implement Load image asynchronously.
NSURL *url = [NSURL URLWithString:_posterImg];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
UIImage *getImg = [[UIImage alloc] initWithData:data];
// do whatever you want with image
}
}];
But when i put this code, getImg will get warning "Unused Variable". i had check "response", "data" and "error", it all look fine but the getImg is NIL. is that i had write any thing wrong? thanks.
The affected variable is response. Although you use data and error, response is only declared as a parameter but nowhere used in your completion handler!
NSURL *url = [NSURL URLWithString:_posterImg];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
NSString *errorMsg = nil;
UIImage *getImg = nil;
if (!error){
getImg = [[UIImage alloc] initWithData:data];
}
else
{
errorMsg = [NSString stringWithFormat:#"Failed to load image. Error Message: %#", [error localizedString]];
}
[self handleImageRequestWithResponse:response image:getImg andErrorMessage:errorMsg];
}];
// Image hasn't load yet here since the request is asynchronously!
//if(getImg != nil && errorMsg == nil)
// NSLog(#"Image is available!");
//else
// NSLog(#"Loading the image asynchronously failed! %#", errorMsg);
// In addition now provide the following method.
- (void) handleImageRequestWithResponse:(NSURLResponse*)response image:(UIImage*)img andErrorMessage:(NSString*)err
{
if(img!= nil && err == nil)
NSLog(#"Image is available!");
else
NSLog(#"Loading the image asynchronously failed! %#", err);
// Handle image
};
EDIT: My bad! Since the code executes asynchronously getImg was of course nil when you checked as before
EDIT:
Using NSData dataWithContentsOfURL is synchronous,i.e. if executed on the main thread your application is blocked.
See this official documentation: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/Reference/Reference.html#//apple_ref/occ/clm/NSData/dataWithContentsOfURL:
Most important:
Important: Do not use this synchronous method to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.
Going for a completionHandler and a handler method called after the requested raw data has been handled/prepared is better for your performance and does not violate the official recommendation!
I am using RestKit version 0.20.3 to make a generic method that is used in many other places. The problem is the returned value from that method is always nil because the "return location;" statement is executed BEFORE the Success call back function over the [objectManager getObjectsAtPath ...] method (see below codes).
I want the "return location;" statement must WAIT for the block variable "location" is filled with data from the Success call back function inside the [objectManager getObjectsAtPath ...] method. How can I do this?
Thank you for your help.
My generic method looks like:
-(KNSunGoogleLatitudeLongitudeGeometryLocation*)getSynchronouslyLatitudeLongitudeWithAddress:(NSString*)address
{
__block KNSunGoogleLatitudeLongitudeGeometryLocation* location = [[KNSunGoogleLatitudeLongitudeGeometryLocation alloc] init];
NSURL *baseURL = [NSURL URLWithString:#"http://maps.googleapis.com/maps/api"];
AFHTTPClient * client = [AFHTTPClient clientWithBaseURL:baseURL];
[client setDefaultHeader:#"Accept" value:RKMIMETypeJSON];
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
//1. KNSunGoogleLatitudeLongitudeGeometryLocation
RKObjectMapping *locationMapping = [RKObjectMapping mappingForClass:[KNSunGoogleLatitudeLongitudeGeometryLocation class]];
[locationMapping addAttributeMappingsFromArray:#[#"lat", #"lng"]];
//2. KNSunGoogleLatitudeLongitudeGeometry
RKObjectMapping *geometryMapping = [RKObjectMapping mappingForClass:[KNSunGoogleLatitudeLongitudeGeometry class]];
//3. KNSunGoogleLatitudeLongitude
RKObjectMapping *latLongMapping = [RKObjectMapping mappingForClass:[KNSunGoogleLatitudeLongitude class]];
//4. property/relationship mapping
[geometryMapping addPropertyMapping:[RKRelationshipMapping
relationshipMappingFromKeyPath:#"location"
toKeyPath:#"location"
withMapping:locationMapping]];
[latLongMapping addPropertyMapping:[RKRelationshipMapping
relationshipMappingFromKeyPath:#"geometry"
toKeyPath:#"geometry"
withMapping:geometryMapping]];
// 6. response
RKResponseDescriptor * responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:latLongMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"results"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
// 7
[objectManager addResponseDescriptor:responseDescriptor];
NSDictionary *queryParams;
queryParams = [NSDictionary dictionaryWithObjectsAndKeys:address, #"address", #"false", #"sensor", nil];
// 6
[objectManager getObjectsAtPath:#"http://maps.googleapis.com/maps/api/geocode/json"
parameters:queryParams
success:^(RKObjectRequestOperation * operaton, RKMappingResult *mappingResult)
{
//-----------
NSArray* results = [mappingResult array];
KNSunGoogleLatitudeLongitude* result0 = [results objectAtIndex:0];
KNSunGoogleLatitudeLongitudeGeometry* geometry = result0.geometry;
location= geometry.location;
NSLog(#"lat=%#, long=%#", location.lat, location.lng);
}
failure:^(RKObjectRequestOperation * operaton, NSError * error)
{
NSLog (#"failure: operation: %# \n\nerror: %#", operaton, error);
}];
return location; // note: ALWAYS RETURNs nil
}
You need to change what you want because it's a bad design. You should not block the requestor while the request is in progress. Instead you should pass a block to your general method that is executed from the block you pass to RestKit. This allows you to properly respect the asynchronous nature of the request.
If you did want to proceed with blocking, you could use look at using a semaphore. But, you would need to manage this yourself. And you wouldn't be able to trigger the request on the main thread - ever. These are significant hurdles to general usage and will probably cause you issues in the future.