While trying to apply TDD to asynchronous code I found out that the same code that was working in the deployment target, didn't work in the test target.
One of the examples of this problems I found using CLLocationManager:
- (void)testReceivingLocation
{
locationManager = [[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.delegate = self;
locationManager.pausesLocationUpdatesAutomatically = NO;
if ([CLLocationManager locationServicesEnabled])
{
[locationManager startUpdatingLocation];
}
startLocation = nil;
NSDate *until = [NSDate dateWithTimeIntervalSinceNow:10];
while ([until timeIntervalSinceNow] > 0)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:until];
}
XCTAssert(alreadyReceivedLocation, #"Location wasn't received.");
}
-(void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
alreadyReceivedLocation = true;
// Never actually get there.
}
What can be the problem?
You should elaborate a bit more on how [SomeClass performActionWithAsyncResponse] does its job.
Supposing completionWithResult is called in a block on the main queue, this won't work because the thread ends after the the test method is finished. Which is not the case in production, because the app keeps running.
Usually I use code like this to wait for async calls which calls back on the main queue in tests.
NSDate *until = [NSDate dateWithTimeIntervalSinceNow:30];
while ([loopUntil timeIntervalSinceNow] > 0)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:until];
}
You could also stop the while loop with a condition that indicates if asynchronous work is done, using a property of the test for example.
See this post for more about asynchronous test patterns:
Pattern for unit testing async queue that calls main queue on completion
Related
In the iOS7 SDK and while using MRC, the following piece of code does not return the shared instance. On runtime it just hangs and it does not move in to the next line of code.
+(id)getInstance
{
static dispatch_once_t pred;
static IAPManager *inAppManager = nil;
dispatch_once(&pred, ^{
inAppManager = [[IAPManager alloc] init];
});
return inAppManager;
}
What is the reason for this anomaly? This is how I am calling
IAPManager *iapManager = [IAPManager getInstance];
if ([iapManager canMakePurchases]) {
[iapManager loadStore:proUpgradeProductId];
}else{
UIAlertView *aView = [[UIAlertView alloc]initWithTitle:#"" message:#"This device is not able or allowed to make payments" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[aView show];
}
The original getInstance method is absolutely fine (I would declare it as returning IAPManager*, but it doesn't make any difference to how it works). "static" variables are initialised once wherever they are.
The problem is most likely that getInstance is called from [[IAPManager alloc] init]. Calling dispatch_once from inside the dispatch_once is an instant deadlock. To find out, you just set a breakpoint in getInstance on the dispatch_once statement. It should be hit once during the first call, and then probably again, and the original call will be on the stack. Or just wait until it hangs, then check in the debugger where each thread is. You'll find a thread waiting for dispatch_once to finish.
Alternatively, it is possible that the init method just doesn't return. Maybe it does some network access that doesn't finish. To step into the code, set a breakpoint on the first (and only) line of the block, that is the [[IAPManager alloc] init] line. Once that breakpoint is reached, you can step through the code.
Diclare inAppManager object outside the method. and use the following code
static IAPManager *inAppManager = nil;
+(id)getInstance
{
if (nil != inAppManager) {
return inAppManager;
}
static dispatch_once_t pred;
dispatch_once(&pred, ^{
inAppManager = [[IAPManager alloc] init];
});
return inAppManager;
}
That may help you.
Thanks
Satya
Try this:
+(instancetype)getInstance
{
static id inAppManager;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
inAppManager = [[[self class] alloc] init];
});
return inAppManager;
}
I create new NSURLSession with following configs
if (!self.session) {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:[self uniquieIdentifier]];
config.discretionary = NO;
self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
}
and on after pressing a button I am trying to stop all current download tasks.
[[[self session] delegateQueue] setSuspended:YES];
[[self session] invalidateAndCancel];
Nevertheless I get responses in delegate method didFinishDownloadingToURL, and I am pretty sure that no new sessions or download task are created after this point. How to stop all task from happening?
I do not reccommend to use invalidateAndCancel method cause the queue and its identifier keeps invalidated and cannot be reused untill you reset the whole device.
NSURLSession class reference
I use this code to cancel all pending tasks.
- (void) cancelDownloadFiles
{
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionTask *_task in downloadTasks)
{
[_task cancel];
id<FFDownloadFileProtocol> file = [self getFileDownloadInfoIndexWithTaskIdentifier:_task.taskIdentifier];
[file.downloadTask cancel];
// Change all related properties.
file.isDownloading = NO;
file.taskIdentifier = -1;
file.downloadProgress = 0.0;
}
}];
cancel = YES;
}
That is the expected behaviour, when you cancel tasks on a session they might still call the delegate method.
Have you check the state of the given task? It should be NSURLSessionTaskStateCanceling.
I have a http request to make whenever a new location has been found asynchronously, for handling request, i have create a class called background requester which takes care of all these requests. The following code sample is as follows.
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
dispatch_queue_t queue;
queue = dispatch_queue_create("com.test.sample", NULL); //create a serial queue can either be null or DISPATCH_QUEUE_SERIAL
dispatch_async(queue,
^{
if (bgTask == UIBackgroundTaskInvalid)
{
bgTask=[[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:
^{
DDLogInfo(#"Task =%d",bgTask);
DDLogInfo(#"Ending bground task due to time expiration");
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
BackgroundRequester *request = [[BackgroundRequester alloc] initwithLocation:self.currentLocation];
[request start];
DDLogInfo(#"Task =%d",bgTask);
DDLogInfo(#"bg Task remaining time=%f",[[UIApplication sharedApplication] backgroundTimeRemaining]);
});
}
//background requester class
//the start function will inturn calll the callAsynchrnously method.
-(void) callAsynchronously:(NSString *)url
{
DDLogInfo(#"Calling where am i from background");
DDLogInfo(#"Url =%#",reqURL);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLCacheStorageNotAllowed timeoutInterval:20.0f];
responseData = [[NSMutableData alloc] init];
connect = [NSURLConnection connectionWithRequest:request delegate:self];
[connect start];
}
You cannot use connectionWithRequest from a background queue (without scheduling the connection in some runloop). Either
use sendSynchronousRequest (which is fine to do if you're using it from a background queue like this), or
schedule the connection in a run loop. If you dig through the AFNetworking code, you'll see they create a run loop on a dedicated thread, which strikes me as the most elegant solution if you really need the NSURLConnectionDataDelegate methods.
You can also use the main run loop (though I'm less crazy about that solution), e.g., something like:
connect = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connect scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[connect start];
Also note, I'm not using connectionWithRequest (because that starts the connection immediately, which is incompatible with your calling of start; only use start if you use initWithRequest with a startImmediately of NO). If you try to do start in conjunction with connectionWithRequest, it can cause problems.
I think the sendSynchronousRequest is simplest (and saves you from having to write any of the NSURLConnectionDataDelegate methods, too). But if you need the NSURLConnectionDataDelegate methods (e.g. you need progress updates, you're using a streaming protocol, etc.), then use the scheduleInRunLoop method.
I am trying get current location name using coreloaction framework in Xcode through CLlocationManager object. When i am testing my app on my iPod touch CLlocationManager give value for some places but return [null] for many places. how can I rectify this problem? help me following is coding.
-(void)viewDidLoad
{
self.locationManager = [[CLLocationManager alloc] init] ;
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
[self.locationManager startUpdatingLocation];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
CLGeocoder *geoCoder = [[CLGeocoder alloc]init];
[geoCoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray *placemarks, NSError *error)
{
if(error)
{
NSLog(#"error:%#",[error localizedDescription ]);
NSLog(#"Check your inetconnection");
}
_placedetail=nil;
self.placedetail=[placemarks objectAtIndex:0];
self.Label.text=self.placedetail.locality;
}];
[manager stopUpdatingLocation];//for stop process update
}
This question is long time ago. but ill add my solution here for future reference.
I had the same problem. i think its happening due to maps are not updated yet in for some small cities. or locality conflict. to avoid this crash try error handling with giving sub locality.
Example : if locality not available go with administrative area like bigger option.
I've got the problem when I tried to do asynchronous requests to server from background thread. I've never got results of those requests. Simple example which shows the problem:
#protocol AsyncImgRequestDelegate
-(void) imageDownloadDidFinish:(UIImage*) img;
#end
#interface AsyncImgRequest : NSObject
{
NSMutableData* receivedData;
id<AsyncImgRequestDelegate> delegate;
}
#property (nonatomic,retain) id<AsyncImgRequestDelegate> delegate;
-(void) downloadImage:(NSString*) url ;
#end
#implementation AsyncImgRequest
-(void) downloadImage:(NSString*) url
{
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:20.0];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
receivedData=[[NSMutableData data] retain];
} else {
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[delegate imageDownloadDidFinish:[UIImage imageWithData:receivedData]];
[connection release];
[receivedData release];
}
#end
Then I call this from main thread
asyncImgRequest = [[AsyncImgRequest alloc] init];
asyncImgRequest.delegate = self;
[self performSelectorInBackground:#selector(downloadImage) withObject:nil];
method downloadImage is listed below:
-(void) downloadImage
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[asyncImgRequest downloadImage:#"http://photography.nationalgeographic.com/staticfiles/NGS/Shared/StaticFiles/Photography/Images/POD/l/leopard-namibia-sw.jpg"];
[pool release];
}
The problem is that method imageDownloadDidFinish is never called. Moreover none of methods
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse*)response
are called. However if I replace
[self performSelectorInBackground:#selector(downloadImage) withObject:nil];
by
[self performSelector:#selector(downloadImage) withObject:nil];
everything is working correct. I assume that the background thread dies before async request is finished it job and this causes the problem but I'm not sure. Am I right with this assumptions? Is there any way to avoid this problem?
I know I can use sync request to avoid this problem but it's just simple example, real situation is more complex.
Thanks in advance.
Yes, the thread is exiting. You can see this by adding:
-(void)threadDone:(NSNotification*)arg
{
NSLog(#"Thread exiting");
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(threadDone:)
name:NSThreadWillExitNotification
object:nil];
You can keep the thread from exiting with:
-(void) downloadImage
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[self downloadImage:urlString];
CFRunLoopRun(); // Avoid thread exiting
[pool release];
}
However, this means the thread will never exit. So you need to stop it when you're done.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
CFRunLoopStop(CFRunLoopGetCurrent());
}
Learn more about Run Loops in the Threading Guide and RunLoop Reference.
You can start the connection on a background thread but you have to ensure the delegate methods are called on main thread. This cannot be done with
[[NSURLConnection alloc] initWithRequest:urlRequest
delegate:self];
since it starts immediately.
Do this to configure the delegate queue and it works even on secondary threads:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest
delegate:self
startImmediately:NO];
[connection setDelegateQueue:[NSOperationQueue mainQueue]];
[connection start];
NSURLRequests are completely asynchronous anyway. If you need to make an NSURLRequest from a thread other than the main thread, I think the best way to do this is just make the NSURLRequest from the main thread.
// Code running on _not the main thread_:
[self performSelectorOnMainThread:#selector( SomeSelectorThatMakesNSURLRequest )
withObject:nil
waitUntilDone:FALSE] ; // DON'T block this thread until the selector completes.
All this does is shoot off the HTTP request from the main thread (so that it actually works and doesn't mysteriously disappear). The HTTP response will come back into the callbacks as usual.
If you want to do this with GCD, you can just go
// From NOT the main thread:
dispatch_async( dispatch_get_main_queue(), ^{ //
// Perform your HTTP request (this runs on the main thread)
} ) ;
The MAIN_QUEUE runs on the main thread.
So the first line of my HTTP get function looks like:
void Server::get( string queryString, function<void (char*resp, int len) > onSuccess,
function<void (char*resp, int len) > onFail )
{
if( ![NSThread isMainThread] )
{
warning( "You are issuing an HTTP request on NOT the main thread. "
"This is a problem because if your thread exits too early, "
"I will be terminated and my delegates won't run" ) ;
// From NOT the main thread:
dispatch_async( dispatch_get_main_queue(), ^{
// Perform your HTTP request (this runs on the main thread)
get( queryString, onSuccess, onFail ) ; // re-issue the same HTTP request,
// but on the main thread.
} ) ;
return ;
}
// proceed with HTTP request normally
}