send a second request after the first is completed - ios

I have a method (requestData) that can be called several times in my ViewController but the first time the ViewController is loaded (in ViewDidLoad method) I need to call it two times BUT the second request should be sent only after the first request has completed:
- (void)viewDidLoad {
[super viewDidLoad];
dataForPlot = 1;
[self requestData: dataForPlot];
dataForPlot = 2;
[self requestData: dataForPlot];
}
- (void) requestData: (int) forPlot {
...
[urlRequest startWithCompletion:^(URLRequest *request, NSData *data, NSError *error, BOOL success) {
if (success) {
if (forPlot == 1) {
...
}
else if (forPlot == 2) {
...
}
}
}
I know I probably need to use blocks but, even if I've tried to read some tutorials, I don't know how.
Anyone could help me ?
Thanks, Corrado
Here is what I've implemented following Duncan suggestion:
typedef void(^myCompletion)(BOOL);
- (void)viewDidLoad {
[super viewDidLoad];
[self requestData:^(BOOL finished) { // first request
if(finished) {
NSLog(#"send second request");
[self requestData: ^(BOOL finished) {}]; // second request
}
}];
- (void) requestData: (myCompletion) compblock {
...
[urlRequest startWithCompletion:^(URLRequest *request, NSData *data, NSError *error, BOOL success) {
if (success) {
...
NSLog(#"request completed");
compblock(YES);
}
}

Don't call the second request until the first completes:
- (void) requestData: (int) forPlot {
...
[urlRequest startWithCompletion:^(URLRequest *request, NSData *data, NSError *error, BOOL success) {
if (success) {
if (forPlot == 1) {
...
dataForPlot = 2;
[self requestData: dataForPlot];
}
else if (forPlot == 2) {
...
}
}
}

Refactor your requestData method to take a completion block.
In your requestData method, When the url request completes, invoke the completion block.
In viewDidLoad, use a completion block that calls requestData a second time, this time with an empty completion block.
In your other calls to the requestData method, pass in a nil completion block (or whatever other action you need to trigger when the request finishes)

Related

Multithreading method calls in Objective-C

Say we have a class A and we call its method:
- (void)saveCompanyOnServerWithTitle:(NSString *)title {
[self.interactor performSaveCompanyNetworkRequestWithCompanyTitle:title];
}
And Interactor implements the method as follows:
- (void)performSaveCompanyNetworkRequestWithCompanyTitle:(NSString *)title {
typeof(self) __weak weakSelf = self;
[self.uploadService addUploadOperationWithCompanyTitle:title completion:^(id result, NSError *error) {
if (error){
[weakSelf.presenter didPerformSaveCompanyNetworkRequestWithError:error];
return;
}
[weakSelf.presenter didPerformSaveCompanyNetworkRequestWithResponse:result];
}];
}
UploadService's method implementation looks like:
- (void)addUploadOperationWithCompanyTitle:(NSString *)title completion:(RCNUploadOperationBlock)completionBlock {
NSURLRequest *request = [RCNRequestFactory createCompanyUploadingRequestWithCompanyName:title];
RCNUploadOperationBlock block = ^void(id result, NSError *error){
if (error){
completionBlock(result, error);
} else {
completionBlock(result, nil);
}
};
RCNCompanyUploadingOperation *uploadingOperation = [[RCNCompanyUploadingOperation alloc] initWithRequest:request
completionBlock:[block copy]];
uploadingOperation.name = title;
[self.uploadingQueue addOperation:uploadingOperation];
}
Now imagine we call saveCompanyOnServerWithTitle: in several queues.
Should I wrap the method call in something like
dispatch_async(dispatch_serial_queue, ^{
[self.interactor performSaveCompanyNetworkRequestWithCompanyTitle:title]
});
Or should I just wrap the method's implementation, e.g.
typeof(self) __weak weakSelf = self;
dispatch_async(dispatch_serial_queue, ^{
[weakSelf.uploadService addUploadOperationWithCompanyTitle:title completion:^(id result, NSError *error) {
if (error){
[weakSelf.presenter didPerformSaveCompanyNetworkRequestWithError:error];
return;
}
[weakSelf.presenter didPerformSaveCompanyNetworkRequestWithResponse:result];
}];
});
I want to make "addUploadOperationWithCompanyTitle:" calls serial. I know the method itself is an asynchronous one.

Nested completion block not being called

I've got a nested completion block set to initialize an SKProduct. If I put a breakpoint right before the completion block, the inside block executes OK (I can poll for _product) but the completion block never fires.
If I call completion() immediately within this block it executes, but not if I call it within the completion block of the nested block.
- (void)initializeProduct:(NSString*)bundleId completion:(void(^)(BOOL finished, NSError* error))completion
{
// completion(YES, nil); If I call completion here, it executes
NSSet* dataSet;
dataSet = [[NSSet alloc] initWithArray:#[bundleId]];
[IAPShare sharedHelper].iap.production = NO;
if(![IAPShare sharedHelper].iap) {
NSSet* dataSet = [[NSSet alloc] initWithObjects:bundleId, nil];
[IAPShare sharedHelper].iap = [[IAPHelper alloc] initWithProductIdentifiers:dataSet];
[IAPShare sharedHelper].iap.products = #[bundleId];
}
[[IAPShare sharedHelper].iap requestProductsWithCompletion:^(SKProductsRequest* request,SKProductsResponse* response)
{
if(response > 0 ) {
_product = [[IAPShare sharedHelper].iap.products objectAtIndex:0]; // We get this far
// ^ breakpoint on this line shows _product is now an SKProduct
completion(YES, nil); // but this never fires
}
else
{
completion(YES, error);
// ^ yes, error is defined in my code, I'm being lazy
}
}];
}
I'm calling it in a method like this with another completion block (and it never enters the block):
- (void)priceForBundleId:(NSString *)bundleId completion:(void(^)(NSString* price))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NO), ^{
[self initializeProduct:bundleId completion:^(BOOL finished, NSError *error) {
// Breakpoint placed here never catches
if (!error)
{
NSString* price = [RSStore priceAsStringForLocale:_product.priceLocale price:_product.price];
[defaults setValue:price forKey:#"bigBoxPrice"];
completion(price);
}
else
{
completion(nil);
}
}];
});
}

iOS- Saving data inside a block

I've been struggling on this for several days so any would be appreciated. I'm trying to save the players array below and display it in a UITableView. I'd like to save it so I can display the local player's friends. I've tried several different things but something that looks it's working for others is this.
__block NSArray *friends;
- (void) loadPlayerData: (NSArray *) identifiers {
[GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
if (error != nil) {
// Handle the error.
}
if (players != nil) {
friends = players;
NSLog(#"Inside: %#", friends); //Properly shows the array
}
}];
NSLog(#"Outside: %#", friends): //Doesn't work, shows nil
}
But friends is still nil/null afterwards. Am I doing something wrong? Is there any way to save players and use it in a UITableView? Thanks.
***EDIT***
So here's the solution I put together.
typedef void(^CallbackBlock)(id object);
+ (void) retrieveFriends: (CallbackBlock)callback {
GKLocalPlayer *lp = [GKLocalPlayer localPlayer];
if (lp.authenticated) {
[lp loadFriendsWithCompletionHandler:^(NSArray *friends, NSError *error) {
if (friends != nil) {
[self loadPlayerDataWithIdentifiers:friends callback:^(NSArray *playersInfo) {
if (callback) callback(playersInfo);
}];
}
}];
}
}
+ (void) loadPlayerDataWithIdentifiers: (NSArray *) identifiers callback:(CallbackBlock)callback {
[GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
if (error != nil) {
// Handle the error.
}
if (players != nil) {
if (callback) callback(players);
}
}];
}
The only thing is, my UITableView is in another class so I tried doing this and making the two methods above public. info isn't printing out anything. Any ideas?
[GameCenterHelper retrieveFriends:^(NSArray *info) {
NSLog(#"Friends Info: %#", info);
}];
Use callback blocks.
typedef void(^CallbackBlock)(id object);
- (void)loadPlayerDataWithIdentifiers:(NSArray *)identifiers callback:(CallbackBlock)callback {
[GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
if (error != nil) {
// Handle the error.
}
if (callback) callback(players);
}];
}
You imply in your question that this is for a table view. If so, you need to reload your table after the data has been loaded.
[self loadPlayerDataWithIdentifiers:identifiers callback:^(NSArray *players) {
self.players = players;
[self.tableView reloadData];
}];
Crimson Chris is correct.
The other option is use GCD to wait for the response to comeback.
Or change this to synchronous call as you want to get the results immediately.

Is it possible to institute a timeout for SKStoreProductViewController loadProductWithParameters?

I'm currently calling storeViewController loadProductWithParameters via dispatch_async . Is it possible to set a timeout value so it only tries to fetch the results for X seconds and then gives up?
I implemented my own timeout by using with the class method below instead of calling loadProductWithParameters directly. It times out thanks to a dispatch_after and __block variable.
+ (void)loadProductViewControllerWithTimeout:(NSTimeInterval)timeout
storeKitViewController:(SKStoreProductViewController *)storeKitViewController
parameters:(NSDictionary *)parameters
completionHandler:(void (^)(BOOL result, NSError *error))completionHandler {
__block BOOL hasReturnedOrTimedOut = NO;
[storeKitViewController loadProductWithParameters:parameters completionBlock:^(BOOL result, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!hasReturnedOrTimedOut) {
hasReturnedOrTimedOut = YES;
if (completionHandler) completionHandler(result, error);
}
});
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!hasReturnedOrTimedOut) {
hasReturnedOrTimedOut = YES;
if (completionHandler) completionHandler(NO, nil); // Or add your own error instead of |nil|.
}
});
}
My latest app update got rejected by Apple because loadProductWithParameters never called its completionBlock and stopped my users from buying songs on iTunes... Hope this helps.
I have acomplished it like so:
__block BOOL timeoutOrFinish = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(30 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if(!timeoutOrFinish) {
timeoutOrFinish = YES;
[self dismissAndShowError];
}
});
[storeViewController loadProductWithParameters:parameters completionBlock:^(BOOL result, NSError * _Nullable error) {
if(timeoutOrFinish) {
return;
}
timeoutOrFinish = YES;
//[[NetworkManager sharedManager] showNetworkActivityIndicator:NO];
if(error) {
[self dismissAndShowError];
}
}];
[self.view.window.rootViewController presentViewController:storeViewController animated:YES completion:nil];
where dismissAndShowError method runs dismissViewControllerAnimated and shows alert with an error.
Basically, you have a separate timer (30 seconds in my case) that switches a flag. After that time, if store has still not been loaded, I close it and display an error. Otherwise, completion is called (on cancel, finish and error) and handles all actions according to the status.

How to write a proper singleton BOOL function

I am using ASIHTTPRequest to check if user logged in, and try to return a boolean value when login successfull
Problem: When I request the boolean it always returns 0 then few seconds later ASIHTTPRequest finishes its request and update the boolean value.
What I want: get the boolean value after all requests finishes. I think proper way would be write a boolean function and retrieve the result of asihhtp requests?
in singleton:
#interface CloudConnection : NSObject
{
BOOL isUserLoggedIN;
}
#property BOOL isUserLoggedIN;
+ (CloudConnection *)sharedInstance;
#end
+ (CloudConnection *)sharedInstance
{
static CloudConnection *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[CloudConnection alloc] init];
// Do any other initialisation stuff here
});
return sharedInstance;
}
- (id)init {
if (self = [super init]) {
//send login request
[self sendLoginRequest];
}
return self;
}
-(void) sendLoginRequest{ .....}
- (void)requestFinished:(ASIHTTPRequest *)request
{ else if (request.responseStatusCode == 202) {
//parse json data
NSLog(#"Login Succesfull");
_isUserLoggedIN=YES;
}
}
- (void)requestFailed:(ASIHTTPRequest *)request{}
In a VC:
CloudConnection *sharedInstance=[CloudConnection sharedInstance];
NSLog(#"is logged in init %hhd",sharedInstance.isUserLoggedIN);
[self performSelector:#selector( checkLoginAfterFiveSeconds) withObject:nil afterDelay:5.0];
-(void) checkLoginAfterFiveSeconds
{
CloudConnection *sharedInstance=[CloudConnection sharedInstance];
NSLog(#"is logged in init %hhd",sharedInstance.isUserLoggedIN);
}
NSLOG:
is logged in init 0
Login Succesfull`
is logged in init 1 //after 5 secs
well if you do what you propose, its gonna block the calling thread. and you never want a thread waiting for network traffic especially not the main / ui thread
make it a void function and make it call a completionHandler or ... send a NSNotification once the result can be calculated directly! :)
Yes you are right :)
In your request completion block call this method:
[self loginResult:result];
-(void)loginResult:(BOOL)result
{
if(result == TRUE)
{
NSLog(#"Login successfully now call any method or do what ever you want");
}
else
{
NSLog(#"Login unsuccessfull");
}
}

Resources