controlling order of received data in NSURLConnection? - iphone

I have an array of URLs which I use to receive associated Images from remote server using NSURLConnection
NSURL *url = [NSURL URLWithString:URLString];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest
requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:2.0f];
// Run network request asynchronously
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
in the delegate protocol
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// As chuncks of the image are received, we build our data file
[self.imageData appendData:data];
}
and finally
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// All data has been downloaded, now we can set the image in the images array
_img = [UIImage imageWithData:self.imageData];
[self.imagesArray insertObject:_img atIndex:_index];
[self.imageData setLength:0];
_index = _index+ 1;
}
but the order of received is not always the same as the order of sending URL request I tried using an instance variable index to insert these images in the same order in the images array but that does not always work and I still am getting unordered listings , could anyone point me in the right direction as to how to achieve this.

What I've done in my own implementations is to create a custom Objective-C object which I insert into a mutable array (let's call it a "RemoteImageObject") which contains both the image data and the URL from which it came from.
What you would do is make the request, create a RemoteImageObject and add the URL to it, add that to your array.
When the request comes back with image data. Go look up your RemoteImageObject in your array and add the image data to it.
And when you want to display the image data, just fetch the imageData for each of those RemoteImageObjects.
Makes sense?

In general if I am downloading images I rely on the excellent AFNetworking classes.
http://afnetworking.com This provides the overall behavior you are looking for (one takes a UIImageView, and default image, another will just deliver the image bytes)
If there is a reason that you MUST make your own, then I would have a dictionary of download name to NSURLConnection. This will allow you to then figure out which download has ASYNCHRONOUSLY completed, and act correctly on it.
If you really mean a SYNCHRONOUS delivery of download objects, then dispatch them one at a time, wait for the response, and then start the next on completion.

Related

How to upload NSData Object to server with specific name?

