How to synchronize variables with TWRequest performRequestWithHandler in iOS5? - iphone

I really couldn't figure out how to deal with iOS5 Twitter API TWRequest performRequestWithHandler. I declared an instance variable of NSMutableArray *parsedTimeLine in my .h file and a method signature of -(void) fetchWebData: (NSString *) screenName;
The said method implementation is here:
-(void) fetchWebData: (NSString *) screenName {
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
[params setObject:screenName forKey:#"screen_name"];
[params setObject:#"f" forKey:#"include_entities"];
[params setObject:#"f" forKey:#"include_rts"];
[params setObject:#"10" forKey:#"count"];
[params setObject:#"t" forKey:#"trim_user"];
[params setObject:#"f" forKey:#"contributor_details"];
[params setObject:#"t" forKey:#"exclude_replies"];
NSURL *url = [NSURL URLWithString:#"http://api.twitter.com/1/statuses/user_timeline.json"];
TWRequest *request = [[TWRequest alloc] initWithURL:url
parameters:params
requestMethod:TWRequestMethodGET];
[request performRequestWithHandler:
^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if (responseData) {
NSError *jsonError;
parsedUserTimeLine =[NSJSONSerialization JSONObjectWithData:responseData
options:NSJSONReadingMutableLeaves
error:&jsonError];
NSLog(#"jsonArray result count inside request: %d",parsedUserTimeLine.count);
NSLog(#"jsonArray values inside request: %#", parsedUserTimeLine);
}
}];
NSLog(#"jsonArray result count outside request: %d",parsedUserTimeLine.count);
NSLog(#"jsonArray values outside request: %#", parsedUserTimeLine);
}
This works fine, but there's a weird thing I really couldn't figure out. My 2 NSLogs INSIDE the request method shows the accurate count value and array contents of the parsedUSerTimeline. But my 2 NSLogs OUTSIDE THE REQUEST method shows
-jsonArray result count outside request: 0
-jsonArray values outside request: (null)
also, I checked the value of parsedUserTimeLine at my viewDidLoad after calling the fetchWebData method shows count = 0 and value = null.
Kindly help me with this guys. I need to access the value of parsedUserTimeLine globally. Thanks in advance.

I don't know the Twitter-Framework, but from the code you've posted i can tell you that this is going to perform asynchron. The NSLogs are both valid. The inner NSLogs are called when the request has finished, the outer are called even if the request has not been started.
Simply call a method to inform the others if the request has been finished.
[request performRequestWithHandler:
^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if (responseData) {
NSError *jsonError;
NSDictionary *parsedUserTimeLine =[NSJSONSerialization JSONObjectWithData:responseData
options:NSJSONReadingMutableLeaves
error:&jsonError];
NSLog(#"jsonArray result count inside request: %d",parsedUserTimeLine.count);
NSLog(#"jsonArray values inside request: %#", parsedUserTimeLine);
// The request will be perform on another thread, so call the method on the main
// thread to avoid crossing operations
[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self requestHasFinishedWithData:parsedUserTimeLine];
}]
}
}];

Related

json parsing of multiple url, one after another in sequence or where to write another function calling in JSONRequestOperationWithRequest

I have parsed json. The result of json stored in array which contain list of video ID.
Now I want to parse another json which retrieve detail of video and this json will be parsed in loop videoIDArray.count times
Here is code:
- (void)viewDidLoad
{
[super viewDidLoad];
videoIDArray = [[NSMutableArray alloc] init];
viewArray = [[NSMutableArray alloc] init];
//======Json Parsing
NSString *urlstring = [NSString stringWithFormat:#"https://myURL/youtubeList"];
NSURL *url = [NSURL URLWithString:urlstring];
NSURLRequest *Request = [NSURLRequest requestWithURL:url];
conn = [[NSURLConnection alloc] initWithRequest:Request delegate:self];
if (conn) {
webdata = [[NSMutableData alloc] init];
}
//==========
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if (connection==conn) {
[webdata setLength:0];
}
if (connection==conn2) {
[webdata2 setLength:0];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
if (connection==conn) {
[webdata appendData:data];
}
if (connection==conn2) {
[webdata2 appendData:data];
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//-------conn getting list of videoID
if (connection == conn) {
NSString *str = [[NSString alloc] initWithBytes:[webdata bytes] length:[webdata length] encoding:NSUTF8StringEncoding];
NSDictionary *Result = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
[videoIDArray addObjectsFromArray:[[[Result valueForKey:#"items"] valueForKey:#"id"] valueForKey:#"videoId"]];
NSLog(#"Video ID %#",videoIDArray);
//======conn2 is for getting detail of video base on videoID object
for (int i=0; i<videoIDArray.count; i++) {
NSString *urlstring = [NSString stringWithFormat:#"https://mydetailURL/videos/%#?v=2&alt=json",[videoIDArray objectAtIndex:i]];
NSURL *url = [NSURL URLWithString:urlstring];
NSURLRequest *Request = [NSURLRequest requestWithURL:url];
conn2 = [[NSURLConnection alloc] initWithRequest:Request delegate:self];
if (conn2) {
webdata2 = [[NSMutableData alloc] init];
}
}
//==========
}
if (connection==conn2) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
[youtubeTableView reloadData];
NSString *str = [[NSString alloc] initWithBytes:[webdata bytes] length:[webdata length] encoding:NSUTF8StringEncoding];
NSDictionary *Result = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
NSLog(#"ResultConn2 %#",Result);
[viewArray addObject:[[[Result valueForKey:#"entry"] valueForKey:#"yt$statistics"] valueForKey:#"viewCount"]];
NSLog(#"View Array %#",viewArray);
}
}
Problem is: it is not parsing as many times as in loop, only for last one connectionDidFinishLoading method called and crashed..
Can somebody tell me how to do this?
Is there any other way to do this?
EDIT
With AFNetworking
i changed my code like:
for (int i=0; i<videoArray.count; i++) {
[self parseWithUrl:[videoArray objectAtIndex:i]];
}
-(void)parseWithUrl: (NSString *)urlstr
{
NSString *tstr=[urlstr stringByReplacingOccurrencesOfString:#"\"" withString:#""];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"https://myURL/feeds/api/videos/%#?v=2&alt=json",tstr]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation =
[AFJSONRequestOperation JSONRequestOperationWithRequest: request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
//instead of NSLog i want to return result
NSDictionary *result = (NSDictionary *)JSON;
NSLog(#"VideoResult %#",result);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Error Retrieving Weather"
message:[NSString stringWithFormat:#"%#",error]
delegate:nil
cancelButtonTitle:#"OK" otherButtonTitles:nil];
[av show];
}];
[operation start];
}
I want to write:
-(NSDictionary *)parseWithUrl: (NSString *)urlstr
Is it possible?
if Yes then suggest me where i should return result?
if i want to call another method after completing json then where to write call code
here is my code:
[self getData:self.weather];
my method called number of times which i don't want.
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
self.weather = (NSDictionary *)JSON;
[self getData:self.weather];
.....
.....
}
Your issue stems from the fact that each of these NSURLConnection connections run asynchronously and therefore you're running many requests concurrently, but your routine is using a single webData2, so your requests are tripping over each other.
If you want to stay with your current design, rather than having a for loop where you initiate all of the second set of requests, you should only request the first one. And then have the connectionDidFinishLoading for the second type of request initiate the next one. (You could manage this "next" process by keeping track of some numeric index indicating which request you're processing, incrementing it each time.)
But you ask how to do these requests sequentially, one after another. Is there any reason why you cannot do them concurrently (more accurately, when first request is done, then issue the detail requests for the individual videos concurrently). In that scenario, even better than the clumsy fix I outlined in the prior paragraph, a more logical solution is an NSOperation-based implementation that:
Uses a separate object for each connection so the individual requests don't interfere with each other;
Enjoys the concurrency of NSURLConnection, but constrains the number of concurrent requests to some reasonable number ... you will yield significant performance benefits by using concurrent requests; and
Is cancelable in case the user dismisses the view controller while all of these requests are in progress and you want to cancel the network requests.
If you're already familiar writing NSURLConnectionDataDelegate based code, wrapping that in an NSOperation is not much worse. (See Defining a Custom Operation Object in the Concurrency Programming Guide.) We can walk you through the steps to do that, but frankly much easier is to use AFNetworking. They've done the complicated stuff for you.
In your edit to your question, you ask whether it is possible to write:
- (NSDictionary *)parseWithUrl: (NSString *)urlstr
While it's technically possible to, you never want a method on the main queue waiting synchronously for a network request. If parseWithURL cannot do what it needs to do inside the success block of the AFNetworking call (e.g. you might initiate a [self.tableView reloadData] or whatever is needed for your UI), then have parseWithURL return the dictionary in a completion handler of its own, e.g.:
- (void)parseWithURL: (NSString *)urlstr completion:(void (^)(NSDictionary *))completion
{
...
AFJSONRequestOperation *operation =
[AFJSONRequestOperation JSONRequestOperationWithRequest: request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
completion(JSON); // call the completion block here
} failure:...];
}
Finally I am done parsing synchronous multiple json parsing with help of AFNetworking
Success of json parsing call another method in AFNetworking done by: link
Here is Code:
- (void)getResponse:(void (^)(id result, NSError *error))block {
NSString *weatherUrl = [NSString stringWithFormat:#"%#weather.php?format=json", BaseURLString];
NSURL *url = [NSURL URLWithString:weatherUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 2
AFJSONRequestOperation *operation =
[AFJSONRequestOperation JSONRequestOperationWithRequest:request
// 3
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
//Success
block(JSON,nil); //call block here
}
// 4
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Error Retrieving Weather"
message:[NSString stringWithFormat:#"%#",error]
delegate:nil
cancelButtonTitle:#"OK" otherButtonTitles:nil];
[av show];
}];
// 5
[operation start];
}
calling will be:
[self getResponse:^(id result, NSError *error) {
//use result here
}];

AFNetworking POST to REST webservice

Brief backstory, our previous developer used ASIHTTPRequest to make POST requests and retrieve data from our webservice. For reasons unknown this portion of our app stopped working. Seemed like good enough time to future proof and go with AFNetworking. REST webservice runs on the CakePHP framework.
In short I am not receiving the request response string using AFNetworking.
I know the webservice works because I am able to successfully post data and receive the proper response using curl:
curl -d "data[Model][field0]=field0value&data[Model][field1]=field1value" https://example.com/api/class/function.plist
Per the previous developer's instructions I came up with the following.
#import "AFHTTPRequestOperation.h"
…
- (IBAction)loginButtonPressed {
NSURL *url = [NSURL URLWithString:#"https://example.com/api/class/function.plist"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setValue:[usernameTextField text] forHTTPHeaderField:#"data[User][email]"];
[request setValue:[passwordTextField text] forHTTPHeaderField:#"data[User][password]"];
AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"operation hasAcceptableStatusCode: %d", [operation.response statusCode]);
NSLog(#"response string: %# ", operation.responseString);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", operation.responseString);
}];
[operation start];
}
output:
operation hasAcceptableStatusCode: 200
response string: a blank plist file
attempted solution 1:
AFNetworking Post Request
the proposed solution uses a function of AFHTTPRequestOperation called operationWithRequest. However, when I attempt to use said solution I get a warning "Class method '+operationWithRequest:completion:' not found (return type defaults to 'id'"
attempted solution 2: NSURLConnection. output: I'm able to print the success log messaged but not the response string.
*update - returns blank plist.
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
NSString *httpBodyData = #"data[User][email]=username#example.com&data[User][password]=awesomepassword";
[httpBodyData dataUsingEncoding:NSUTF8StringEncoding];
[req setHTTPMethod:#"POST"];
[req setHTTPBody:[NSData dataWithContentsOfFile:httpBodyData]];
NSHTTPURLResponse __autoreleasing *response;
NSError __autoreleasing *error;
[NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error];
// *update - returns blank plist
NSData *responseData = [NSURLConnection sendSynchronousRequest:req returningResponse:nil error:nil];
NSString *str = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog(#"responseData %#",str);
if (error == nil && response.statusCode == 200) {
// Process response
NSLog(#"success");//returns success code of 200 but blank
NSLog(#"resp %#", response );
} else {
// Process error
NSLog(#"error");
}
These are the essential (stripping out conditions I've made for my own use) lines that ended up satisfying my request to the web service. Thanks for the suggestions #8vius and #mattt !
- (IBAction)loginButtonPressed {
NSURL *baseURL = [NSURL URLWithString:#"https://www.example.com/api/class"];
//build normal NSMutableURLRequest objects
//make sure to setHTTPMethod to "POST".
//from https://github.com/AFNetworking/AFNetworking
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:baseURL];
[httpClient defaultValueForHeader:#"Accept"];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
[usernameTextField text], kUsernameField,
[passwordTextField text], kPasswordField,
nil];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST"
path:#"https://www.example.com/api/class/function" parameters:params];
//Add your request object to an AFHTTPRequestOperation
AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc]
initWithRequest:request] autorelease];
//"Why don't I get JSON / XML / Property List in my HTTP client callbacks?"
//see: https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-FAQ
//mattt's suggestion http://stackoverflow.com/a/9931815/1004227 -
//-still didn't prevent me from receiving plist data
//[httpClient registerHTTPOperationClass:
// [AFPropertyListParameterEncoding class]];
[httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[operation setCompletionBlockWithSuccess:
^(AFHTTPRequestOperation *operation,
id responseObject) {
NSString *response = [operation responseString];
NSLog(#"response: [%#]",response);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", [operation error]);
}];
//call start on your request operation
[operation start];
[httpClient release];
}
Use AFHTTPClient -postPath:parameters:success:failure:, passing your parameters (nested dictionaries/arrays are fine). If you're expecting a plist back, be sure to have the client register AFPropertyListRequestOperation.
In any case, setValue:forHTTPHeaderField: is not what you want here. HTTP headers are for specifying information about the request itself; data is part of the request body. AFHTTPClient automatically converts parameters into either a query string for GET requests or an HTTP body for POST, et al.

iPhone EXC_BAD_ACCESS on accessing instance var in block

I've got a question about instance variables in combination with blocks & arc in Objective C with IOS5.
Shortly, when i access this code, the iPhone gives me an EXC_BAD_ACCESS and terminates:
- (void) doRequest: (void (^)(XMLTreeNode*) )completionHandler {
NSString * urlString = [NSString stringWithFormat:#"blablaurl=%#&", action];
for( NSString* key in parameters ){
urlString = [urlString stringByAppendingFormat:#"&%#=%#", key, [parameters objectForKey:key]];
}
NSURL * url = [NSURL URLWithString:urlString];
NSLog( #"Visiting: %#", [url absoluteString] );
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"GET"];
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse * response, NSData * data, NSError * err) {
NSLog( #"Params=%#", parameters );
completionHandler(e);
}];
}
The exc_bad_access occurs on:
NSLog( #"Params=%#", parameters );
(parameters is an instance variable of the class).. Just defined in the header file, no special property or what-so-ever..
Why does it crash and how can i prevent it? Thanks!
My guess is that it crashes because the objects lifetime is over after the doRequest call, and thus ARC cleans up all variables (and with that the parameter var).. When the urlconnection completes and calls the block, the instance variables are aready cleaned up..
parameters is clean up by ARC.
2 case here:
Your main object isn't released before the block completion: Just create an strong,nonatomic property for "parameters". Using the "strong" keyword in your property say to ARC that you need "parameters" during all your main object life
Your main object is released before the block completion: create a new __block pointer to your object
__block blockParameters = parameters;
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse * response, NSData * data, NSError * err) {
NSLog( #"Params=%#", blockParameters );
completionHandler(e);
}];
Using the "__block" keyword say to ARC that you need "blockParameters" during all your block life
You have only the parameters of the block at your disposal, i.e. response, data and error in this case. You could use [response URL] to get at the parameters.
NSString *path = [[response URL] path];
NSString *secondPartOfURL = [[path componentsSeparatedByString:#"?"] objectAtIndex:1];
NSArray *keyValuePairs = [secondPartOfURL componentsSeparatedByString#´:#"&"];
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
for (NSString *pair in keyValuePairs) {
NSArray *keyValue = [pair componentsSeparatedByString:#"="];
[parameters setValue:[keyValue objectAtIndex:1]
forKey:[keyValue objectAtIndex:0]];
}
NSLog(#"Params=%#", parameters);

Why did the release statement here crashes the app?

NSError *theError = nil;
NSArray *keys = [NSArray arrayWithObjects:#"password", #"userId", nil];
NSArray *objects = [NSArray arrayWithObjects:passwordTextField.text, userNameTextField.text, nil];
NSDictionary *requestDictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
NSString *JSONString =[requestDictionary JSONRepresentation];
NSData *JSONData =[JSONString dataUsingEncoding:NSUTF8StringEncoding];
NSLog(#"JSONString :%#", JSONString);
NSLog(#"JSONData :%#", JSONData);
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://153.20.32.74/11AprP306/passenger/jsonitem"]];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:JSONData];
[request setHTTPMethod:#"POST"];
NSURLResponse *theResponse =[[NSURLResponse alloc]init];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&theResponse error:&theError];
NSLog(#"response : %#", theResponse);
NSLog(#"error : %#", theError);
NSLog(#"data : %#", data);
NSMutableString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"string: %#", string);
[string release];
//[theResponse release]; // this statement crashes the app
Has it got something with to do with this statement :NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&theResponse error:&theError];
I see a & symbol used. What does it means?
I'll post this as a new answer as you edited your question.
You are doing it the wrong way.
It is the responsability of sendSynchronousRequest:returningResponse:error: to create the response for you (or the error if something went wrong)
This is why you need to pass a pointer to theResponse and a pointer to theError
When the call to sendSynchronousRequest:returningResponse:error: is done, theResponse will be created and most importantly autoreleased (by sendSynchronousRequest:returningResponse:error) !!
So in the end you are back to the autorelease/over release issue.
The correct code is:
NSURLResponse *theResponse = nil; // no need to init it will be done later on
NSError *theError = nil; // no need to init either
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&theResponse
error:&theError];
if (aError != nil) { } // handle error
else {} // handle your response data
//no need to release theResponse
Well, you autorelease theResponse when you instantiate it, so releasing it twice is causing your problem. Either don't make the autorelease call or don't make the release call.
Personally, I'd get rid of the autorelease. release gives finer-grained control over the run of your program.
Oh, and the & there is nothing to worry about -- it just passes the address of the variable it proceeds. In this case, you need to pas an NSURLResponse**. Since you have an NSURLResponse*, you pass a reference to it.
This is because theResponse has sent the message autorelease in:
NSURLResponse *theResponse =[[[NSURLResponse alloc]init] autorelease];
If you release an object that has been autoreleased you will cause your application to crash for the Garbage Collector will over release the object.
The & simply means "give me the address of theError and theResponse (basically you are passing a pointer of pointer which is required by the method sendSynchronousRequest:returningResponse:error:)
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request
returningResponse:(NSURLResponse **)response
error:(NSError **)error
The NSURLResponse ** and NSError ** means 'address of address' so give them only theError or theResponse (without the &) would simply give the method 'their address' when it is expecting something else.

Using Twitter API's upload_with_media with iOS SDK?

I've been looking for a Objective-C version of this PHP code sample on how to use Twitter's upload_with_media for an iPhone application.
I have been able to find one. Could you point me to ane example or explain how I could translate this code to Objective-C?
Thanks
You can Do like this for upload_with_media,
TWRequest *postRequest = [[TWRequest alloc] initWithURL:[NSURL URLWithString:#"https://upload.twitter.com/1/statuses/update_with_media.json"] parameters:nil requestMethod:TWRequestMethodPOST];
UIImage * image = [UIImage imageNamed:#"myImage.png"];
//add text
[postRequest addMultiPartData:[#"I just found the secret level!" dataUsingEncoding:NSUTF8StringEncoding] withName:#"status" type:#"multipart/form-data"];
//add image
[postRequest addMultiPartData:UIImagePNGRepresentation(image) withName:#"media" type:#"multipart/form-data"];
// Set the account used to post the tweet.
[postRequest setAccount:twitterAccount];
// Perform the request created above and create a handler block to handle the response.
[postRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
NSString *output = [NSString stringWithFormat:#"HTTP response status: %i", [urlResponse statusCode]];
[self performSelectorOnMainThread:#selector(displayText:) withObject:output waitUntilDone:NO];
}];