[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.
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
...
}
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;
}
here what i want to do
i want perform a loop or kind of something like that before making an action
so far, i'm doing like this
//check if it's multiplayer mode
if ([PlayerInfo instance].playingMultiplayer == YES)
{
//no cards has been played
//the while and NSRunLoop combination seems harsh
while ([PlayerInfo instance].cardsPlayed == NULL)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
//after the "loop thing" done, execute this method
//take out cards with the broadcasted cardsPlayed, it's event based
[self takeOutCards:[PlayerInfo instance].cardsPlayed];
}
//single player, don't bother this
else
{
//AI logic, select possible best cards
NSArray * bestCards = [playerLogic selectBestMove];
[self takeOutCards:bestCards];
}
this is looks like a bad practice.
by the way, [PlayerInfo instance].cardsPlayed is a variable that broadcasted from server and will be changed frequently. the changes based on user interaction while another user will wait what cards would be played.
in short, what should i do while waiting the broadcasted variable to come? any suggestion? thanks
Your app already has an event loop running, and it should already be idling in between user actions and while the network is being checked for new state. What you want to do is generate an event when the condition is triggered so the app can react.
The simplest way to do this is to post a notification (within the app) when the condition occurs. Something like this:
// just guessing about your PlayerInfo here, and assuming ARC
#property (nonatomic, strong) NSArray *cardsPlayed;
#synthesize cardsPlayed = _cardsPlayed;
// replace the synthesized setter with one that does the set and checks for
// the condition you care about. if that condition holds, post a notification
//
- (void)setCardsPlayed:(NSArray *)cardsPlayed {
_cardsPlayed = cardsPlayed;
// is the condition about an array that's nil or empty? guessing 'either' here...
if (!_cardsPlayed || !_cardsPlayed.count) {
[[NSNotificationCenter defaultCenter]
postNotificationName:#"CardsPlayedDidBecomeEmpty" object:self];
}
}
Then, when initializing the object that cares about the condition (where you proposed that loop in your question)...
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(cardsPlayedEmpty:)
name:#"CardsPlayedDidBecomeEmpty" object:nil];
This will cause cardsPlayedEmpty: to be invoked when the condition comes to pass. It should have a signature like this:
- (void)CardsPlayedDidBecomeEmpty:(NSNotification *)notification {
}
EDIT - I think your revised question is that you want to pause before checking server state,. You can do that using performSelector:withObject:afterDelay: ...
- (void)getRemoteState {
NSURLRequest = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// here, handle the response and check the condition you care about
// post NSNotification as above here
}];
}
// now the code you're trying to write ...
if ([PlayerInfo instance].playingMultiplayer == YES) {
// give other players a chance to play, then check remote state
// this will wait 20 seconds before checking
[self performSelector:#selector(getRemoteState) withObject:nil afterDelay:20.0];
I am using [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) in my app. By using this my app is terminated in iOS 4.3 but it is working fine in iOS 5.0.
How to use this in iOS 4.3 can any one help me.
Here's a full implementation that works for me. Feel free to rename it and add as a category on NSURLConnection, or just add it as a local method in the class you're working in.
-(void)sendAsynchronousRequest:(NSURLRequest*)request queue:(NSOperationQueue*)queue completionHandler:(void(^)(NSURLResponse *response, NSData *data, NSError *error))handler
{
__block NSURLResponse *response = nil;
__block NSError *error = nil;
__block NSData *data = nil;
// Wrap up synchronous request within a block operation
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
}];
// Set completion block
// EDIT: Set completion block, perform on main thread for safety
blockOperation.completionBlock = ^{
// Perform completion on main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
handler(response, data, error);
}];
};
// (or execute completion block on background thread)
// blockOperation.completionBlock = ^{ handler(response, data, error); };
// Execute operation
[queue addOperation:blockOperation];
}
EDIT
I had to modify the method because I was making UIKit calls in my completion block (e.g. updating labels etc). So it's actually a bit safer to call completion block on the main thread. (original version commented out)
The method you are trying to use is only available on iOS 5. For earlier OSes, consider using
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
and wrapping it into a new thread to achieve async behavior.
Both H2CO3 and Ken Thomases suggestions are right.
In addition, you could take a look at ios4-implementation-of-nsurlconnection-sendasynchronousrequestqueuecompletio.
If you use the main queue as the queue where the completion handler performs, you could use (as Tom suggested) the delegate pattern. To avoid duplicate code, you could use a wrapper on NSURLConnection delegates mechanism.
In the other case, if you want to maintain the async behaviour and you don't want to deal with sync call (as H2CO3 suggested, note that his suggestion is also valid) and the completion handler performs in a different queue, then I suggest you to wrap the async delegate pattern in a NSOperation class. This approach is quite difficult but you can find a good way of do this in Concurrent Operations Demystified (see both the posts).
Hope it helps.
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.