I'm working on an update of an iOS app that another developer created. He was using ASIHTTPRequest to handle http requests. However, the version of the app I have to work with crashes. Since ASIHTTPRequest is not being updated anymore, I thought then that I should switch to using AFNetworking, but it's far too complicated for me to figure out.
So finally I thought I could just use NSURLConnection on its own.
Here is the original code that I'm trying to replace:
-(NSArray*)readUTF16LEFeed:(NSURL*) urlToRead{
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:urlToRead];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
lastModified = [NSDate date];
NSData *response = [request responseData];//UTF-16LE
NSString* responseString = [[NSString alloc] initWithData:response encoding:NSUTF16LittleEndianStringEncoding];
DLog(#"string is: %#",responseString);
responseString = [responseString stringByReplacingOccurrencesOfString:#"ISO-8859-1" withString:#"UTF16-LE"];
NSData* data = [responseString dataUsingEncoding:NSUTF16LittleEndianStringEncoding];
return [self parseNamesFromXML:data];
}
return nil;
}
And here is what I'm trying to use instead:
-(NSArray*)readUTF16LEFeed:(NSURL*) urlToRead{
NSURLRequest *request = [NSURLRequest requestWithURL:urlToRead];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error ) {
if ([data length] >0 && error == nil)
{
// DO YOUR WORK HERE
lastModified = [NSDate date];
// NSData *response = [request responseData];//UTF-16LE
NSString* responseString = [[NSString alloc] initWithData:data encoding: NSUTF16LittleEndianStringEncoding];
DLog(#"string is: %#",responseString);
responseString = [responseString stringByReplacingOccurrencesOfString:#"ISO-8859-1" withString:#"UTF16-LE"];
NSData* data = [responseString dataUsingEncoding:NSUTF16LittleEndianStringEncoding];
return [self parseNamesFromXML:data];
}
else if ([data length] == 0 && error == nil)
{
NSLog(#"Nothing was downloaded.");
}
else if (error != nil){
NSLog(#"Error = %#", error);
}
}];
return nil;
}
However, I'm getting the error "Incompatible block pointer types sending NSArray to parameter of type void. And also "Control may reach end of non-void block."
How can I get this to work?
Make return type of your method as void. Dont return anything. Just call [self parseNamesFromXML:data]; method.
-(void)readUTF16LEFeed:(NSURL*) urlToRead{
NSURLRequest *request = [NSURLRequest requestWithURL:urlToRead];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error ) {
if ([data length] >0 && error == nil)
{
// DO YOUR WORK HERE
lastModified = [NSDate date];
// NSData *response = [request responseData];//UTF-16LE
NSString* responseString = [[NSString alloc] initWithData:data encoding: NSUTF16LittleEndianStringEncoding];
DLog(#"string is: %#",responseString);
responseString = [responseString stringByReplacingOccurrencesOfString:#"ISO-8859-1" withString:#"UTF16-LE"];
NSData* data = [responseString dataUsingEncoding:NSUTF16LittleEndianStringEncoding];
[self parseNamesFromXML:data];
}
else if ([data length] == 0 && error == nil)
{
NSLog(#"Nothing was downloaded.");
}
else if (error != nil){
NSLog(#"Error = %#", error);
}
}];
}
In parseNamesFromXML method, process your results and assign results array to a property. You can access it where ever you want.
-(void) parseNamesFromXML:(NSData *)xmlData
{
NSError *error;
//DLog(#"data is %#", [[NSString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding ]);
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];
if (doc)
{
self.dataArray = [self parseXmlDoc:doc];
}
}
The completion block is called asynchronously, so by the time your code reaches return [self parseNamesFromXML:data]; the methods scope is already done (meaning the method returned nil.
Try using [NSURLConnection sendSynchronousRequest:returningResponse:error:] instead, since the original code is also synchronous.
Edit:
As Julian Król suggested, if you return something within a block, it will be counted as the return value of this block, not of the original method. But since the block does not have a return value, you'll get the compiler error.
Related
Code:
+(id)loadJSONDataFromURL:(NSString *)urlString{
MsgsHelper *msg=[[MsgsHelper alloc]init];
// This function takes the URL of a web service, calls it, and either returns "nil", or a NSDictionary,
// describing the JSON data that was returned.
NSError *error;
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
// Call the web service, and (if it's successful) store the raw data that it returns
NSURLResponse *response = nil; //
NSData *data = [ NSURLConnection sendSynchronousRequest:request returningResponse: &response error:&error ];
//here we get the respond from NSURLResponse and then we check for the statusCode //1
//200 is ok,, 0 is no internet connection else is server error //2
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;//1
//any warning like this one with integers shuold add the casting (int)
int statCode = (int)[httpResponse statusCode];//2
if(statCode == 200){
if (!data)
{
//NSLog(#"Download Error: %#", error.localizedDescription);
return nil;
}
// Parse the (binary) JSON data from the web service into an NSDictionary object
id dictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if (dictionary == nil) {
//NSLog(#"JSON Error: %#", error);
return nil;
}
return dictionary;
}else if(statCode == 0){
[msg alertStatus:NSLocalizedString(#"No internet", #"Message") :#"" :0 ];
return nil;
}else{
//Server Error
//NSLog(#"Server Error");
[msg alertStatus:NSLocalizedString(#"Server Error", #"Message") :#"" :0 ];
return nil;
}}
the problem on result as below:
results: (
{
"Name" = "\U0633\U0639\U0648\U062f \U0639\U0628\U062f\U0627\U0644\U0639\U0632\U064a\U0632 \U064a\U0646 \U062c\U062f\U064a\U062f";
"location" = CENTER;
}
)
maybe need to dataUsingEncoding to read arabic string, but how do it.
Thanks,
i am using Gdata and already suceessfully login gmail and call below method
- (void)viewController:(GTMOAuth2ViewControllerTouch *)viewController
finishedWithAuth:(GTMOAuth2Authentication *)auth{
if (error != nil) {
}
else{
// i got successful login here
self.auth=auth;
}
}
in above method i got Authentifacation token and etc.
Now
NSString *urlStr = #"https://www.google.com/m8/feeds/contacts/default/full";
NSURL *url = [NSURL URLWithString:urlStr];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[self.auth authorizeRequest:request
completionHandler:^(NSError *error) {
NSString *output = nil;
if (error) {
output = [error description];
} else {
// Synchronous fetches like this are a really bad idea in Cocoa applications
//
// For a very easy async alternative, we could use GTMHTTPFetcher
NSURLResponse *response = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (data) {
// API fetch succeeded
output = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
} else {
// fetch failed
output = [error description];
}
}
}];
but i got error 401
please helpful me
I have a NSURLConnection (two of them), and they're running in the wrong order.
Here's my method:
- (void)loginToMistarWithPin:(NSString *)pin password:(NSString *)password {
NSURL *url = [NSURL URLWithString:#"https://mistar.oakland.k12.mi.us/novi/StudentPortal/Home/Login"];
//Create and send request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:#"POST"];
NSString *postString = [NSString stringWithFormat:#"Pin=%#&Password=%#",
[self percentEscapeString:pin],
[self percentEscapeString:password]];
NSData * postBody = [postString dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:postBody];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
// do whatever with the data...and errors
if ([data length] > 0 && error == nil) {
NSError *parseError;
NSDictionary *responseJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
if (responseJSON) {
// the response was JSON and we successfully decoded it
NSLog(#"Response was = %#", responseJSON);
} else {
// the response was not JSON, so let's see what it was so we can diagnose the issue
NSString *loggedInPage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Response was not JSON (from login), it was = %#", loggedInPage);
}
}
else {
NSLog(#"error: %#", error);
}
}];
//Now redirect to assignments page
NSURL *homeURL = [NSURL URLWithString:#"https://mistar.oakland.k12.mi.us/novi/StudentPortal/Home/PortalMainPage"];
NSMutableURLRequest *requestHome = [[NSMutableURLRequest alloc] initWithURL:homeURL];
[request setHTTPMethod:#"POST"];
[NSURLConnection sendAsynchronousRequest:requestHome queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *homeResponse, NSData *homeData, NSError *homeError)
{
// do whatever with the data...and errors
if ([homeData length] > 0 && homeError == nil) {
NSError *parseError;
NSDictionary *responseJSON = [NSJSONSerialization JSONObjectWithData:homeData options:0 error:&parseError];
if (responseJSON) {
// the response was JSON and we successfully decoded it
NSLog(#"Response was = %#", responseJSON);
} else {
// the response was not JSON, so let's see what it was so we can diagnose the issue
NSString *homePage = [[NSString alloc] initWithData:homeData encoding:NSUTF8StringEncoding];
NSLog(#"Response was not JSON (from home), it was = %#", homePage);
}
}
else {
NSLog(#"error: %#", homeError);
}
}];
}
- (NSString *)percentEscapeString:(NSString *)string
{
NSString *result = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)string,
(CFStringRef)#" ",
(CFStringRef)#":/?#!$&'()*+,;=",
kCFStringEncodingUTF8));
return [result stringByReplacingOccurrencesOfString:#" " withString:#"+"];
}
So, it's two NSURLConnection's that are added to the [NSOperationQueue mainQueue]. What my output is showing me is that the second NSURLConnection is running before the first one. So it tries to go to the page where I download data before I'm logged in, so it (obviously) returns a "You're not logged in" error.
How do I schedule them one after another?
The issue, as I suspect you have realized, is that you're doing asynchronous network requests (which is good; you don't want to block the main queue), so there's no assurance of the order they'll finish.
The quickest and easiest answer is to simply put the call to the second request inside the completion block of the first one, not after it. You don't want to be making that second one unless the first one succeeded anyway.
To keep your code from getting unwieldy, separate the login from the request for main page. And you can use the completion block pattern which is common with asynchronous methods. You add a parameter to loginToMistarWithPin that specifies what it should do when the request finishes. You might have one completion block handler for success, and one for failure:
- (void)loginToMistarWithPin:(NSString *)pin password:(NSString *)password success:(void (^)(void))successHandler failure:(void (^)(void))failureHandler {
NSURL *url = [NSURL URLWithString:#"https://mistar.oakland.k12.mi.us/novi/StudentPortal/Home/Login"];
//Create and send request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:#"POST"];
NSString *postString = [NSString stringWithFormat:#"Pin=%#&Password=%#",
[self percentEscapeString:pin],
[self percentEscapeString:password]];
NSData * postBody = [postString dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:postBody];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
// do whatever with the data...and errors
if ([data length] > 0 && error == nil) {
NSError *parseError;
NSDictionary *responseJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
if (responseJSON) {
// the response was JSON and we successfully decoded it
NSLog(#"Response was = %#", responseJSON);
// assuming you validated that everything was successful, call the success block
if (successHandler)
successHandler();
} else {
// the response was not JSON, so let's see what it was so we can diagnose the issue
NSString *loggedInPage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Response was not JSON (from login), it was = %#", loggedInPage);
if (failureHandler)
failureHandler();
}
}
else {
NSLog(#"error: %#", error);
if (failureHandler)
failureHandler();
}
}];
}
- (void)requestMainPage {
//Now redirect to assignments page
NSURL *homeURL = [NSURL URLWithString:#"https://mistar.oakland.k12.mi.us/novi/StudentPortal/Home/PortalMainPage"];
NSMutableURLRequest *requestHome = [[NSMutableURLRequest alloc] initWithURL:homeURL];
[requestHome setHTTPMethod:#"GET"]; // this looks like GET request, not POST
[NSURLConnection sendAsynchronousRequest:requestHome queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *homeResponse, NSData *homeData, NSError *homeError)
{
// do whatever with the data...and errors
if ([homeData length] > 0 && homeError == nil) {
NSError *parseError;
NSDictionary *responseJSON = [NSJSONSerialization JSONObjectWithData:homeData options:0 error:&parseError];
if (responseJSON) {
// the response was JSON and we successfully decoded it
NSLog(#"Response was = %#", responseJSON);
} else {
// the response was not JSON, so let's see what it was so we can diagnose the issue
NSString *homePage = [[NSString alloc] initWithData:homeData encoding:NSUTF8StringEncoding];
NSLog(#"Response was not JSON (from home), it was = %#", homePage);
}
}
else {
NSLog(#"error: %#", homeError);
}
}];
}
Then, when you want to login, you can do something like:
[self loginToMistarWithPin:#"1234" password:#"pass" success:^{
[self requestMainPage];
} failure:^{
NSLog(#"login failed");
}];
Now, change those successHandler and failureHandler block parameters to include whatever data you need to pass back, but hopefully it illustrates the idea. Keep your methods short and tight, and use completion block parameters to specify what an asynchronous method should do when it's done.
Can you check the below link. It is about forcing one operation to wait for another.
NSOperation - Forcing an operation to wait others dynamically
Hope this helps.
I have a code block for my NSURLConnection sendAsynchronousRequest, and when I try to set a UILabel's text, the text is never set, even though the values are there. Here's my code:
NSString *address = [addresses objectAtIndex:indexPath.row];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://myurl.com/%#", address]]];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ([data length] > 0 && error == nil)
{
NSString *dataOfData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if(![dataOfData isEqualToString:#"ERROR: address invalid"]) {
[balanceLabel setText:[NSString stringWithFormat:#"Balance: %#", dataOfData]];
if(data) {
qrCodeButton.alpha = 1;
}
} else {
errorLabel.text = #"This address is invalid.";
}
}
else if ([data length] == 0 && error == nil)
{
NSLog(#"Nothing was downloaded.");
[balanceLabel setText:#"Server Error, Please Try Again"];
}
else if (error != nil){
NSLog(#"Error = %#", error);
}
}];
Why is the UILabel's text never set? Is there a limitation to code blocks? If so, how would I fix my problem? Cheers!
It is because an NSOperationQueue is not the main thread. What you're doing is illegal. And the sad thing is that there is no need for it! Just say:
[NSURLConnection sendAsynchronousRequest:urlRequest
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// ... and the rest exactly as you have it now
All fixed. Your request is asynchronous on a background thread, but when it comes back to you on the completion handler, you'll be on the main thread and able to talk to the interface etc.
Your code operates UI element should execute on main thread.
dispatch_async(dispatch_get_main_queue(), ^{
if ([data length] > 0 && error == nil)
{
NSString *dataOfData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if(![dataOfData isEqualToString:#"ERROR: address invalid"]) {
[balanceLabel setText:[NSString stringWithFormat:#"Balance: %#", dataOfData]];
if(data) {
qrCodeButton.alpha = 1;
}
} else {
errorLabel.text = #"This address is invalid.";
}
}
else if ([data length] == 0 && error == nil)
{
NSLog(#"Nothing was downloaded.");
[balanceLabel setText:#"Server Error, Please Try Again"];
}
else if (error != nil){
NSLog(#"Error = %#", error);
}
}) ;
Make sure errorLabel is not nil and the UILabel is visible (It is added in the view hierarchy and its frame is appropriate).
I get an self.usersArray with 2 elements in the format:
(
{
userCreated = "2012-01-05 12:27:22";
username = Simulator;
},
{
userCreated = "2013-01-01 14:27:22";
username = "joey ";
}
)
This is gotten in a completion block after which I call another method to fetch points for these 2 users through a helper class:
-(void)getPoints{
self.usersPointsArray = [[NSMutableArray alloc] init];
for (NSDictionary *usersDictionary in self.usersArray) {
[SantiappsHelper fetchPointsForUser:[usersDictionary objectForKey:#"username"] WithCompletionHandler:^(NSArray *points){
if ([points count] > 0) {
[self.usersPointsArray addObject:[points objectAtIndex:0]];
}
NSLog(#"self.usersPointsArray %#", self.usersPointsArray);
}];
}
}
The final self.usersPointsArray log looks like:
(
{
PUNTOS = 5;
username = Simulator;
},
{
PUNTOS = 2;
username = joey;
}
)
But the problem is that the way the call for points is structured, the self.usersPointsArray is returned twice, each time with an additional object, due to the for loop, I know.
Here is the Helper class method:
+(void)fetchPointsForUser:(NSString*)usuario WithCompletionHandler:(Handler2)handler{
NSURL *url = [NSURL URLWithString:#"http://myserver.com/myapp/readpoints.php"];
NSDictionary *postDict = [NSDictionary dictionaryWithObjectsAndKeys:usuario, #"userNa", nil];
NSData *postData = [self encodeDictionary:postDict];
// Create the request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:[NSString stringWithFormat:#"%d", postData.length] forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
__block NSArray *pointsArray = [[NSArray alloc] init];
dispatch_async(dispatch_get_main_queue(), ^{
// Peform the request
NSURLResponse *response;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error) {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
NSLog(#"HTTP Error: %d %#", httpResponse.statusCode, error);
return;
}
return;
}
NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
pointsArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];
if (handler)
handler(pointsArray);
});
}
I cannot use the self.usersPointsArray with the initial objects, only with the finalized object. It wont always be 2 elements, i actually dont know how many it will be.
What would be the way to structure it so I get a final call when the self.usersPointsArray is complete and then I reload my tableview?
I think of your problem as a standard consumer-producer problem. You can create a queue count for the amount of items that will be processed (int totalToProcess=self.usersArray.count). Each time the completion handler is hit, it will do totalToProcess--. When totalToProcess reaches 0 you have processed all of the elements in your queue and can refresh your table.
If I understand your question correctly I believe this solves your problem. If not, hopefully I can with a bit more information.