XCTest: Test asyncronous function without completion block - ios

I want to test a function in which an asynchronous task is called (async call to a webservice):
+(void)loadAndUpdateConnectionPool{
//Load the File from Server
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *responseCode, NSData *responseData, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)responseCode;
if([httpResponse statusCode] != 200){
// Show Error
}else{
// Save Data
// Post Notification to View
}
}];
}
Since the function doesn't have a completion handler, how can I test this in my XCTest class?
-(void)testLoadConnectionPool {
[ConnectionPool loadAndUpdateConnectionPool];
// no completion handler, how to test?
XCTAssertNotNil([ConnectionPool savedData]);
}
Is there any best practice, like a timeout or anything? (I know I can't use dispatch_sempaphore without redesigning the loadAndUpdateConnectionPool function).

You post a notification on complete (post a notification on error too), so you could add an expectation for that notification.
- (void)testLoadConnectionPool {
// We want to wait for this notification
self.expectation = [self expectationForNotification:#"TheNotification" object:self handler:^BOOL(NSNotification * _Nonnull notification) {
// Notification was posted
XCTAssertNotNil([ConnectionPool savedData]);
}];
[ConnectionPool loadAndUpdateConnectionPool];
// Wait for the notification. Test will fail if notification isn't called in 3 seconds
[self waitForExpectationsWithTimeout:3 handler:nil];
}

Related

NSUrlConnection not called via background fetch

