When I want to get userID from Facebook, I need to wait the API "startWithCompletionHandler" to send me back the userID, then I continue my task. Just like the code listed below.
But I can not use dispatch_semaphore or dispatch_group to wait for the api to callback. Except for completionHandler, any other solution like "blockAndWait" in MagicalRecord that can help me solve question like this?
Also, the solution listed below isn't working. It seems blocked the main thread, so it'll keep logging "WAITING" and can not pop up the Facebook login view.
-(void)getAccountIDAndWait{
__block BOOL isFinish = NO;
[[FBRequest requestForMe] startWithCompletionHandler:
^(FBRequestConnection *connection, NSDictionary<FBGraphUser> *aUser, NSError *error) {
if (error) {
NSLog(#"bug in getAccountIDAndWait");
return;
}
self.accountID = [aUser objectForKey:#"id"];
isFinish = YES;
}];
while(isFinish==NO){
sleep(0.1);
NSLog(#"WAITING");
}
}
Expanding on what Chuck said I've slightly modified your code to allow the getAccountIdAndWait function to finish (and thus the main thread to idle) and when the completionHandler is called it will call the doTheNextThing function.
-(void)getAccountIDAndWait{
[[FBRequest requestForMe] startWithCompletionHandler:
^(FBRequestConnection *connection, NSDictionary<FBGraphUser> *aUser, NSError *error) {
if (error) {
NSLog(#"bug in getAccountIDAndWait");
return;
}
self.accountID = [aUser objectForKey:#"id"];
[self doTheNextThing];
}];
}
The approach you are using there is blocking. As you correctly observe, this is not really what you want. The design intent here is for you to put things that you want to happen after the request completes in the completion handler.
Related
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
...
}
[NSURLConnection sendAsynchronousRequest:mutURLRequest queue:opQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if(httpResponse.statusCode ==200)
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"MUITCheckinPostSucceeded" object:self userInfo:postDictionary];
}
}];
This is my NSURLConnection and I'm not sure how to check if it was successful. I tried a simple flag but that did not work because the boolean didn't retain the "YES" value outside of the NSURLConnection. This is a school assignment so don't post the correct code I'd just like to know the method I need to implement or how I can tackle this problem in a way I haven't tried yet. Thanks in advance.
Try something like this:
[NSURLConnection sendAsynchronousRequest: myURLRequest
queue: [NSOperationQueue mainQueue]
completionHandler: ^(NSURLResponse *urlResponse, NSData *responseData, NSError *requestError) {
// Check for Errors
if (requestError || !responseData) {
// jump back to the main thread to update the UI
dispatch_async(dispatch_get_main_queue(), ^{
[myLabel setText: #"Something went wrong..."];
});
} else {
// jump back to the main thread to update the UI
dispatch_async(dispatch_get_main_queue(), ^{
[myLabel setText: #"All going well..."];
});
}
}
];
You can update your class properties from the completion block. In this case, if flag was atomic, you can just update it. But if you're setting anything else (e.g. any object properties updated from the resulting data object), you might want to dispatch that back to the main queue to avoid synchronization issues:
self.flag = NO;
[NSURLConnection sendAsynchronousRequest:mutURLRequest queue:opQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSInteger statusCode = -1;
// to be safe, you should make sure `response` is `NSHTTPURLResponse`
if ([response isKindOfClass:[NSHTTPURLResponse class]])
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
statusCode = httpResponse.statusCode;
}
if (error)
{
// for diagnostic purposes only
NSLog(#"%s: sendAsynchronousRequest error: %#", __FUNCTION__, error);
}
if (error == nil && statusCode == 200)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.flag = YES;
// set any other class properties here
[[NSNotificationCenter defaultCenter] postNotificationName:#"MUITCheckinPostSucceeded" object:self userInfo:postDictionary];
}];
}
}];
I notice that you're posting a notification. If you have multiple view controllers or model objects listening for that notification, that's fine and a notification makes sense. But if this code was in the view controller and that controller is the only thing that cares about the results, you generally forego the notification and just initiate the update the UI right from the code that's dispatched back to the main queue in that completion block.
One final caveat. Any references to self (or ivars, which have an implicit reference to self) will maintain a strong reference to the object for the duration of the operation (i.e. it will retain it). For example, if you dismiss the view controller while the network operation is in progress, the view controller won't be released until after the network operation is done. That's often fine (as it's just for the duration of the connection ... it's not the dreaded strong reference cycle), especially for a school assignment. But if that's an issue, there are techniques to only use a weak reference to the view controller inside the completion block, thus preventing the retaining of the view controller for the duration of the network operation. But that's beyond the scope of your original question (esp since it leads to a bunch of other questions about whether you want to cancel the network operation or not, when you dismiss the view controller), so I'll leave it at here.
I am new to iOS development coming from a JS background with EmberJS. I want to port my EmberJS App to an iOS App. Therefore i would like to use similiar structures in my iOS App. As EmberJS makes heavy use of promises i searched for something similar for iOS and stumbled upon ReactiveCocoa. It is said in the introduction of ReactiveCocoa that this framework can be used to implement Promises. I tried it but it does not work properly. I wanted to start with a quite simple example:
Make an asynchronous network request (to fill a UITableViewController). Return a promise from this method.
Subscribe to this promise and reload the TableView when it is finished.
I want to do it this way, because i will have to perform several things after the data has been loaded successfully. My approach works basically but i am experiencing the following issues:
My TableView does not reload immediately after the request has been finished.
I am seeing the Log Statements in my subscribeCompleted immediately after the request finished. But the TableView stays blank.
The TableView loads the data after a few seconds of waiting.
If i start scrolling the TableView after i have seen the Log output, the TableView is suddenly loaded.
I suspect this may happen because i am fetching the data in a background thread. I think the resolve of the promise (subscribeCompleted) may happen in the background thread too and Cocoa Touch may not like this. Am i right? But if this is the case, how am i supposed implement a promise?
I hope you can help me getting started with ReactiveCocoa. Thx! :-)
UPDATE:
I managed to fix it by wrapping the to reloadData in a dispatch_async(dispatch_get_main_queue(), ^{... But still i am not sure wether this is the best way to go or what is recommended by ReactiveCocoa. So i am still keen on hearing some answers :-)
// this method wants to use the promise
- (void) loadDataAndPerformActionsAfterwards{
RACSignal *signal = [self fetchObjects];
[signal subscribeCompleted:^{
NSLog(#"Entered subscribeCompleted block signal!");
NSLog(#"Number of objects: %i", self.objects.count);
[self.tableView reloadData];
}];
}
// this method returns a promise. I omitted some parts but it shows basically how i go about resolving the promise.
- (RACSignal*) fetchMoviesForCurrentFormState{
return [RACSignal createSignal:^RACDisposable*(id<RACSubscriber> subscriber) {
NSLog(#"RAC createSignal Block called");
NSString *requestURL = #"...";
NSURL *urlObj = [NSURL URLWithString: requestURL];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData* data = [NSData dataWithContentsOfURL: urlObj];
if(data){
[self performSelectorOnMainThread:#selector(fetchedData:)
withObject:data waitUntilDone:YES];
[subscriber sendCompleted];
}else{
// Not implemented yet: handle the error case
[subscriber sendCompleted];
}
});
// actually i do not know yet what i should return here. Copied from a basic example.
return nil;
}];
}
You're right that this is an issue with threading. However, you don't need to drop down to the level of GCD.
Signals can be "delivered" onto another thread, which just invokes any subscription callbacks there:
- (void) loadDataAndPerformActionsAfterwards {
[[[self
fetchObjects]
deliverOn:RACScheduler.mainThreadScheduler]
subscribeCompleted:^{
NSLog(#"Entered subscribeCompleted block signal!");
NSLog(#"Number of objects: %i", self.objects.count);
[self.tableView reloadData];
}];
}
You may take a look into RXPromise. It's an Objective-C implementation of the Promises/A+ specification with a couple more features. (I'm the author).
A solution utilizing the RXPromise library would look as follows:
- (void) loadDataAndPerformActionsAfterwards {
[self fetchMovie]
.thenOn(dispatch_queue_get_main(), ^id(id fetchedMovie) {
self.model = fetchedObjects;
[self.tableView reloadData];
}, nil);
}
This assumes, method fetchMovie returns a Promise.
How do you get this? Well, you can easily wrap any asynchronous method or operation into one that returns a Promise. This works for any signal approach: completion blocks, callback functions, delegates, KVO, Notification, etc.
For example, a simplified implementation for NSURLConnection's async convenience class method (in practice you should check the response and do better error handling):
- (RXPromise*) fetchMovie {
RXPromise* promise = [[RXPromise alloc] init];
NSMutableRequest* request = ...;
[NSURLConnection sendAsynchronousRequest:request
queue:networkQueue
completionHandler:^(NSURLResponse* response, NSData* data, NSError* error){
if (error) {
[promise rejectWithReason:error];
}
else {
[promise fulfillWithValue:data];
}
}];
return promise;
}
You might want to use an approach using the NSURLConnection delegates, or an approach utilizing a NSOperation subclass. This enables you to implement cancellation:
- (RXPromise*) fetchObjects {
RXPromise* promise = [[RXPromise alloc] init];
NSMutableRequest* request = ...;
HTTPOperation* op =
[[HTTPOperation alloc] initWithRequest:request
queue:networkQueue
completionHandler:^(NSURLResponse* response, NSData* data, NSError* error){
if (error) {
[promise rejectWithReason:error];
}
else {
[promise fulfillWithValue:data];
}
}];
promise.then(nil, ^id(NSError* error){
[op cancel];
return nil;
});
[op start];
return promise;
}
Here, the HTTPOperation object will listen to its own promise for an error signal. If it receives one, for example a cancel message send from another object to the promise, the handler then "forwards" the cancel message to the operation.
A View Controller for example can now cancel a running HTTPOperation as follows:
- (void) viewWillDisappear:(BOOL)animate {
[super viewWillDisappear:animate];
[self.fetchObjectsPromise cancel];
self.fetchObjectPromise = nil;
}
The following method uses AFNetworking to send a request to a webservice and (when successful) acts upon the response. AFNetworking does the request asynchronously and uses blocks for success or failure. So the method to send the request looks something like this (simplified):
- (void)sendRequestForConnector:(NSString *)connector
success:(void (^)(NSData *responseXml))success
failure:(void (^)(NSError *error))failure
{
AFHTTPRequestOperation *operation = [self HTTPRequestOperationForConnector:connector];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
[self handleSuccessForConnector:connector response:[operation responseData]];
success([operation responseData]);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
[self handleFailureForConnector:connector error:error];
failure(error);
}
[operation start];
}
In another class this method is used to call the webservice multiple times. When the previous call was successful, the next call is done. This method looks something like this (failure blocks omitted for brevity):
- (void)import
{
[client sendRequestForConnector:#"cities" success:^{
[client sendRequestForConnector:#"categories" success:^{
[client sendRequestForConnector:#"categoryTranslations" success:^{
[client sendRequestForConnector:#"products" success:^{
[client sendRequestForConnector:#"productTranslations" success:^{
[self finishImport];
}];
}];
}];
}];
}];
}
This works, but as one can see, each webservice call adds another nested success block handler. It seems to me there must be a nicer way to do this, but I can't think of one.
Preferably, I want to wait for the previous call to be completed before I do the next call and use something like GCD to call the whole import method asynchronously. However, AFNetworking is not designed to make synchronous calls.
Any ideas how to achieve this are greatly welcomed. Or is my current solution the way to go?
I was in same situation where i need to wait for response and need to call ws for upload after completed last upload ,
there are several ways to do this
1) You can use dispatch_group and notify when you finished all.
2) Call ws one by one with create semaphores
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
dispatch_semaphore_t test = dispatch_semaphore_create(0);
for (NSUInteger i = 0; i < self.uploadDocument.count; ++i) {
// My custom method with block which call ws
[self uploadMultipleImagesUsingAFNetworkingMultipartFormatWithDocumentId:[[self.uploadDocument objectAtIndex:i] valueForKey:kDocument_DocumentId] andIndex:i withCompletionBlock:^(id responseObj, NSError *error) {
// Your Logic
dispatch_semaphore_signal(test);
}];
dispatch_semaphore_wait(test, DISPATCH_TIME_FOREVER);
// Here comes when your all ws finish
dispatch_async(dispatch_get_main_queue(), ^{
[self.tblUpload reloadData];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
});
}
You could have a look at octokit. They combine AFNetworking with Mantle and ReactiveCocoa - chaining works easily and cleanly with ReactiveCocoa.
https://github.com/octokit/octokit.objc
I recommend you to have a look at this answer Better asynchronous control flow with Objective-C blocks
A few custom solutions are suggested to solve this.
I face the same problem and have been thinking about introducing PromiseKit & AFNetworking-PromiseKit for a while. But I finally gave up and come up with a simple and somewhat kludge solution,
So I introduced a property called "step", the main purpose to kvo it. Then for each asynchronous call when it finishes it needs to mark its step. The kvo method will then move the whole process to next step, i.e. the kvo method does the flow control.
For example, your sendRequestForConnector:#"cities" will be step 1, #"categories" be step 2, #"categoryTranslations" be step 3, #"products" be step 4 ... at the end of each success block set the step to its own value. So kvo will know this step is finished and it needs to call the next asynchronous method.
The kvo method oversee and controls the whole process.
switch (step.intValue) {
case 1: //call step2; break;
case 2: //call step3; break;
case 3: //call step4; break;
case 4: //call step5; break;
...
case x: // finish, remove kvo on step
}
So far it serves my purpose and my codes do look much cleaner.
If you want to make synchronous calls then just use NSString's "stringWithContentsOfURL:" method. So just loop through an NSArray of NSURL's with this code in your loop.
NSError* error = nil;
NSString* htmlSource = [NSString stringWithContentsOfURL:theNSURL encoding:NSUTF8StringEncoding error:&error];
if (error) {
// handle error
}
// parse htmlSource
I'm using an AFNetworking client object which makes an asynchronous request for an XML document and parses it.
Also using NSNotificationCenter to post a notification when the document has finished parsing.
Is there a way to wait for a notification to be posted without blocking the main thread?
E.g code:
-(void)saveConfiguration:(id)sender {
TLHTTPClient *RESTClient = [TLHTTPClient sharedClient];
// Performs the asynchronous fetching....this works.
[RESTClient fetchActiveUser:[usernameTextField stringValue] withPassword:[passwordTextField stringValue]];
/*
* What do I need here ? while (xxx) ?
*/
NSLog(#"Fetch Complete.");
}
Basically I'm wondering what sort of code I need in the above specified area to ensure that the function waits until the fetch has been completed ?
As it is right now I'll see "Fetch Complete." in the debug console before the fetch has been completed.
I tried adding a BOOL flag to the TLHTTPClient class:
BOOL fetchingFlag;
and then trying:
while([RESTClient fetchingFlag]) { NSLog(#"fetching...); }
When this class receives the notification it sets RESTClient.fetchingFlag = FALSE; which should technically kill the while loop right? Except my while loop runs infinitely ?!
Basically I'm wondering what sort of code I need in the above specified area to ensure that the function waits until the fetch has been completed ?
You need no code. Don't put anything in the method after you start the fetch, and nothing will happen. Your program will "wait" (it will actually be processing other input) until the notification is recieved.
In the notification handler method, put all the stuff that you need to do when the fetch is completed. This is (one of) the point(s) of notifications and other callback schemes -- your object won't do anything further until it gets the notification that it's time to act.
Is there a way to wait for a notification to be posted without blocking the main thread?
That's exactly how it works already.
If you don't need to inform multiple objects upon completion of the task, you could add a completion handler (objc block) to the -fetchActiveUser:withPassword: method (so it would become something like -fetchActiveUser:withPassword:completionHandler: and add the code to be executed in the completion handler.
An example, lets say your -fetchActiveUser:withPassword:completionHandler: method looks like the following:
- (void)fetchActiveUser:(NSString *)user
withPassword:(NSString *)pass
completionHandler:(void (^)(TLUser *user, NSError *error))handler
{
NSURL *URL = [NSURL URLWithString:#"http://www.website.com/page.html"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
NSOperationQueue *queue = [NSOperationQueue currentQueue];
[NSURLConnection sendAsynchronousRequest:request
queue:queue
completionHandler:^ (NSURLResponse *response, NSData *data, NSError *error)
{
if (!handler)
{
return
};
if (data)
{
TLUser *user = [TLUser userWithData:data];
if (user)
{
handler(user, nil);
}
else
{
NSError *error = // 'failed to create user' error ...
handler(nil, error);
}
}
else
{
handler(nil, error);
}
}];
}
The completion handler will be called whenever the task is finished. It will either return a TLUser object or an Error if something went wrong (bad connection, data format changed while parsing, etc...).
You'll be able to call the method like this:
- (void)saveConfiguration:(id)sender
{
TLHTTPClient *RESTClient = [TLHTTPClient sharedClient];
// Performs the asynchronous fetching
[RESTClient fetchActiveUser:[usernameTextField stringValue]
withPassword:[passwordTextField stringValue]
completionHandler:^ (TLUser *user, NSError *error)
{
if (user)
{
NSLog(#"%#", user);
}
else
{
NSLog(#"%#", error);
}
}];
}
Of course, in this example I've used the build in asynchronous methods of NSURLConnection in stead of AFNetworking, but you should be able to get the general idea.