Nested completion block not being called - ios

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);
}
}];
});
}

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.

send a second request after the first is completed

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)

ios recursive call with block leaks memory

I am calling a function recursively from completion block which increases the memory foot print, this most probably is leading to a block retain cycle,following is the code:
- (void)tick {
if (counter > counterLimit) {
[self finish];
return;
}
counter++;
context = [[Context alloc] init];
//this is executed in another thread
[self.executer computeWithContext:(Context*)context completion:^(NSDictionary *dictionary, Context *context_)
{
[self handleResponse];
[self tick];
}];
}
self is owning executer, executer is owning the block, the block captures self strongly (same effect as owning). you have a retain cycle.
create a weak reference for self and use that in the block exclusively.
- (void)tick {
// ...
__weak typeof(self) weakSelf = self;
[self.executer computeWithContext:(Context*)context completion:^(NSDictionary *dictionary, Context *context_)
{
typeof(weakSelf) strongSelf = weakSelf;
if(strongSelf) { // ensure the receiver is still there to receive
[strongSelf handleResponse];
[strongSelf tick];
}
}];
}

Return value from inside block (Objective-C)

I've been trying to get a value from inside a block for a few hours now, I can't understand how to use the handlers on completion and literally everything.
Here's my code:
+ (void)downloadUserID:(void(^)(NSString *result))handler {
//Now redirect to assignments page
__block NSMutableString *returnString = [[NSMutableString alloc] init]; //'__block' so that it has a direct connection to both scopes, in the method AND in the block
NSURL *homeURL = [NSURL URLWithString:#"https://mistar.oakland.k12.mi.us/novi/StudentPortal/Home/PortalMainPage"];
NSMutableURLRequest *requestHome = [[NSMutableURLRequest alloc] initWithURL:homeURL];
[requestHome setHTTPMethod:#"GET"]; // this looks like GET request, not POST
[NSURLConnection sendAsynchronousRequest:requestHome queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *homeResponse, NSData *homeData, NSError *homeError) {
// do whatever with the data...and errors
if ([homeData length] > 0 && homeError == nil) {
NSError *parseError;
NSDictionary *responseJSON = [NSJSONSerialization JSONObjectWithData:homeData options:0 error:&parseError];
if (responseJSON) {
// the response was JSON and we successfully decoded it
//NSLog(#"Response was = %#", responseJSON);
} else {
// the response was not JSON, so let's see what it was so we can diagnose the issue
returnString = (#"Response was not JSON (from home), it was = %#", [[NSMutableString alloc] initWithData:homeData encoding:NSUTF8StringEncoding]);
//NSLog(returnString);
}
}
else {
//NSLog(#"error: %#", homeError);
}
}];
//NSLog(#"myResult: %#", [[NSString alloc] initWithData:myResult encoding:NSUTF8StringEncoding]);
handler(returnString);
}
- (void)getUserID {
[TClient downloadUserID:^(NSString *getIt){
NSLog([NSString stringWithFormat:#"From get userID %#", getIt]);
}];
}
So I'm trying to NSLog the returnString from the downloadUserID method.
I first tried returning, then I realized you can't do a return from inside a block. So now I've been trying to do it with the :(void(^)(NSString *result))handler to try and access it from another class method.
So I'm calling downloadUserID from the getUserID method, and trying to log the returnString string. It just keeps going to nil. It just prints From get userID and nothing else.
How do I access the returnString that's inside the block of the downloadUserID method?
The problem is not the block itself, the problem is realizing that the block is executed asynchronously.
In your code, at the time you call handler(returnString); the block is probably still executing on another thread, so there's no way you can catch the value at this point.
Probably what you want to do is move that line inside the block (probably at the end, before the closing braces).
You can do this if you write such a wrapper.
In this situation, you need a while loop that will wait for a response from the block.
Method which shoud return value of enum
- (RXCM_TroubleTypes) logic_getEnumValueOfCurrentCacheProblem
{
RXCM_TroubleTypes result = RXCM_HaveNotTrouble;
NetworkStatus statusConnection = [self network_typeOfInternetConnection];
RXCM_TypesOfInternetConnection convertedNetStatus = [RXCM convertNetworkStatusTo_TypeOfInternetConnection:statusConnection];
BOOL isAllowed = [self someMethodWith:convertedNetStatus];
if (isAllowed){
return RXCM_HaveNotTrouble;
}else {
return RXCM_Trouble_NotSuitableTypeOfInternetConnection;
}
return result;
}
Method which calls delegate's method with block.
And waits answer from it.
Here I use while loop. Just check every 0.5sec answer from block
- (BOOL) isUserPermissioned:(RXCM_TypesOfInternetConnection)newType
{
__block BOOL isReceivedValueFromBlock = NO;
__block BOOL result = NO;
__block BOOL isCalledDelegateMethod = NO;
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_sync(aQueue,^{
while (!isReceivedValueFromBlock) {
NSLog(#"While");
if (!isCalledDelegateMethod){
[self.delegate rxcm_isAllowToContinueDownloadingOnNewTypeOfInternetConnection:newType
completion:^(BOOL isContinueWorkOnNewTypeOfConnection) {
result = isContinueWorkOnNewTypeOfConnection;
isReceivedValueFromBlock = YES;
}];
isCalledDelegateMethod = YES;
}
[NSThread sleepForTimeInterval:0.5];
}
});
return result;
}
Delegate's method in ViewController
- (void) rxcm_isAllowToContinueDownloadingOnNewTypeOfInternetConnection:(RXCM_TypesOfInternetConnection)newType
completion:(void(^)(BOOL isContinueWorkOnNewTypeOfConnection))completion
{
__weak ViewController* weak = self;
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Alert"
message:#"to continue download on the new type of connection"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *ok = [UIAlertAction actionWithTitle:#"YES" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completion(YES);
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:#"NO" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completion(NO);
}];
[alert addAction:cancel];
[alert addAction:ok];
[weak presentViewController:alert animated:YES completion:nil];
});
}

Spotify EXC_BAD_EXE while second time tap on Login Button after dismiss LoginViewController

Hi i Intigrate SpotifyLib CocoaLibSpotify iOS Library 17-20-26-630 into my Project. I open its SPLoginViewController using Bellow Method:-
-(void)OpenSpotify
{
NSError *error = nil;
[SPSession initializeSharedSessionWithApplicationKey:[NSData dataWithBytes:&g_appkey length:g_appkey_size]
userAgent:#"com.mycomp.spotify"
loadingPolicy:SPAsyncLoadingImmediate
error:&error];
if (error != nil) {
NSLog(#"CocoaLibSpotify init failed: %#", error);
abort();
}
[[SPSession sharedSession] setDelegate:self];
[self performSelector:#selector(showLogin) withObject:nil afterDelay:0.0];
}
-(void)showLogin
{
SPLoginViewController *controller = [SPLoginViewController loginControllerForSession:[SPSession sharedSession]];
controller.allowsCancel = YES;
//controller.view.frame=;
[self presentViewController:controller animated:YES completion:nil];
}
At First time that Appear Spotify Login Screen. After that I tap On Cancel Button, and Try to open again login screen then i got crash EXC_BAD_EXE at this line. sp_error createErrorCode = sp_session_create(&config, &_session);
UPDATE
I Found exet where is got BAD_EXC
in this method
+(void)dispatchToLibSpotifyThread:(dispatch_block_t)block waitUntilDone:(BOOL)wait {
NSLock *waitingLock = nil;
if (wait) waitingLock = [NSLock new];
// Make sure we only queue one thing at a time, and only
// when the runloop is ready for it.
[runloopReadyLock lockWhenCondition:1];
CFRunLoopPerformBlock(libspotify_runloop, kCFRunLoopDefaultMode, ^() {
[waitingLock lock];
if (block) { #autoreleasepool { block(); } }
[waitingLock unlock];
});
if (CFRunLoopIsWaiting(libspotify_runloop)) {
CFRunLoopSourceSignal(libspotify_runloop_source);
CFRunLoopWakeUp(libspotify_runloop);
}
[runloopReadyLock unlock]; // at hear when my debug poin reach after pass this i got bad_exc
if (wait) {
[waitingLock lock];
[waitingLock unlock];
}
}
after doing lots of search i got Solution i check that whether the session already exists then i put if condition like:-
-(void)OpenSpotify
{
SPSession *session = [SPSession sharedSession];
if (!session) {
NSError *error = nil;
[SPSession initializeSharedSessionWithApplicationKey:[NSData dataWithBytes:&g_appkey length:g_appkey_size]
userAgent:#"com.mycomp.spotify"
loadingPolicy:SPAsyncLoadingImmediate
error:&error];
if (error != nil) {
NSLog(#"CocoaLibSpotify init failed: %#", error);
abort();
}
[[SPSession sharedSession] setDelegate:self];
}
[self performSelector:#selector(showLogin) withObject:nil afterDelay:0.0];
}
-(void)showLogin
{
SPLoginViewController *controller = [SPLoginViewController loginControllerForSession:[SPSession sharedSession]];
controller.allowsCancel = YES;
[self presentViewController:controller animated:YES completion:nil];
}
Now no crash and working fine.

Resources