-(void)application:(UIApplication )application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
NSLog(#"In performFetchWithCompletionHandler");
DownloadAlerts alertDownload = [[DownloadAlerts alloc]init];
[alertDownload getAlertsOnLocUpdate];
completionHandler(UIBackgroundFetchResultNewData); // We will add content here soon.
}
[NSURLConnection sendAsynchronousRequest:request
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *error) {
if ([data length] >0 && error == nil) {
NSLog(#"Hello01");
} else if ([data length] == 0 && error == nil) {
NSLog(#"Hello02");
} else if (error != nil) {
NSLog(#"Hello03");
}
}];
I am executing the above piece of code in a function that gets called via the performFetchWithCompletionHandler in appdelegate.h.
When I launch the app via background fetch, none of the 3 if blocks get executed. How do I implement this? I need to download data via background fetch. Kindly help... NSUrlSession also doens't work..
Have you tried to send synchronous request? I think your`s performFetchWithCompletionHandler method finishes before NSURLConnection receives the response. by the way, are you sure that performFetchWithCompletionHandler is called? Maybe you need to set capability for it?
Need to call "completionHandler(UIBackgroundFetchResultNewData);" after you received the data, pass it into "getAlertsOnLocUpdate" method as a parameter, and call it after you receive the answer from server (in completionHandler of URLConnection)

Segue throwing NSInternalInconsistencyException, with NSURLConnection sendAsynchronousRequest

So I have been trying to create an app for a website and I've got the "Log in" page working except when it won't transition to the next view.
This is the code that I believe is causing the problem :
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSString *str=[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
//NSLog(#"%#", str);
if ([str rangeOfString:#"The username or password you provided is invalid. Please try again."].location == NSNotFound) {
loginPageStatusLabel.text = #"Correct";
NSLog(#"Correct Login");
[self performSegueWithIdentifier:#"toHome" sender:self];
} else {
loginPageStatusLabel.text = #"Incorrect";
NSLog(#"Login Failed");
}
}];
* Assertion failure in -[UIKeyboardTaskQueue waitUntilAllTasksAreFinished], /SourceCache/UIKit_Sim/UIKit-2935.137/Keyboard/UIKeyboardTaskQueue.m:368
2014-05-11 00:06:51.426 LoginTests[3381:3e03] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UIKeyboardTaskQueue waitUntilAllTasksAreFinished]' may only be called from the main thread.'waitUntilAllTasksAreFinished]' may only be called from the main thread.
That is the error being thrown whenever I try to "Log in". The Segue with work if I run it alone, so I am assuming the problem is that the app is trying to go to the next View before it's ready and its causing an error.
I'm fairly new to Obj-C so if I have not posted the adequate information or not called things by the proper names please inform me.
Thank You!
I don't know what value you supplied for queue parameter, but given that your completion block is performing UI updates that must happen on the main thread, you can use [NSOperationQueue mainQueue] (or manually dispatch this code to the main queue). This queue parameter specifies what queue the completion block should be added to, and because you're doing UI related stuff in your completion block, this must be done on the main thread.
Having corrected that, if you are still have assertion errors, you can add an exception breakpoint and that will help confirm precisely where this assertion error is taking place. Or look at your stack trace.
I'd also, in addition to using [NSOperationQueue mainQueue], would suggest doing some more robust error handling:
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (!data) {
// for example, no internet connection or your web server is down
NSLog(#"request failed: %#", error);
return;
}
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
int statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
// for example, 404 would mean that your web site said it couldn't find the URL
// anything besides 200 means that there was some fundamental web server error
NSLog(#"request resulted in statusCode of %d", statusCode);
return;
}
}
// if we got here, we know the request was sent and processed by the web server, so now
// let's see if the login was successful.
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// I'm looking for "Welcome" ... I doubt that's right (but I don't have access to
// your web server, so I'm guessing). But the idea is that you have to find whatever
// appears after successful login that is not in the response if login failed
if ([responseString rangeOfString:#"Welcome"].location != NSNotFound) {
loginPageStatusLabel.text = #"Correct";
NSLog(#"Correct Login");
[self performSegueWithIdentifier:#"toHome" sender:self];
} else {
loginPageStatusLabel.text = #"Incorrect";
NSLog(#"Login Failed");
}
}];

How do i put requests with parameters into an NSArray and call in a loop later?

I want to call a unspecified number of URL requests which must be fired one after other. As the server can´t handle multiple requests with identical user-ID at the same time (only the last request is processed) i have to send my requests in an interval with about 1 seconds of gap. I did that within a dispatch_after block and increasing delays. But this is neither really secure nor elegant.
I´ve been just reading all day about GCD and want to try to change my code to send URL requests in a chain. My server connection class is build upon a NSURLConnection with asynchronuous request. That means it wouldn´t work with dispatch_async as the method call returns immediately back and the next request in the dispatch queue is called (which is probably immediately). But i have to wait for the response of the server until i may send the next request. My server connection class sends back via a delegate, but with dispatch_async it is never sending any deletate callbacks. Anyhow it wouldn´t work this way.
Probably it is better to put all requests into a NSArray and then call a method which will send requests from the array to the connection class and the delegate callback will pop the item from the array and sending the next request till all requests are done. Unfortunately i absolutely have no idea how i could store the requests and parameters in an array. Currently my call looks like that:
- (void)sendSettings
{
//NSLog(#"begins: %s", __FUNCTION__);
dataProtocol = [[BackgroundSoundConnection alloc] init];
[dataProtocol setDelegate:self];
//double delayInSeconds;
//dispatch_time_t popTime;
//delayInSeconds = 0.1f;
if (self.switch1.on)
{
if (![self.pinnedSettings.nextCall.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"setBackgroundSoundNextCall/%#", self.sound.globalId] httpMethod:#"PUT" sound:self.sound stickerType:#"nextCall" personMSISDN:nil];
}
} else {
if ([self.pinnedSettings.nextCall.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"disableBackgroundSoundNextcall"] httpMethod:#"PUT" sound:nil stickerType:nil personMSISDN:nil];
}
}
if (self.switch2.on)
{
if (![self.pinnedSettings.incomingCalls.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"setBackgroundSoundIncoming/%#", self.sound.globalId] httpMethod:#"PUT" sound:self.sound stickerType:#"incomingCalls" personMSISDN:nil];
}
} else {
if ([self.pinnedSettings.incomingCalls.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"disableBackgroundSoundIncoming"] httpMethod:#"PUT" sound:nil stickerType:nil personMSISDN:nil];
}
}
if (self.switch3.on)
{
if (![self.pinnedSettings.outgoingCalls.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"setBackgroundSoundOutgoing/%#", self.sound.globalId] httpMethod:#"PUT" sound:self.sound stickerType:#"outgoingCalls" personMSISDN:nil];
}
} else {
if ([self.pinnedSettings.outgoingCalls.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"disableBackgroundSoundOutgoing"] httpMethod:#"PUT" sound:nil stickerType:nil personMSISDN:nil];
}
}
for (int i = 0; i < [personArray count]; i++)
{
if (![personArray[i] connectedToServer])
{
NSLog(#"sound: %#", [personArray[i] soundId]);
NSLog(#"msisdn: %#", [personArray[i] personMSISDN]);
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"setBackgroundSoundContext/%#/%#", [personArray[i] soundId], [personArray[i] personMSISDN]] httpMethod:#"PUT" sound:self.sound stickerType:#"contextCalls" personMSISDN:[personArray[i] personMSISDN]];
}
}
[self animateViewAway:self.view];
}
A part of the request parameters is already in an array. I could use this array and push the other request parameters into it and then sending the first parameter. And after server responded send the next request triggered by the callback from the delegate. Probably this would work.
But i´m just wondering if there isn´t andy way to que the requests a dispatch queue. But how could i que the delegates as well? Or what do i have to do that the queue will wait until the server responds? I´d like to avoid rewriting my server connection class from asynchronous to synchronous URLConnection which would probably make the difference.
Can anybody point me to a solution with asynchronous URLConnection and dispatch_async?
I haven´t seen the possibilites of NSOperation and NSOperationQueue yet. In the podcast of Jeff Kelley i´ve heard that the advantage of GCD over NSOperation is the dependencies feature. http://iphreaksshow.com/042-iphreaks-show-concurrency-with-jeff-kelley/
Or did i mix up everything? What would you recommend?
A complete NSURLRequest represents a complete request by containing a path, query params or body, headers, etc. You can build several of these to represent your several server requests.
NSURLConnection provides an asynch send (sendAsynchronousRequest:queue:completionHandler:). A naive way to sequence a series of requests, is to nest the requests in completion blocks as follows...
[NSURLConnection sendAsynchronousRequest:request0 queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
[NSURLConnection sendAsynchronousRequest:request1 queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
// and so on... yikes, we'll have code in column 1000 pretty soon
But it should be clear that this is a weak idea. You can get the same effect for sequencing an arbitrary number of requests with pretty compact code as follows:
- (void)doManyRequests:(NSArray *)requests withResults:(NSMutableArray *)results completion:(void (^)(void))completion {
if (!requests.count) {
return completion();
}
NSURLRequest *nextRequest = requests[0];
NSArray *remainingRequests = [requests subarrayWithRange:NSMakeRange(1, requests.count-1)];
[NSURLConnection sendAsynchronousRequest:nextRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
[results addObject:data];
[self doManyRequests:remainingRequests withResults:results completion:completion];
}];
}
Now, as you suggested, prepare several requests and place them in an array:
NSURLRequest *request0 = // however you build this for a given user id
NSURLRequest *request1 = // etc.
NSURLRequest *request2 = // etc.
NSArray *requests = #[request0, request1, request2];
NSMutableArray *results = [NSMutableArray array];
[self doManyRequests:requests withResults:results completion:^{
NSLog(#"this will be an array of NSData objects %#", results);
}];

NSURLConnection Progress Bar with sendAsynchronousRequest Objective-C

I am downloading a bunch of largish zip files with the following method. It can take a little while and so I'd like to display a progress bar.
I've researched how to do with with the delegate methods for NSURLConnection and it seems straightforward, however I want to achieve the same thing with "sendAsynchronousRequest". How can I get the number of bytes downloaded as it downloads as well as the total number of bytes expected so that I can display a progress bar? I understand that I cannot use the delegate methods if I kick off a download in the manner I am doing it.
// Begin the download process
- (void)beginDownload:(NSMutableArray *)requests {
// Now fire off a bunch of requests asynchrounously to download
self.outstandingRequests = [requests count];
for (NSURLRequest *request in requests) { // Get the request
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// Error check
if ( error != nil ) {
// The alertview for login failed
self.appDelegate.warningView.title = #"Refresh Error!";
self.appDelegate.warningView.message = [error localizedDescription];
// Show the view
[self.appDelegate.warningView show];
// Debug
if ( DEBUG ) {
NSLog(#"A request failed - %d left!",self.outstandingRequests);
}
}
else {
// Debug
if ( DEBUG ) {
NSLog(#"A request is done - %d left!",self.outstandingRequests);
}
}
// Decrement outstanding requests
self.outstandingRequests--;
// No requests are left
if (self.outstandingRequests == 0) {
// Debug
if ( DEBUG ) {
NSLog(#"All requests are done!");
}
// Get rid of loading view
[self performSelector:#selector(dismissLoadingView) withObject:nil afterDelay:0.15];
}
}];
}
}
https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLConnectionDownloadDelegate_Protocol/NSURLConnectionDownloadDelegate/NSURLConnectionDownloadDelegate.html#//apple_ref/doc/uid/TP40010954-CH2-SW1
How to make an progress bar for an NSURLConnection when downloading a file?
http://iphonedevsdk.com/forum/iphone-sdk-development/24233-nsurlconnection-with-uiprogressbar.html
http://iphoneeasydevelopment.blogspot.com/2011/10/use-progess-bar-when-downloading-file.html
sendAsynchronousRequest won't work for your purposes as it doesn't call your callback until the request has completed. You'll need to use initRequest:withDelegate: and handle your own data accumulation.
When the header is received (possibly multiple times for redirects) your didReceiveResponse method will be called, you can pick up the expected size there:
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
_expectedBytes = (NSUInteger)response.expectedContentLength;
_data = [NSMutableData dataWithCapacity:_expectedBytes];
// make a progress update here
}
You'll receive a call to the delegate method didReceiveData each time a chunk of data is received, so you know how much data you've received up to this point.
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[_data appendData:data];
_receivedBytes = _data.length;
// make a progress update here
}

AFNetworking: Handle error globally and repeat request

I have a use case that should be rather common but I can't find an easy way to handle it with AFNetworking:
Whenever the server returns a specific status code for any request, I want to:
remove a cached authentication token
re-authenticate (which is a separate request)
repeat the failed request.
I thought that this could be done via some global completion/error handler in AFHTTPClient, but I didn't find anything useful. So, what's the "right" way to do what I want? Override enqueueHTTPRequestOperation: in my AFHTTPClient subclass, copy the operation and wrap the original completion handler with a block that does what I want (re-authenticate, enqueue copied operation)? Or am I on the wrong track altogether?
Thanks!
EDIT: Removed reference to 401 status code, since that's probably reserved for HTTP basic while I'm using token auth.
I use an alternative means for doing this with AFNetworking 2.0.
You can subclass dataTaskWithRequest:success:failure: and wrap the passed completion block with some error checking. For example, if you're working with OAuth, you could watch for a 401 error (expiry) and refresh your access token.
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)urlRequest completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))originalCompletionHandler{
//create a completion block that wraps the original
void (^authFailBlock)(NSURLResponse *response, id responseObject, NSError *error) = ^(NSURLResponse *response, id responseObject, NSError *error)
{
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
if([httpResponse statusCode] == 401){
NSLog(#"401 auth error!");
//since there was an error, call you refresh method and then redo the original task
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//call your method for refreshing OAuth tokens. This is an example:
[self refreshAccessToken:^(id responseObject) {
NSLog(#"response was %#", responseObject);
//store your new token
//now, queue up and execute the original task
NSURLSessionDataTask *originalTask = [super dataTaskWithRequest:urlRequest completionHandler:originalCompletionHandler];
[originalTask resume];
}];
});
}else{
NSLog(#"no auth error");
originalCompletionHandler(response, responseObject, error);
}
};
NSURLSessionDataTask *task = [super dataTaskWithRequest:urlRequest completionHandler:authFailBlock];
return task;
}
In the AFHTTPClient's init method register for the AFNetworkingOperationDidFinishNotification which will be posted after a request finishes.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(HTTPOperationDidFinish:) name:AFNetworkingOperationDidFinishNotification object:nil];
In the notification handler check the status code and copy the AFHTTPRequestOperation or create a new one.
- (void)HTTPOperationDidFinish:(NSNotification *)notification {
AFHTTPRequestOperation *operation = (AFHTTPRequestOperation *)[notification object];
if (![operation isKindOfClass:[AFHTTPRequestOperation class]]) {
return;
}
if ([operation.response statusCode] == 401) {
// enqueue a new request operation here
}
}
EDIT:
In general you should not need to do that and just handle the authentication with this AFNetworking method:
- (void)setAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block;
Here is the Swift implementation of user #adamup 's answer
class SessionManager:AFHTTPSessionManager{
static let sharedInstance = SessionManager()
override func dataTaskWithRequest(request: NSURLRequest!, completionHandler: ((NSURLResponse!, AnyObject!, NSError!) -> Void)!) -> NSURLSessionDataTask! {
var authFailBlock : (response:NSURLResponse!, responseObject:AnyObject!, error:NSError!) -> Void = {(response:NSURLResponse!, responseObject:AnyObject!, error:NSError!) -> Void in
var httpResponse = response as! NSHTTPURLResponse
if httpResponse.statusCode == 401 {
//println("auth failed")
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), { () -> Void in
self.refreshToken(){ token -> Void in
if let tkn = token{
var mutableRequest = request.mutableCopy() as! NSMutableURLRequest
mutableRequest.setValue(tkn, forHTTPHeaderField: "Authorization")
var newRequest = mutableRequest.copy() as! NSURLRequest
var originalTask = super.dataTaskWithRequest(newRequest, completionHandler: completionHandler)
originalTask.resume()
}else{
completionHandler(response,responseObject,error)
}
}
})
}
else{
//println("no auth error")
completionHandler(response,responseObject,error)
}
}
var task = super.dataTaskWithRequest(request, completionHandler:authFailBlock )
return task
}}
where refreshToken (...) is an extension method I wrote to get a new token from the server.
Took a similar approach, but I couldn't get the status code object with phix23's answer so I needed a different plan of action. AFNetworking 2.0 changed a couple of things.
-(void)networkRequestDidFinish: (NSNotification *) notification
{
NSError *error = [notification.userInfo objectForKey:AFNetworkingTaskDidCompleteErrorKey];
NSHTTPURLResponse *httpResponse = error.userInfo[AFNetworkingOperationFailingURLResponseErrorKey];
if (httpResponse.statusCode == 401){
NSLog(#"Error was 401");
}
}
If you are subclassing AFHTTPSessionManager or using directly an AFURLSessionManager you could use the following method to set a block executed after the completion of a task:
/**
Sets a block to be executed as the last message related to a specific task, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didCompleteWithError:`.
#param block A block object to be executed when a session task is completed. The block has no return value, and takes three arguments: the session, the task, and any error that occurred in the process of executing the task.
*/
- (void)setTaskDidCompleteBlock:(void (^)(NSURLSession *session, NSURLSessionTask *task, NSError *error))block;
Just perform whatever you want to do for each tasks of the session in it:
[self setTaskDidCompleteBlock:^(NSURLSession *session, NSURLSessionTask *task, NSError *error) {
if ([task.response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
if (httpResponse.statusCode == 500) {
}
}
}];
EDIT:
In fact if you need to handle an error returned in the response object the above method won't do the job.
One way if you are subclassing AFHTTPSessionManager could be to subclass and set a custom response serializer with it's responseObjectForResponse:data:error: overloaded like that:
#interface MyJSONResponseSerializer : AFJSONResponseSerializer
#end
#implementation MyJSONResponseSerializer
#pragma mark - AFURLResponseSerialization
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
id responseObject = [super responseObjectForResponse:response data:data error:error];
if ([responseObject isKindOfClass:[NSDictionary class]]
&& /* .. check for status or error fields .. */)
{
// Handle error globally here
}
return responseObject;
}
#end
and set it in your AFHTTPSessionManager subclass:
#interface MyAPIClient : AFHTTPSessionManager
+ (instancetype)sharedClient;
#end
#implementation MyAPIClient
+ (instancetype)sharedClient {
static MyAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[MyAPIClient alloc] initWithBaseURL:[NSURL URLWithString:MyAPIBaseURLString]];
_sharedClient.responseSerializer = [MyJSONResponseSerializer serializer];
});
return _sharedClient;
}
#end
To ensure that multiple token refreshes are not issued at around the same time, it is beneficial to either queue your network requests and block the queue when the token is refreshing, or add a mutex lock (#synchronized directive) to your token refresh method.

Resources