How to upload NSData Object to server with specific name ?
NSData *imgData; // Image Data
NSString *imgName = #"myfile.png";
I want to upload data to server with specific file name.
1. Upload Data (imgData)
2. To server (my_server.com/upload.html)
3. With file name (imgName)
I am currently working on a school project where I needed to do something similar to this...
I was placing longitude and latitude values in a database.
MAKE NOTE THAT THERE MAY BE MUCH SIMPLER WAYS TO DO THIS. I AM BY NO MEANS A "PRO" AT OBJECTIVE-C PROGRAMMING
There are going to be three things you need.
a server in which you have access to.
a PHP file that will write the data to the database.
an NSURLConnection object;
I had placed the following code in one of the delegate methods for CLLocationManager.
The NSURLConnection object should look something like this:
//this is where I put the url for my PHP file
url = [[NSURL alloc] initWithString:[NSString stringWithFormat:#"http://www.example.com/folder/example.php"]];
urlRequest = [NSURLRequest requestWithURL:url];
connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
Of course the variables: url, urlRequest, and connection are declared in the header file like this
NSURL *url;
NSURLRequest *urlRequest;
NSMutableData *filedata;
NSURLConnection *connection;

ASIHTTPRequest Send Data With Each Request

I have an array which includes URLs of JSON feeds. I am using ASIHTTPRequest to download the feed and process it. Each feed contains several JSON entries or objects. The request downloads the data and selects only one object and stores it.
The feeds URLs look like this: http:www.*.com/id.json, where id is some string. After downloading the data and selecting the object, I'd like to store the id in a dictionary as a key that maps to a value of the object downloaded.
How can I pass that string with the request? So for example:
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
request.tag = 3;
[request setDelegate:self];
[request startAsynchronous];
Now in requestFinished, I can identify that request as follows: if (request.tag == 3. Along with tag of 3, I'd like to send the ID. So I can do something with it in if (request.tag == 3). Is there some property where I can pass a string or any data along with a request?
You can pass your own dictionary of data in the userInfo property, which, like the tag property, can be read back on the request after receiving the response.
NSString* jsonId = #"1234";
request.userInfo = [NSDictionary dictionaryWithObject:jsonId forKey:#"id"];
See documentation.
If you need to handle success and failure on many different types of
request, you have several options:
If your requests are all of the same broad type, but you want to
distinguish between them, you can set the userInfo NSDictionary
property of each request with your own custom data that you can read
in your finished / failed delegate methods. For simpler cases, you can
set the request’s tag property instead. Both of these properties are
for your own use, and are not sent to the server.
If you need to handle success and failure in a completely
different way for each request, set a different setDidFinishSelector /
setDidFailSelector for each request
If you want to post data like a web page posts a form, you can use the ASIFormDataRequest subclass. It makes it very easy to send POST requests with strings you add individually:
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:#"Ben" forKey:#"first_name"];
[request setPostValue:#"Copsey" forKey:#"last_name"];
See the documentation.

How to keep a connection alive using ASIHTTP? I need to make multiple requests, but can't afford to open, then close a request connection

I have a method that I call to make web service requests using GET. It looks like this:
- (UIImage*)getImageWithSeriesGUID:(NSString*)seriesGUID ImageID:(int)imageID {
NSString * unescapedString = RIVERWOODS_GET_IMAGE(seriesGUID, imageID);
NSURL *url = [[NSURL alloc] initWithString:[unescapedString stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setRequestMethod:#"GET"];
[request addRequestHeader:#"Connection" value:#"Keep-Alive"];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
NSData *response = [request responseData];
//NSLog(#"Size: %#",[response length]);
NSString *content = [[[NSString alloc] initWithData:response
encoding:NSUTF8StringEncoding] autorelease];
NSLog(#"Data: %#", content);
UIImage *image = [UIImage imageWithData:response];
return image;
}
return nil;
}
This approach works ok, but it is just REALLY slowly. On the other end I am iterating through a for loop so this method gets called 20 times if the picture array I need to collect has 20 images. I am looking to improve the efficiency of this process, and I am thinking that I should be able to iterate through all the image Id's I need to collect right here in this method.
It seems to me that the reason this goes so slowly is because the multiple requests that we keep opening and closing. THe images I am pulling in are on average ~15kb.
My question: What can I do to change this method around to make it so I can improve efficiency and take advantage of the HTTP keep-alive features? I am thinking that instead of passing in an image ID, I can just pass in the count of the array I need to make, and then setup a for-loop of some sorts here in the request method which would then allow me to pass back an array of images...
Is that the way to go? Is there something I am missing? I appreciate your help and input!!
The reason why this is slow as hell is that you're doing the requests synchronously (which is always a no-no anyway), one-by-one. You need to refactor your download method to work asynchronously, and concurrently.
My approach to requesting data on the wire in that manner is as follows:
Create a global network connection 'controller' (accessible from your App Delegate), which can create an ASINetworkQueue on the fly when required and release it when no requests remain
Wrap your requests into a subclass of ASIHTTPRequest, and override the done/fail methods in those subclasses (make them fire a notification with returned data if you like; or write to disk and update a db with their reference).
For every request, grab the queue reference, and add your request to the queue.
The queue will grow and shrink as needed
If I were at my computer I'd check into github an example of this, but really the only difficult part is the global connection manager, and the ASI* guys have written a great example here on gist.github. Also, a better explanation of the above (where I learnt it from) is here.

How can I validate an image file I download from the Web on iPhone?

I am downloading images using the NSURLConnection sendSynchronousRequest method and that works fine. However, occasionally I run into an issue where the image URL points to something other than an image file. For example, I found this non-image URL was causing issues: http://www.100plusposters.com/images/ExoticFlowers.jpg The URL returns a Web page, which I assume occurs because the image is missing from the site.
One nice thing about Objective-C is that the invalid image doesn't cause a crash. It simply and quietly continues along and just doesn't display any image, but that is still a problem.
How can I validate the data returned to ensure it is a valid image file before displaying it?
Thanks!
My relevant code, in case that helps...
NSError *error = nil;
NSURLResponse *response;
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:5];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if(data != nil && [error localizedDescription] == nil)
{
//Create UIImage object using initWithData method
//Display UIImage
}
Looks like NSURLResponse object contains a MIMEType property. That should give you a pretty good idea what the type of the returned data is.
Could we take this question one step further? What if the image data is incomplete or corrupt from the server? The complete transport works, the MIME type is image/jpeg, the UIImage is constructed and non-nil but the renderer discovers inconsistencies in the data and the log may show "Bad Huffman code" or "premature end of data segment"
How would I capture this error before tossing the UIImage into the view context and thereby getting a not-pretty image on the screen?
I think by crosschecking the content length obtained from the header of the Http Request and finally recieved data would give you, whether the data downloaded was complete or not.
About the corrupt i dont have much info.

Creating a highscore like system, iPhone side

I'm sorry for opening a new question, I had to - as I wrote the other question from my iPhone as unregistered user and it is not very comfortable to write from the iPhone.
Rephrasing the question:
Is it possible to use the:
[NSMutableArray writeToURL:(NSString *)path atomically:(BOOL)AuxSomething];
In order to send a file (NSMutableArray) XML file to a url, and update the url to contain that file?
for example:
I have an array and I want to upload it to a specific URL and the next time the app launches I want to download that array.
NSMutableArray *arrayToWrite = [[NSMutableArray alloc] initWithObjects:#"One",#"Two",nil];
[arrayToWrite writeToURL:
[NSURL urlWithString:#"mywebsite.atwebpages.com/myArray.plist"] atomically:YES];
And at runtime:
NSMutableArray *arrayToRead =
[[NSMutableArray alloc] initWithContentsOfURL:[NSURL urlWithString:#"mywebsite.atwebpages.com/myArray.plist"]];
Meaning, I want to write an NSMutableArray to a URL, which is on a web hosting service (e.g. batcave.net), the URL receives the information and updates server sided files accordingly.
A highscore like setup, user sends his scores, the server updates it's files, other users download the highscores at runtime.
I hope this is clarified.
Edit: What I am looking for is scripting PHP or ASP so the website, the URL where the data is sent to would know how to handle it. I want an example or a tutorial on how to implement this scripting for handling data, if it's possible to do this on a web hosting service.
~Thanks in advance.
To answer the question "How do I create a high score like system?", there are multiple parts of the system:
You need an ID for each user (a GUID generated on the iPhone, together with the users name should be sufficient).
You need a server that: remembers high scores; receives high scores from users; either displays (on a web site) the high scores and/or makes the high scores available for download to the phone.
You need some fraud protection, although that is likely fighting a losing battle against jailbreakers.
On the iPhone app side, you might want to be able to download the current high scores for display, which is done easily enough with something like:
int statusCode = 0;
NSData* result = nil;
NSHTTPURLResponse* response = nil;
NSError* error = nil;
NSString* url = #"http://www.yourserver.com/highscores.php"; // returns XML plist data
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:180];
result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// NSLog( #"NSURLConnection result %d %# %#", [response statusCode], [request description], [error description] );
statusCode = [response statusCode];
if ( (statusCode == 0) || (!result && statusCode == 200) ) {
statusCode = 500;
}
Since it is synchronous, you might want to put it inside an NSOperation. Alternatively, you can use
+ (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id)delegate
To send high score data, because it is so small, the easiest way is simply to encode it in the URL.
NSString* url = [NSString stringWithFormat:#"http://www.yourserver.com/sethighscores.php?uid=%#;name=%#;score=%d;antifraud=%#", [uid encodeParameter], [name encodeParameter], score, [secureHash encodeParameter]];
Where encodeParameter is a custom category on NSString that encodes URL parameters and secureHash is a string representing a one way secure hashing of the uid, name, score and some secret known to your iPhone app and your web site. You'll need to figure these out on your own or ask separate questions since this is already getting long.
According to NSData writeToURL docs (at least for iPhone OS 2.2.1):
"Since at present only file:// URLs are supported, there is no difference between this method and writeToFile:atomically:, except for the type of the first argument."
Although the docs for NSArray/NSDictionary/NSString do not specifically mention the restriction, it would seem highly likely that the same restriction applies.
So you will have to upload the XML using some other mechanism.
Also, web sites generally are read only, unless you provide specific code on the web server to support uploading.