How is it better to wait an asynchronous method to be finished in iPhone app? - objective-c

everybody.
I want to understand, how i shoud procceed situations when an asynchronous method has "didFinish:#selector(SEL)" parameter.
My code example is:
//
// Authentication check
- ( void )authenticationSuccess: ( GDataServiceTicket* ) ticket
authenticatedWithError: ( NSError* ) error {
if ( error == nil )
{
NSLog( #"authentication success" );
}
else
{
NSLog( #"authentication error" );
}
}
//
- ( void ) fetchFeedOfSpreadsheets {
//create and authenticate to a google spreadsheet service
if ( !(mService) )
{
GDataServiceGoogleSpreadsheet *service = [self spreadsheetService];
[mService autorelease];
mService = [service retain];
}
// check autentication success ( invoke "authenticationSuccess" method for debug success & error )
[mService authenticateWithDelegate: self
didAuthenticateSelector:#selector(authenticationSuccess:
authenticatedWithError:) ];
// HERE I WANT TO MAKE A PAUSE AND WHAIT THE RESULT, EITHER I AUTHENTICATED OR NOT
// AND MAKE AN "IF" STATEMENT TO CONTINTUE WORKING ON SERVER, OR RETURN ERROR
//fetch retrieves the feed of spreadsheets entries
NSURL *feedURL = [ NSURL URLWithString: kGDataGoogleSpreadsheetsPrivateFullFeed ];
GDataServiceTicket *ticket;
ticket = [mService fetchFeedWithURL: feedURL
delegate: self
didFinishSelector: #selector(spreadsheetsTicket:finishedWithFeed:
error: ) ];
// HERE I WANT TO WAIT SECOND TIME. I WANT "spreadsheetsTicket:
// finishedWithFeed:error:" TO PROCCEED ERROR AND PUT A FEED IN SOME NSARRAY OBJECT
// AND AFTER THAT I WANT TO WORK WITH THAT NSARRAY RIGHT HERE
}
I's clear, that i can push the code i want into the end of "authenticationSuccess" method section, but it's also clear, that it's a wrong a way to solve the proble. There a number of situations like this, where i call an asynchronous method with a selector parameter, and i want to find a solution providing me a flexible code writing.
Thanks in advance.

It's a standard practice in Objective-C to put the code to be executed after the authentication in the authenticationSucess: method. You might not like it, but that is life.
Many people had the same complaint as you, so
on iOS 4 and later, there's something called blocks which allow you to write the code to be executed after the authentication in the method which initiates the authentication, as in
[mService authenticateAndExecute:^{
code to be executed when successfully authenticated ;
} whenError:^{
code to be executed when authentication failed;
} ];
But in this case you need to modify the API, which is possible by using categories. See this blog post by Mike Ash. He has many other posts on blocks on the same blog, which are also very instructive.

If you're going to use a library that works asynchronously (and therefore doesn't block your UI), you should have a good reason for trying to force it to work synchronously.
You should be checking for an authentication error at the end of your authenticationSuccess:authenticatedWithError: method, and calling the next request from there if there's a success. Similarly, in your spreadsheetsTicket:finishedWithFeed:error: check for an error, and continuing processing if there isn't one. It might be a better design to do that continued work in a separate method, but that's up to you.
Is there a specific reason you want to use the GData API in a synchronous fashion?

Related

Inviting Peer to MCSEssion

