How do I return variables captured from a block to the caller in Objective-C - objective-c

I'm having trouble returning an asynchronous response/error pair captured in a block back to caller. Here is the code:
- (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
{
__block NSData* b_data = nil;
__block NSURLResponse* b_response = nil;
__block NSError* b_error = nil;
dispatch_semaphore_t wait = dispatch_semaphore_create(0);
[self sendRequest:request completion:^(NSHTTPURLResponse* c_response, NSData* c_data, NSError* c_error) {
b_data = c_data;
b_response = c_response;
b_error = c_error;
dispatch_semaphore_signal(wait);
}];
dispatch_semaphore_wait(wait, DISPATCH_TIME_FOREVER);
response = &b_response; // ERROR: Assigning NSURLResponse *__strong * to NSURLResponse *__autoreleasing * changes retain/release properties of pointer
error = &b_error; // ERROR: Assigning NSError *__strong * to NSError *__autoreleasing * changes retain/release properties of pointer
return b_data;
}
Basically, I'm converting an async request to a synchronous one. I need to return the response/error pair back to the caller through a pointer-to-a-pointer. However, I'm running into some problems with ARC and blocks.
I would like to copy the response/error out of the block and return it to the caller without changing the method signature (there is a slew of legacy code that calls this method - I'm trying to gingerly replace NSURLConnection with NSURLSession).
Is there a nice way I can extract the results and return them to the caller?

Your two errors have nothing to do with the block. Those lines should be:
if (response) {
*response = b_response;
}
if (error) {
*error = b_error;
}
The if checks are required incase the caller passes nil for the parameter. You will get a crash trying to dereference a nil pointer.
It's also unnecessary to copy the response/error pair to local __block variables. You can write them directly to the parameter after checking for nil.

Related

How to get data out of a block?

I'm trying to make an equivalent to the .NET recognize() call, which is synchronous, for ios in objective-c. I found code to recognize speech but the string that was recognized is only inside a block.
I've tried making the block not a block (it seems to be part of the API that it be a block), making __block variables and returning their values, also out parameters in the caller/declarer of the block; finally I wrote a file while in the block and read the file outside. It still didn't work like I want because of being asynchronous although I at least got some data out. I also tried writing to a global variable from inside the block and reading it outside.
I'm using code from here: How to implement speech-to-text via Speech framework, which is (before I mangled it):
/*!
* #brief Starts listening and recognizing user input through the
* phone's microphone
*/
- (void)startListening {
// Initialize the AVAudioEngine
audioEngine = [[AVAudioEngine alloc] init];
// Make sure there's not a recognition task already running
if (recognitionTask) {
[recognitionTask cancel];
recognitionTask = nil;
}
// Starts an AVAudio Session
NSError *error;
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryRecord error:&error];
[audioSession setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error];
// Starts a recognition process, in the block it logs the input or stops the audio
// process if there's an error.
recognitionRequest = [[SFSpeechAudioBufferRecognitionRequest alloc] init];
AVAudioInputNode *inputNode = audioEngine.inputNode;
recognitionRequest.shouldReportPartialResults = YES;
recognitionTask = [speechRecognizer recognitionTaskWithRequest:recognitionRequest resultHandler:^(SFSpeechRecognitionResult * _Nullable result, NSError * _Nullable error) {
BOOL isFinal = NO;
if (result) {
// Whatever you say in the microphone after pressing the button should be being logged
// in the console.
NSLog(#"RESULT:%#",result.bestTranscription.formattedString);
isFinal = !result.isFinal;
}
if (error) {
[audioEngine stop];
[inputNode removeTapOnBus:0];
recognitionRequest = nil;
recognitionTask = nil;
}
}];
// Sets the recording format
AVAudioFormat *recordingFormat = [inputNode outputFormatForBus:0];
[inputNode installTapOnBus:0 bufferSize:1024 format:recordingFormat block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
[recognitionRequest appendAudioPCMBuffer:buffer];
}];
// Starts the audio engine, i.e. it starts listening.
[audioEngine prepare];
[audioEngine startAndReturnError:&error];
NSLog(#"Say Something, I'm listening");
}
I want to call Listen(), (like startListening() above), have it block execution until done, and have it return the string that was said. But actually I would be thrilled just to get result.bestTranscription.formattedString somehow to the caller of startListening().
I'd recommend you to take another approach. In Objective-C having a function that blocks for a long period of time is an anti-pattern.
In this language there's no async/await, nor cooperative multitasking, so blocking for long-ish periods of time might lead to resource leaks and deadlocks. Moreover if done on the main thread (where the app UI runs), the app might be forcefully killed by the system due to being non-responsive.
You should use some asynchronous patterns such as delegates or callbacks.
You might also try using some promises library to linearize your code a bit, and make it look "sequential".
The easiest approach with callbacks would be to pass a completion block to your "recognize" function and call it with the result string when it finishes:
- (void)recognizeWithCompletion:(void (^)(NSString *resultString, NSError *error))completion {
...
recognitionTask = [speechRecognizer recognitionTaskWithRequest:recognitionRequest
resultHandler:^(SFSpeechRecognitionResult *result, NSError *error)
{
...
dispatch_async(dispatch_get_main_queue(), ^{
completion(result.bestTranscription.formattedString, error);
});
...
}];
...
}
Note that the 2nd parameter (NSError) - is an error in case the caller wants to react on that too.
Caller side of this:
// client side - add this to your UI code somewhere
__weak typeof(self) weakSelf = self;
[self recognizeWithCompletion:^(NSString *resultString, NSError *error) {
if (!error) {
[weakSelf processCommand:resultString];
}
}];
// separate method
- (void)processCommand:(NSString *command) {
// can do your processing here based on the text
...
}

Objective-C block doesn't return and call it self in the block?

I'm learning to develop iOS applications and now I'm reading some Objective-C source code.
This is a method to get user profile.
+ (void)getProfile:(void (^)(NSString *message))completion {
NSDictionary *dic = #{#"module":#"profile"};
[[self defaultManager] POST:KBaseUrl parameters:dic success:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([self jsonOKForResponseObject:responseObject] && [self checkLogin:responseObject]) {
[ProfileManager sharedInstance].rank = responseObject[#"Variables"][#"space"][#"group"][#"grouptitle"];
[ProfileManager sharedInstance].credit = responseObject[#"Variables"][#"space"][#"credits"];
[ProfileManager sharedInstance].gender = responseObject[#"Variables"][#"space"][#"gender"];
completion(nil);
} else {
completion(#"fail");
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
completion(#"fail");
}];
}
My question is about the completion block.
I suppose that the completion block returns void and receives an NSString parameter.
In the block, what does completion(nil) mean?
Does that mean the block completion calls it self and send nil as parameter?
Doesn't that conflict with the parameter's type NSString*?
I'm not quite familiar with block in ObjC. Can anyone give a hint?
Yes you are right. It calls itself and sends nil as a parameter and it doesn't conflict with the NSString parameter. You are just passing nil to the NSString param.
You can call the above method like:
[YourClass getProfile:^(NSString *message) {
//message will be nill if you pass completion(nil);
}];
So when you pass the nill in the completion block, the message in the above method call will be nil!
The completion block is to notify you that your method call is complete, and at this point you can let that method pass certain paramteres , and if we consider your method:
+ (void)getProfile:(void (^)(NSString *message))completion {
NSDictionary *dic = #{#"module":#"profile"};
[[self defaultManager] POST:KBaseUrl parameters:dic success:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([self jsonOKForResponseObject:responseObject] && [self checkLogin:responseObject]) {
.....
completion(#"hey sucess");
} else {
completion(#"if loop failed");
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
completion(#"some error occured");
}];
}
and when you call the method getProfile:
[YourClass getProfile:^(NSString *message) {
//Execution will reach here when the method call for getPRofile is complete and you have a result which you just sent through the completion block as a parameter which is a string and is waiting for you to process.
//you can do more from here
if([message isEqualToString:#"hey success"]){
//do something
}
if([message isEqualToString:#"if loop failed"]){
//do something
}
if([message isEqualToString:#"some error occured"]){
//do something
}
}];
As per #rmaddy comment, iis always a good practice to use BOOL to indicate the status success or fail rather than depending on a string as string can get localized/changed. We shold use the string to get more description of the error.
So your block should be:
+ (void)getProfile:(void (^)(BOOL status,NSString *message))completion {
.....
completion(YES,#"hey success");
}
and you can call it like":
[YourClass getProfile:^(BOOL status, NSString *message) {
if(status){
//get the success message
}else{
//get the fail message
}
}];
Blocks had a lot of type like :-
As a local variable:
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
As a property:
#property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);
As a method parameter:
- (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;
As an argument to a method call:
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];
As a typedef:
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};
read more about it from here
Completion block does not return anything. It is just a piece of code to be executed, thats all. Though, you can give it some input where you call it so you can use the result elsewhere.
NSString *message is the input for your block so when you call your function getProfile as:
[MyClass getProfile:^(NSString *message) {
// write code to be executed when getProfile function finishes its job and sends message here.
}];
[MyClass getProfile:nil];
When used like this you're preferring not to do anything when getProfile finishes its job.
You are probably mixing your network manager's function's completion block with the one you wrote.

Objective C Incompatible block pointer types sending

I'm using parse 1.7.4 , this is y code :
+(NSArray *)getCategorieFromParse{
PFQuery *categoriesQuery = [PFQuery queryWithClassName:#"Categorie"];
[categoriesQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error){
if (!error)
return objects;
else
return [[NSArray alloc] init];
}];
}
but this is generate this error :
Incompatible block pointer types sending 'NSArray *(^)(NSArray
*__strong, NSError *__strong)' to parameter of type 'PFArrayResultBlock __nullable' (aka 'void (^)(NSArray * __nullable
__strong, NSError * __nullable __strong)')
At the return line
Your block wasn't declared with a return type, and it returns an NSArray*, there it is a block returning an NSArray*. The method you are calling expects a block returning void. Obviously your block is not acceptable.
I suspect there is some deep misunderstanding going on what this block is supposed to do. Your method getCategorieFromParse cannot return an array. It's sending an asynchronous request, and your callback block will be called long after getCategorieFromParse returns. The callback block shouldn't try to return anything; it's job is to process the array that it was given.
You cannot return values from within a code block. You should rather use a delegate (just an example I found on google) instead.
You make asynchronous calls. You can't return array synchronously.
Solution: make your method also asynchronous:
+(void) getCategorieFromParse:(void (^)(NSArray*))completion
{
PFQuery *categoriesQuery = [PFQuery queryWithClassName:#"Categorie"];
[categoriesQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error){
if (!error)
completion(objects);
else
completion([[NSArray alloc] init]);
}];
}

Is my understanding of completionHandler blocks correct?

Ive read quite bit about blocks by now, Apple's Guide, Cocoabuilder, 3 articles on SO and ive used examples in my code that I basically got from online tutorials. Im still trying to understand one specific question. So I decided to make an app with nothing more than a completionHandler example to understand better. This is what I came up with:
ViewController
- (void)viewDidLoad
[SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) {
self.usersArray = [NSMutableArray array];
for (NSDictionary *userDict in users) {
[self.usersArray addObject:[userDict objectForKey:#"username"]];
}
//WHILE TESTING postarray method, comment this out...
//[self getPoints];
[self.tableView reloadData];
}];
}
SantiappsHelper.h/m
typedef void (^Handler)(NSArray *users);
+(void)fetchUsersWithCompletionHandler:(Handler)handler {
NSString *urlString = [NSString stringWithFormat:#"http://www.myserver.com/myapp/getusers.php"];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
[request setHTTPMethod: #"GET"];
__block NSArray *usersArray = [[NSArray alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//dispatch_async(dispatch_get_main_queue(), ^{
// Peform the request
NSURLResponse *response;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error) {
// Deal with your error
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
return;
}
NSLog(#"Error %#", error);
return;
}
NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];
if (handler){
//dispatch_sync WAITS for the block to complete before returning the value
//otherwise, the array is returned but gets zeroed out
dispatch_sync(dispatch_get_main_queue(), ^{
handler(usersArray);
});
}
});
}
Here is what I understand...
I call fetchUsersWithCompletionHandler from my VC & pass it this completion block. That block takes an NSArray users parameter & returns void.
Meanwhile in the SantiappsHelper Class we have a variable called handler of type ^block which it receives from VC.
The fetchUsersWithCompletionHandler method runs, taking that CompletionBlock parameter, which itself takes the NSArray users parameter? a little confusing.
The webfetch is dispatch_async so it wont block the main thread. So execution on the main thread continues. That new thread executes the fetch synchronously that new thread will stall until the response is returned. Once that new thread receives the response, it fills in the NSHTTPURLResponse. Once it returns, it fills in usersArray with the NSJSONSerialized data.
Finally it reaches the if test and it checks for the existence of the PARAMETER handler that was passed in? A little confusing...the parameter passed in was the completionBlock. Wouldnt that handler parameter always and obviously exist since it was passed in?
Once the handler !NULL then execution is returned to the main thread passing back the NSArray users expected by the block back in the VC.
But if I change the second dispatch to async, the usersArray is properly populated but once handler(usersArray) is sent back to the main thread, its empty or nil! Why?
Correct. The best way to say/think about this is to say that you are invoking a method called fetchUsersWithCompletionHandler:. This method will go away and do some work and at some point in the future it may execute the code you have declared in the block literal and pass in an array of users.
The method takes an argument called handler of type void (^)(NSArray *users). This type represents a block of code that when invoked should receive and array and return no result.
The fetchUsersWithCompletionHandler: does some work and at some point may invoke the block passed in with an array of users as the blocks argument.
Correct
The if (handler) { checks to see if the handler arguments is not nil. In most cases this would be the case especially if you always invoke the fetchUsersWithCompletionHandler: with a block literal, but you could always invoke the method with [self fetchUsersWithCompletionHandler:nil]; or invoked it passing along a variable from somewhere else as the completion, which may be nil. If you try to dereference nil to invoke it then you will crash.
Execution is not "passed back" to the main thread, you are simply enqueueing a block of work to be performed on the main thread. You are doing this with dispatch_sync call which will block this background thread until the block completes - this isn't really required.
The array being nil could be a consequence of you declaring the usersArray with __block storage. This is not required as you are not modifying what usersArray is pointing to at any point you are simply calling methods on it.

Using NSURLConnection Asynchronous URL Request with Block Parameter

I'm attempting to appropriately use NSURLConnection's sendAsynchronousRequest method but I'm having trouble with the block parameter. I built the block above the call and am using __block variables to attempt to retrieve the data collected within it. I've confirmed that the data within the block is correct so the block is working just fine, leaving the retrieval of the block data the problem. My assignments and return statement at the end of the method are not being handed the correct values and I assume it is because the the block has yet to finish executing before the assignment is taking place. How can I "wait" until the block has finished executing before performing the assignments without interrupting the main thread?
- (NSData *) sendAsynchronousURLRequest: (NSMutableURLRequest *)request withResponse: (NSURLResponse *)response andError:(NSError *)error
{
// Prepare the block handler.
__block NSURLResponse *retrievedBlockResponse;
__block NSError *retrievedBlockError;
__block NSData *retrivedBlockData;
void(^requestHandler)(NSURLResponse*, NSData*, NSError*) =
^(NSURLResponse *blockResponse, NSData *blockData, NSError *blockError)
{
retrievedBlockResponse = blockResponse;
retrivedBlockData = blockData;
retrievedBlockError = blockError;
// NOTE: Correct data is here...
NSLog(#"Data Inside Block:\n%#", [NSMutableDictionary DictionaryFromJSONData:blockData errorReport:nil]);
};
// Send the asynchronous request (operationQueue initialized elsewhere in class).
[NSURLConnection sendAsynchronousRequest:request queue:operationQueue completionHandler:requestHandler];
// NOTE: Reference to data cannot be established here!
// Establish references to block variables and return NSData object.
response = retrievedBlockResponse;
error = retrievedBlockError;
return retrivedBlockData;
}