I'm trying to coding for a task call API, the problem here is when screen was displayed
a task still run in background:
[[JPNetworkingManager sharedManager] getSnapNSendsForLetterWithID:letterID withIsArchived:isArchived completionHandler:^(NSArray *snapNSends, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
//do my code
});
}];
So how I can cancel this task? Thanks!
You need to make sure this api returns completion once
[[JPNetworkingManager sharedManager] getSnapNSendsForLetterWithID
Or do this
var once = true
[[JPNetworkingManager sharedManager] getSnapNSendsForLetterWithID:letterID withIsArchived:isArchived completionHandler:^(NSArray *snapNSends, NSError *error) {
if once {
dispatch_async(dispatch_get_main_queue(), ^{
//do my code
});
once = false
}
}
Related
When running this code...
-(IBAction)loginWithTwitter:(id)sender {
NSLog(#"Logging in with twitter");
ACAccountStore *accountStore = [[ACAccountStore alloc]init];
ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error) {
if (error) {
[self showError:error];
return;
}
if (granted) {
NSArray *accountsArray = [accountStore accountsWithAccountType:accountType];
if ([accountsArray count] > 1) {
NSLog(#"Multiple twitter accounts");
}
ACAccount *twitterAccount = [accountsArray objectAtIndex:0];
NSLog(#"%#", twitterAccount);
[self pushMainController];
}
}];
}
There is a 5-10 second delay before the pushMainControlleris actually called, even though the account information is logged almost immediately (after pre-authorization). If I move the pushMainController call after the block however, it happens immediately, the only problem being that a user isn't necessarily logged in at that point. I understand that it can take a second for a block to have a response due to variables such as network connectivity, but can someone help me understand this?
The completion block is not being done on the main queue. You need to ensure your UI code gets done on the main thread:
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error) {
if (error) {
[self showError:error];
return;
}
if (granted) {
NSArray *accountsArray = [accountStore accountsWithAccountType:accountType];
if ([accountsArray count] > 1) {
NSLog(#"Multiple twitter accounts");
}
ACAccount *twitterAccount = [accountsArray objectAtIndex:0];
NSLog(#"%#", twitterAccount);
dispatch_async(dispatch_get_main_queue(), ^{
[self pushMainController];
});
}
}];
You may have to wrap the showError call as well.
I'm using block for my APIs and the API class throws error via block like below code.
[HJHLifeAPI deletePlantWithIdentifier:identifier completionHandler:^(NSError *error) {
if (error) {
[[error alertView] show];
return ;
}
[self refresh:self.refreshControl];
}];
But the problem is that I use this pattern of codes in several places. As a result, I should write several duplicated codes for error handling. Is there any way to refactor this code? I think exception can be one solution, but I think Apple don't encourage developers to use it.
It's up to How your HJHLifeAPI is designed.
I usually use AFNetworking for API things and here's an example.
// This is the method like deletePlantWithIdentifier:
// It actually invoke __requestWitPath:
- (void)requestSomethingWithId:(NSString *)memId done:(NetDoneBlock)done
{
NSMutableDictionary *param_ = #{#"key":#"id"};
[self __requestWithPath:#"/apiPath.jsp" parameter:param_ done:done];
}
#pragma PRIVATE
- (void)__requestWithPath:(NSString *)apiPath parameter:(NSDictionary *)parameter done:(NetDoneBlock)done
{
AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:[NSURL URLWithString:SERVER_URL]];
AFHTTPRequestOperation *operation = [manager POST:apiPath parameters:parameter constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
done();
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Error Handle Here
}];
[operation start];
}
You can handle all errors in one __request....
Create the block
void(^errorHandler)(NSError *error) = ^(NSError *error) {
if (error) {
[[error alertView] show];
return ;
}
[self refresh:self.refreshControl];
}
Save it somewhere (don't forget to copy it)
self.errorHandler = errorHandler;
Reuse it everywhere:
[HJHLifeAPI deletePlantWithIdentifier:identifier completionHandler:self.errorHandler];
I have the following snippet of code below that fetches data from Parse using PFQueues in the background and returns data and a status. This structure is based off of waiting for the dispatch_group_t to notify that's it's completed all entered groups. Unfortunately dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
is called before the completion blocks call dispatch_group_leave. By the time the dispatch_group_leave() is called on any of the completion blocks, an EXC_BAD_INSTRUCTION is thrown. I've attached an image below for the instruction error. Does anyone know if I'm doing something wrong or if Parse has some annoyances that prevent me from using this method?
- (void)downloadAndCacheObjectsWithCompletion:(void (^)(NSError *))callback
{
__block NSError *downloadError1;
__block NSError *downloadError2;
__block NSError *downloadError3;
__block NSError *downloadError4;
NSLog(#"%#", NSStringFromSelector(_cmd));
dispatch_group_t downloadGroup = dispatch_group_create();
dispatch_group_enter(downloadGroup);
[self fetchDataWithCompletion:^(NSArray *artwork, NSError *error) {
downloadError1 = error;
dispatch_group_leave(downloadGroup);
}];
dispatch_group_enter(downloadGroup);
[self fetchDataWithCompletion:^(NSArray *artworkPhotos, NSError *error) {
downloadError2 = error;
dispatch_group_leave(downloadGroup);
}];
dispatch_group_enter(downloadGroup);
[self fetchDataWithCompletion:^(NSArray *artists, NSError *error) {
downloadError3 = error;
dispatch_group_leave(downloadGroup);
}];
dispatch_group_enter(downloadGroup);
[self fetchDataWithCompletion:^(NSArray *badges, NSError *error) {
downloadError4 = error;
dispatch_group_leave(downloadGroup);
}];
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
NSError *returnError;
if (downloadError1 || downloadError2 || downloadError3 || downloadError4) {
returnError = [[NSError alloc] initWithDomain:#"ParseFactory" code:-1 userInfo:#{NSLocalizedDescriptionKey: #"There was an error retrieving the content"}];
}
if (callback) {
callback(returnError);
}
});
}
- (void)fetchDataWithCompletion:(void(^)(NSArray *data, NSError *error))callback
{
NSLog(#"Fetching Data");
if ([self.cachedData objectForKey:kDataClassName]) {
if (callback) {
callback([self.cachedData objectForKey:kDataClassName], nil);
}
return;
}
PFQuery *dataQueue = [PFQuery queryWithClassName:kDataClassName];
dataQueue.cachePolicy = kPFCachePolicyCacheThenNetwork;
[dataQueue findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
[self.cachedData setObject:objects forKey:kDataClassName];
} else {
NSLog(#"Fetching Data Error: %#", error);
}
if (callback) {
callback(objects, error);
}
}];
}
The download process listed above is called from AppDelegate as such
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Register PFObject subclasses
[Data registerSubclass];
[Parse setApplicationId:#"appkey" clientKey:#"clientkey"];
[[ParseFactory sharedInstance] downloadAndCacheObjectsWithCompletion:^(NSError *error) {
}];
return YES;
}
Stack trace:
The error you're seeing indicates that your program calls dispatch_group_leave too many times. It's trivial to reproduce. I reproduced it with this program:
int main(int argc, const char * argv[])
{
#autoreleasepool {
dispatch_group_t group = dispatch_group_create();
dispatch_group_leave(group);
}
return 0;
}
Therefore I deduce that your fetchDataWithCompletion: method calls its completion block more than once. If you can't figure out why, edit your question to include the source code of that method (and any related methods or declarations).
I come in late, but it seems clear your issue comes from the kPFCachePolicyCacheThenNetwork.
Parse will call the completion block twice, one with cached data (even the 1st time), one with downloaded data... so your dispatch_group_leave will be called twice as much as your dispatch_group_enter.
I have a class which manages all calls to an api.
It has a method to manage this, lets call this callAPIMethod:
This method accepts a success and fail block.
Inside this method, I call uploadTaskWithRequest to make a call to an API.
Within the uploadTaskWithRequest completion handler I'd like to (depending on the result) pass results back through to either the success or fail blocks.
I'm having some issues with this. It works and is keeping everything super tidy but when I call callAPIMethod using the success/fail blocks it's locking up the UI/MainThread rather than being asynchronous as I'd expect.
How should I go about implementing this pattern? Or is there a better way to go about it?
I don't need to support pre-iOS7.
Thanks
Edit: Basic implementation discussed above.
- (void)callApiMethod:(NSString *)method withData:(NSString *)requestData as:(kRequestType)requestType success:(void (^)(id responseData))success failure:(void (^)(NSString *errorDescription))failure {
[redacted]
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session uploadTaskWithRequest:request
fromData:postData
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
failure(error.description);
} else {
NSError *jsonError;
id responseData = [NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:&jsonError];
if (jsonError) {
failure(jsonError.description);
} else {
success(responseData);
}
}
}];
[task resume];
}
CallAPI method, used as follows (from a UITableViewController):
[apiController callApiMethod:#"users.json?action=token"
withData:loginData
as:kRequestPOST
success:^(id responseData) {
if ([responseData isKindOfClass:[NSDictionary class]]) {
if ([responseData objectForKey:#"token"]) {
//Store token/credentials
} else if ([responseData objectForKey:#"error"]) {
//Error
[self displayErrorMessage:[responseData objectForKey:#"error"]];
return;
} else {
//Undefined Error
[self displayErrorMessage:nil];
return;
}
} else {
//Error
[self displayErrorMessage:nil];
return;
}
//If login success
}
failure:^(NSString *errorDescription) {
[self displayErrorMessage:errorDescription];
}];
Your NSURLSession code looks fine. I'd suggest adding some breakpoints so you can identify if it is deadlocking somewhere and if so, where. But nothing in this code sample would suggest any such problem.
I would suggest that you ensure that all UI calls are dispatched back to the main queue. This NSURLSessionUploadTask completion handler may be called on a background queue, but all UI updates (alerts, navigation, updating of UIView controls, etc.) must take place on the main queue.
Is it something wrong with the requestPanoramaNearCoordinate Google maps SDK method? cause it get´s stuck in the while loop. I´v written the loop cause I want to wait with executing the rest of the method until the asynchronous callback method has completed. But the the while loop loops infinitely. Is it my code that´s simply wrong?
__block GMSPanorama *panPhoto = nil;
__block BOOL finished = NO;
[self.panoService requestPanoramaNearCoordinate:ranLatLng callback:^(GMSPanorama *panorama, NSError *error) {
NSLog(#"panorama: %# error: %#", panorama, error);
panPhoto = panorama;
finished = YES;
}];
while (!finished) {
// Do nothing);
}
if (!panPhoto) return [self randomLatitudeLongitude];
return ranLatLng;
}
Why you launch async method and then doing loop? You must add block (with GMSPanorama argument) as parameter to your method and call this block inside callback:^(GMSPanorama *panorama, NSError *error){
Smth like that:
- (void) methodNameWithBlock:(BlockName)block;
__block GMSPanorama *panPhoto = nil;
__block BOOL finished = NO;
[self.panoService requestPanoramaNearCoordinate:ranLatLng callback:^(GMSPanorama *panorama, NSError *error) {
NSLog(#"panorama: %# error: %#", panorama, error);
panPhoto = panorama;
finished = YES;
BlockName handler = [block copy];
if (!ranLatLng){
handler([self randomLatitudeLongitude])
} else {
handler(ranLatLng)
}
}];
}