This is my first time posting on stackoverflow, and I'm aware of the strict posting requirements. Please let me know if I'm not following any of the guidelines.
I'm currently writing an IOS (8.4) application in Xcode, using Objective-C. The goal is to use MCSessions in order to stream data between IOS devices. I'm currently struggling with the concept of sessions, despite reading numerous posts here and elsewhere that attempt to clarify the topic. Here are the resources I'm already aware of:
https://developer.apple.com/videos/play/wwdc2013-708/
https://medium.com/#phoenixy/using-multipeer-connectivity-120abacb9db
Here's my current understanding: At the most basic level, you have an advertiser, and a browser. The advertiser has a local session, which allows them to "advertise". When the browser sees an advertiser, the browser sends an invite to the advertiser to his (the browser's) local MCSession. Assuming this is all correct, here's where I'm getting confused. The advertiser can accept the invite, and in the process, passes his local session to the invitationHandler.
I have implemented the following logic in code, as shown below. However, in tracing MCSession state changes for both the advertiser and browser, a connection is attempted, but the final state is always didNotNonnect.
Code for sending invitation (Browser):
[self.broadcasterBrowser invitePeer:[broadcasterPeerIDs objectAtIndex:indexPath.row]
toSession: self.appDelegate.mpcHandler.session withContext:nil timeout:30.0 ];
Code for accepting invitation (Advertiser):
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser
didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void(^)(BOOL accept, MCSession *session))invitationHandler
{
ArrayInvitationHandler = [NSArray arrayWithObject:[invitationHandler copy]];
// ask the user
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:peerID.displayName
message:#"Would like to create a session with you"
delegate:self
cancelButtonTitle:#"Decline"
otherButtonTitles:#"Accept", nil];
[alertView show];
if (alertViewResult)
{
void (^invitationHandler)(BOOL, MCSession *) = [ArrayInvitationHandler objectAtIndex:0];
invitationHandler(YES, self.appDelegate.mpcHandler.session);
}
}
Any help is greatly appreciated!
Austin
I ran into a similar problem trying to use MPC. I created a custom class to handle all of the MPC connectivity details. While testing though, every time my advertiser would accept the invite, it would complain about wrong connection data and fail. I discovered that the problem was that I was vending out the MCPeerID object for my device from a class variable I created as below:
static var peerObject : MCPeerID {
return MCPeerID(displayName: deviceNameString)
}
lazy var sessionIVar = MCSession (peer: MyConnectivityClass.peerObject)
func startAdvertisingForConnectivity () {
advertiserService = MCNearbyServiceAdvertiser (peer: MyConnectivityClass.peerObject, discoveryInfo: nil, serviceType: "my-multipeer-connectivity-service-identifier")
}
Then when I got an invitation I would initialize a MCSession object using the "peerObject" computed property and return it in the invitation handler, like this:
func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: #escaping (Bool, MCSession?) -> Swift.Void) {
invitationHandler(true, sessionIVar)
}
I assumed that each time I called for "MyConnectivityClass.peerObject" it would give back an identical peerID because I was always initializing it with the same display name. It turns out that's not true. So when I was advertising I was using one peerID object and then when I was responding to the invitation, I was responding with a MCSession object that contained an entirely different peerID.
So the solution was to change the "MyConnectivityClass.peerObject" computed class property to a constant, or an Ivar, in my connection handler class. Like this:
let peerObject : MCPeerID = MCPeerID(displayName: deviceNameString)
Then the rest of the code just worked because no matter how many times I called for the MCPeerID object, it was always the same. Looking back I don't know why I started out with it the way I did. :-)
Then in my connectivity class I archived and stored the MCPeerID objects for both the browser and the advertiser so that I could have the advertiser automatically accept the invitation for trusted MCPeerIDs. That's not possible if you create the MCPeerID object each time you use it, even if you always initialize it with the same DisplayName.

How to do multiple serial connection using nsurlconnection?

i am new to iPhone development so i don't know whether this is simple or complicated.
Here is my problem. I have a app which first connects to web service for authentication process. After authentication i need to get data of logged in user. So, how to connect to web services twice and serially using NSURLConnection. Serially means first login and then retrieve data. In my case, the request are sent in random order, means sometime it sends login request first and some time retrieve data request.
Can anyone please help this out.
Thanks.
Set the delegate of your NSURLConnection to self, and implement NSURLConnectionDelegate methods, there's a method named
- (void) connectionDidFinishLoading:(NSURLConnection*)connection
which tells you when the request was finished successfully so inside it you can call the second request.
Now your only problem is that you don't know which request to call second :) as you don't know which one was called first so what I usually do is simply, define some variable, simply an int or NSString as a tag, e.g.
Add these before your #implemenetation
#define TAG_LOGIN_REQUEST 1
#define TAG_DATA_REQUEST 2
now define this inside the class
int currentTag
now before you start the login request add this
currentTag = TAG_LOGIN_REQUEST;
and before you start the data request add this
currentTag = TAG_DATA_REQUEST;
whenever this delegate method gets called
- (void) connectionDidFinishLoading:(NSURLConnection*)connection
you simply check
if (tag == TAG_LOGIN_REQUEST) {
// send data request
}
else if (tag == TAG_DATA_REQUEST) {
// send login request
}
I hope this helps, if there's anything that's not clear, please tell me.
Just try to synchronise you calling. Here is a example:
request for login and wait for login response
get login response and call getData()
if login response if yes, request for data.
Hope this helps.. :)
EDIT:
maintain two global flag (variable). let loginReq and dataReq are those flag.
before calling getLogin(), make loginReq = true;
in your connectinDidFinishLoading check
if(loginReq == true){
loginReq = false;
dataReq = true;
getData();
}
else if(dataReq == true){
dataReq = false;
}

