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];
});
}
Related
The app I'm working on is using a function that is working fine but blocks the main thread. I am attempting to add a loading spinner using SVProgressHUD and that requires I call my function asynchronously in order to display the spinner. As soon as I call the function asynchronously however the app crashes with EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0 The only change I have made to the function is to invoke the popViewControllerAnimated lines on the main thread. Why is running this code on a new thread causing it to crash and how can I fix it?
Calling code:
-(void) _doSaveDataPoint {
[SVProgressHUD show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self _saveDataPoint];
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
});
});
}
_saveDataPoint function. popViewController called on main thread near the end of this code:
-(void) _saveDataPoint {
NSString *errorMsg = nil;
if ([[myLegend type] isEqualToString:#"PIN"]) {
if ([myNodes count]==0) {
errorMsg = #"Please make sure you have added one point on to the map to continue.";
}
}
else if ([[myLegend type] isEqualToString:#"POLYGON"]) {
if ([myNodes count]<3) {
errorMsg = #"Please make sure you have at least 3 points set before continuing.";
}
}
else {
if ([myNodes count]<2) {
errorMsg = #"Please make sure you have at least 2 points set before continuing.";
}
}
if (errorMsg !=nil) {
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:#"Not enough points"
message:errorMsg
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
// Just dismiss
}];
[alertController addAction:okAction];
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:alertController animated:YES completion:nil];
});
return;
}
ClientLegendDataPointBounds *bounds = [[ClientLegendDataPointBounds alloc] init];
int count = 0;
GeoPoint *first = nil;
NSMutableDictionary *attr = [[NSMutableDictionary alloc] init];
for (_EditAnnotation *anno in myNodes) {
GeoPoint *point = [[GeoPoint alloc] initWithLatitude:[anno coordinate].latitude andLongitude:[anno coordinate].longitude];
[bounds expand:point];
if (count==0) {
first = point;
count++;
continue;
}
NSString *xKey = [NSString stringWithFormat:#"x%d",count-1];
NSNumber *xCoord = [NSNumber numberWithDouble:[point latitude ]];
NSString *yKey = [NSString stringWithFormat:#"y%d",count-1];
NSNumber *yCoord = [NSNumber numberWithDouble:[point longitude]];
[attr setObject:xCoord forKey:xKey];
[attr setObject:yCoord forKey:yKey];
count++;
}
if (count>0) {
NSString *pointCount = [NSString stringWithFormat:#"%d", count-1];
[attr setObject:pointCount forKey:#"pointCount"];
}
[self _setBarThemeDefault];
if (myDataPoint==nil) {
myDataPoint = [myLegend addDataPoint:[NSNumber numberWithLongLong:[DateTime currentTimeInMillis]] title:#"" description:#"" latitude:[first latitude] longitude:[first longitude] attributes:attr type:[myLegend type] bounds:bounds];
dispatch_async(dispatch_get_main_queue(), ^{
[[self navigationController] popViewControllerAnimated:NO];
});
[myHandler newItemCreated:myDataPoint];
} else {
[myDataPoint setAttributes:attr];
[myDataPoint setBounds:bounds];
[myDataPoint setLatitude:[first latitude]];
[myDataPoint setLongitude:[first longitude]];
[myDataPoint setModified:[NSNumber numberWithLongLong:[DateTime currentTimeInMillis]]];
[myDataPoint update];
dispatch_async(dispatch_get_main_queue(), ^{
[[self navigationController] popViewControllerAnimated:YES];
});
[myHandler itemUpdated:myDataPoint];
}
[self _finishSurveyLog:[SurveyLogItem ACT_SAVE_SPATIAL_CONST]];
[self _saveUserLocation];
}
I don't know exactly the plugin but could it be that the plugin itselfs dispatches the ui stuff to the main queue? So you don't have to dispatch the call to the main queue by yourself. Take a look at the source code:
SVProgressHUD.m
After clicking post to the share dialog, the Host App(e.g. Safari) hangs up if arrSites variable is currently not empty. I can only store 1 object inside my arrSites variable. How can I addObject to my NSMutableArray variable?
Below is my implemented code and it generates an error in [arrSites addObject:dictSite] line.
- (void)didSelectPost
{
inputItem = self.extensionContext.inputItems.firstObject;
NSItemProvider *urlItemProvider = [[inputItem.userInfo valueForKey:NSExtensionItemAttachmentsKey] objectAtIndex:0];
if ([urlItemProvider hasItemConformingToTypeIdentifier:(__bridge NSString *)kUTTypeURL])
{
NSLog(#"++++++++++ Attachment is a URL");
[urlItemProvider loadItemForTypeIdentifier:(__bridge NSString *)kUTTypeURL options:nil completionHandler:^(NSURL *url, NSError *error)
{
if (error)
{
NSLog(#"Error occured");
}
else
{
NSMutableArray *arrSites;
if ([sharedUserDefaults valueForKey:#"SharedExtension"]){
arrSites = [sharedUserDefaults objectForKey:#"SharedExtension"];
}else{
arrSites = [[NSMutableArray alloc] init];
}
NSDictionary *dictSite = [NSDictionary dictionaryWithObjectsAndKeys:self.contentText, #"Text", url.absoluteString, #"URL",nil];
[arrSites addObject:dictSite];
[sharedUserDefaults setObject:arrSites forKey:#"SharedExtension"];
[sharedUserDefaults synchronize];
UIAlertController * alert= [UIAlertController
alertControllerWithTitle:#"Success"
message:#"V7 Posted Successfully."
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* ok = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[UIView animateWithDuration:0.20 animations:^
{
self.view.transform = CGAffineTransformMakeTranslation(0, self.view.frame.size.height);
}
completion:^(BOOL finished)
{
[self.extensionContext completeRequestReturningItems:nil completionHandler:nil];
}];
}];
[alert addAction:ok];
[self presentViewController:alert animated:YES completion:nil];
}
}];
}
}
Without memory allocation you can't add the object to array, use like
// allocate the memory of array in before
NSMutableArray *arrSites = [[NSMutableArray alloc] init];
if ([sharedUserDefaults valueForKey:#"SharedExtension"]){
[arrSites addObjectsFromArray:[sharedUserDefaults objectForKey:#"SharedExtension"]];
}
[arrSites addObject:dictSite];
Most likely the source of the problem is that
arrSites = [sharedUserDefaults objectForKey:#"SharedExtension"];
creates immutable object (NSArray instead of NSMutableArray). You can fix this issue using
arrSites = [[sharedUserDefaults objectForKey:#"SharedExtension"] mutableCopy];
instead.
Again, question about GCD. I don't know why they were invented as they don't work properly or I misunderstood something.
I've got some controller for adding new Coupon. It is responsible to saving details into Core Data. Then ImagesCollection is used to pick up some imgs and used to convert UIImage into NSData.
It looks something like that:
- (IBAction)saveButtonClicked:(id)sender {
__weak typeof(self) weakSelf = self;
__block NSSet *imgs = nil;
if (!imagesCollection)
imgs = [self.coupon couponImages];
[imagesCollection saveImagesWithCompletion:^(NSSet *images, NSError *error) {
if (error) {
[[[UIAlertView alloc] initWithTitle:error.localizedDescription
message:error.localizedFailureReason
delegate:nil
cancelButtonTitle:NSLocalizedString(#"OK", nil) otherButtonTitles:nil, nil] show];
} else {
imgs = [images copy];
}
}];
------- PhotosCollection --------
#interface...
typedef void (^ImagesCompletionBlock)(NSSet *images, NSError *error);
-(void)saveImagesWithCompletion:(ImagesCompletionBlock)completion;
#implementation...
ImagesCompletionBlock _block;
-(void)saveImagesWithCompletion:(void (^)(NSSet *images, NSError *error))success {
_block = [success copy];
NSError *error = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSMutableSet *processedImages = [NSMutableSet new];
for (UIImage *image in images) {
[processedImages addObject:[NSData dataWithData:UIImagePNGRepresentation(image)]];
}
dispatch_async(dispatch_get_main_queue(), ^{
if (_block) {
_block([processedImages copy],error);
}
});
});
}
How it doesn't wait for completion handler to be fired??
Thanks for any help,
Frustrated - Adrian.
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);
}
}];
});
}
I have a PHP RESTful server that I connect to to invoke methods based on the URL path called from my objective-c program. I use the ASIHTTPRequest and SBJson.
Everything works well but I'm doing requests in different controllers and now duplicated methods are in all those controllers. What is the best way to abstract that code (requests) in a single class, so I can easily re-use the code, so I can keep the controllers more simple.
Please save me from what I feel to be a redundant and repetitive coding sequence.
I have tried to make a singleton class, but each view controller requires different request connect points, and most view controllers require specific actions to be called when the succeeded method is called and Im not sure how to handle it all.
Here is what my code template currently looks like in the Objective-c program:
//*1) Somewhere at the top of my .m file I have this*
//These are the suffixes to the URL path that I connect to at my server,
//depending on the action required
NSString *const RequestCreateCustomer = #"Create/Customer";
NSString *const RequestUpdateCustomer = #"Update/Customer";
NSString *const RequestDeleteCustomer = #"Delete/Customer";
//*2) Then I have my connection invocation code*
//Method invocation, all of them look something like this
-navigationButton{
...
[self retrieveWithRequestStringType:RequestUpdateCustomer];
}
-(void)retrieveWithRequestStringType:(NSString*)typeOfRequest{
NSLog(#"Retrieve %# method called", typeOfRequest);
NSString *urlString = [NSString stringWithFormat:#"%#/Secure/CB/%#", #"http://www.defaultStapleURLToMyServer.com/CB", typeOfRequest];
NSString *encodedUrlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [[NSURL alloc] initWithString:encodedUrlString];
serverRequest = nil;
serverRequest = [ASIFormDataRequest requestWithURL:url];
[serverRequest addRequestHeader:#"Content-Type" value:#"application/json"];
[serverRequest addRequestHeader:#"Request-Method" value:#"POST"];
//Normally at this point depending on the request type, I prepare some data that needs to be sent along with the request
NSMutableDictionary *completeDataArray = [[NSMutableDictionary alloc] init];
if([typeOfRequest isEqualToString:RequestCreateCustomer]){
[serverRequest setUserInfo:[NSDictionary dictionaryWithObject:RequestCreateCustomer forKey:#"RequestType"]];
if( ! [self validateAndPrepareAllData:&completeDataArray]){
return;
}
}
else if([typeOfRequest isEqualToString:RequestUpdateCustomer]){
[serverRequest setUserInfo:[NSDictionary dictionaryWithObject:RequestUpdateCustomer forKey:#"RequestType"]];
if( ! [self validateAndPrepareAllData:&completeDataArray]){
return;
}
}
else if([typeOfRequest isEqualToString:RequestDeleteCustomer]){
[serverRequest setUserInfo:[NSDictionary dictionaryWithObject:RequestDeleteCustomer forKey:#"RequestType"]];
if( ! [self validateAndPrepareCustomerIdData:&completeDataArray]){
return;
}
}
NSString *jsonString = [completeDataArray JSONRepresentation];
[serverRequest appendPostData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]];
[serverRequest setDelegate:self];
[serverRequest setDidFinishSelector:#selector(requestSucceeded:)];
[serverRequest setDidFailSelector:#selector(requestFailed:)];
[serverRequest startAsynchronous];
}
//*3) And heres the connection did succeed, and connection did fail methods*
-(void)requestSucceeded:(ASIHTTPRequest*)request{
NSInteger statusCode = [[[request responseHeaders] objectForKey:#"StatusCode"] intValue];
NSLog(#"StatusCode: %#", [[request responseHeaders] objectForKey:#"StatusCode"]);
NSString *myString = [[NSString alloc] initWithData:[request responseData] encoding:NSUTF8StringEncoding];
NSDictionary *JSONDictionary = [myString JSONValue];
switch (statusCode) {
case 400:
case 401:
{
NSLog(#"display error message");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:[[request.responseString JSONValue] objectForKey:#"Message"] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
break;
}
case 200:
NSLog(#"status code = 200 so successful");
//Created customer request succeeded
if([[[request userInfo] objectForKey:#"RequestType"] isEqualToString:RequestCreateCustomer]){
[self.delegate addedCustomerVCWithCustomer:[[CustomerDataModel alloc] initWithJSONData:[JSONDictionary objectForKey:#"CustomerObject"]]];
[self cancelModalView];
}
//Edit customer request succeeded
else if([[[request userInfo] objectForKey:#"RequestType"] isEqualToString:RequestUpdateCustomer]){
[self.delegate editedCustomerVCWithCustomer:[[CustomerDataModel alloc] initWithJSONData:[JSONDictionary objectForKey:#"CustomerObject"]]];
[self cancelModalView];
}
//Delete customer request succeeded
else if([[[request userInfo] objectForKey:#"RequestType"] isEqualToString:RequestDeleteCustomer]){
[self.delegate deletedCustomer:customer];
[self cancelModalView];
}
break;
default:
[SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:#"went to none: %d or %#", (int)statusCode, [[request responseHeaders] objectForKey:#"StatusCode"]]];
NSLog(#"went to none: %d or %#", (int)statusCode, [[request responseHeaders] objectForKey:#"StatusCode"]);
break;
}
[SVProgressHUD dismiss];
}
-(void)requestFailed:(ASIHTTPRequest*)request{
if([[[request userInfo] objectForKey:#"RquestType"] isEqualToString:RequestCreateCustomer]){
NSLog(#"retrieving states failed so trying again");
[self addCustomerRequest];
}
else if(){
//... you get the point
}
}
Can someone help out with an alternative solution?
create a new class that has blocks/delegate (but prefers block) on it.. let's say Request.h and Request.m.. and make a public method with a block parameter.. example
this should be public methods
typedef void(^CompletionBlock)(id results);
typedef void(^ErrorBlock)(NSError *error);
- (void)setCompetionBlock:(CompletionBlock)block; and
- (void)setErrorBlock:(ErrorBlock)error;
and make a variable name.. this should be private variables
CompletionBlock completionBlock;
ErrorBlock errorBlock;
and declare this methods in your .m
- (void)setCompletionBlock:(CompletionBlock)aCompletionBlock {
completionBlock = [aCompletionBlock copy];
}
- (void)setErrorBlock:(ErrorBlock)aError {
errorBlock = [aError copy];
}
- (void)reportSuccess:(id)results {
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(results);
});
}
}
- (void)reportFailure:(NSError *)error {
if (errorBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
errorBlock(error);
});
}
}
and call [self reportSuccess:jsonSuccess]; under your success methods, in your code it is -(void)requestSucceeded:(ASIHTTPRequest*)request{ and [self reportFailure:NSerror*] under -(void)requestFailed:(ASIHTTPRequest*)request
and to call this class
Request *req = [Request alloc]init];
[req retrieveWithRequestStringType:#"sample"];
[req setCompletion:^(id result){
}];
[req setErrorBlock:(NSError *error) {
}];