How would you write fetching a collection the "Reactive Cocoa" way?

The client I'm building is using Reactive Cocoa with Octokit and so far it has been going very well. However now I'm at a point where I want to fetch a collection of repositories and am having trouble wrapping my head around doing this the "RAC way"
// fire this when an authenticated client is set
[[RACAbleWithStart([GHDataStore sharedStore], client)
filter:^BOOL (OCTClient *client) {
return client != nil && client.authenticated;
}]
subscribeNext:^(OCTClient *client) {
[[[client fetchUserRepositories] deliverOn:RACScheduler.mainThreadScheduler]
subscribeNext:^(OCTRepository *fetchedRepo) {
NSLog(#" Received new repo: %#",fetchedRepo.name);
}
error:^(NSError *error) {
NSLog(#"Error fetching repos: %#",error.localizedDescription);
}];
} completed:^{
NSLog(#"Completed fetching repos");
}];
I originally assumed that -subscribeNext: would pass an NSArray, but now understand that it sends the message every "next" object returned, which in this case is an OCTRepository.
Now I could do something like this:
NSMutableArray *repos = [NSMutableArray array];
// most of that code above
subscribeNext:^(OCTRepository *fetchedRepo) {
[repos addObject:fetchedRepo];
}
// the rest of the code above
Sure, this works, but it doesn't seem to follow the functional principles that RAC enables. I'm really trying to stick to conventions here. Any light on capabilities of RAC/Octokit are greatly appreciated!
It largely depends on what you want to do with the repositories afterward. It seems like you want to do something once you have all the repositories, so I'll set up an example that does that.
// Watch for the client to change
RAC(self.repositories) = [[[[[RACAbleWithStart([GHDataStore sharedStore], client)
// Ignore clients that aren't authenticated
filter:^ BOOL (OCTClient *client) {
return client != nil && client.authenticated;
}]
// For each client, execute the block. Returns a signal that sends a signal
// to fetch the user repositories whenever a new client comes in. A signal of
// of signals is often used to do some work in response to some other work.
// Often times, you'd want to use `-flattenMap:`, but we're using `-map:` with
// `-switchToLatest` so the resultant signal will only send repositories for
// the most recent client.
map:^(OCTClient *client) {
// -collect will send a single value--an NSArray with all of the values
// that were send on the original signal.
return [[client fetchUserRepositories] collect];
}]
// Switch to the latest signal that was returned from the map block.
switchToLatest]
// Execute a block when an error occurs, but don't alter the values sent on
// the original signal.
doError:^(NSError *error) {
NSLog(#"Error fetching repos: %#",error.localizedDescription);
}]
deliverOn:RACScheduler.mainThreadScheduler];
Now self.repositories will change (and fire a KVO notification) whenever the repositories are updated from the client.
A couple things to note about this:
It's best to avoid subscribeNext: whenever possible. Using it steps outside of the functional paradigm (as do doNext: and doError:, but they're also helpful tools at times). In general, you want to think about how you can transform the signal into something that does what you want.
If you want to chain one or more pieces of work together, you often want to use flattenMap:. More generally, you want to start thinking about signals of signals--signals that send other signals that represent the other work.
You often want to wait as long as possible to move work back to the main thread.
When thinking through a problem, it's sometimes valuable to start by writing out each individual signal to think about a) what you have, b) what you want, and c) how to get from one to the other.
EDIT: Updated to address #JustinSpahrSummers' comment below.
There is a -collect operator that should do exactly what you're looking for.
// Collect all receiver's `next`s into a NSArray. nil values will be converted
// to NSNull.
//
// This corresponds to the `ToArray` method in Rx.
//
// Returns a signal which sends a single NSArray when the receiver completes
// successfully.
- (RACSignal *)collect;

Salesforce Rest Request, insert data

I'm using the salesforce iphonesdk, I wonder if anybody knows how to submit data into the salesforce object, like using INSERT ? Which method is to be used?
You need to use the create() call, passing it an array of SObjects to insert — see the documentation here.
You should probably read the Getting Started section of the reference material, especially the API Call Basics. Your question demonstrates a considerable lack of research, and I'd advise making sure you do research things yourself before asking a question on stackoverflow if you don't want it to be closed out. If you've read the documentation and still don't understand how to do something, then you're in a good position to ask for help!
Swift version:
Your controller class should conform to RestClientDelegate protocol
data is the dictionary(object) to be inserted
let data = ["Percentage__c": "1", "Amount__c":"11", "Volume__c":"111", "Promotion_Details__c": "00112233"]
let request = RestClient.shared.requestForCreate(withObjectType: "<your_table_name_>", fields: data)
RestClient.shared.send(request, delegate: self)
Another Way:
If you don't want to conform to RestClientDelegate, simply call:
RestClient.shared.send(request: request, onFailure: { (err, res) in
// some closure to handle error
}) { (any, res) in
// another closure to handle error
}
With guidance by Lacey Snr,
Found my own answer
NSMutableDictionary *dic = [[NSMutableDictionary alloc]init];
[dic setObject:#"Smith" forKey:#"Name_c"];
[dic setObject:#"5" forKey:#"PAX_c"];
SFRestRequest *request = [[SFRestAPI sharedInstance]
requestForCreateWithObjectType:#"Booking_Forms_c" fields:dic];
[[SFRestAPI sharedInstance] send:request delegate:self];

Improve my asyncronous request design

I would like some opinions on a design that i have. My app is starting to send an asynchronous request to get some JSON data and if that goes well i get to my callback method and then start some other asynchronous request for some data then in another callback i go on and on and i feel my code is getting to be like spaghetti code, i jump from one callback method to a request to another callback then request and so on, any ideas of an better design ?
Let me show some example code (not the real code but shows my point) :
-(void)startApp {
//make an request and use callBackMethod to parse data
}
-(void)callBackMethod {
//parse data and check it, if ok go make next request and use CallBackMethod1
}
-(void)callBackMethod1 {
//parse data and check it, if ok go make next request and use CallBackMethod2
}
-(void)callBackMethod2 {
//parse data and check it, if ok go make next request and use CallBackMethod3
}
-(void)callBackMethod3 {
//parse data
}
You get the point. It's a mess after my opinion and i want to refactor the code to something more readable and easier to maintain. I heard about blocks. Not sure how/if that might make my design better.
donnib
Your asynchronous request design is fine, although you should use more descriptive method names. The example here is not really spaghetti code, it's just you're doing something somewhat complicated and the code structure necessarily reflects that.
An implementation using blocks would look something vaguely like this:
- (void)startApp {
[self makeRequest:... completionHandler:^(NSData *data){
// Parse data and check it, as in callBackMethod
if (ok) [self makeRequest:... completionHandler:^(NSData *data){
// Parse data and check it, as in callBackMethod1
if (ok) [self makeRequest:... completionHandler:^(NSData *data){
// Parse data and check it, as in callBackMethod2
if (ok) [self makeRequest:... completionHandler:^(NSData *data){
// Parse data, as in callBackMethod3
}];
}];
}];
}];
}
Depending on the situation, that may or may not be easier to follow. Another alternative, if you control the web service you're querying and the "check" is amenable, is to have the web service do all the checking on its end and return all the data in one response instead of requiring all this back-and-forth.