Add UIImage to NSMutableArray through Delegate Method S3 - iphone

I'm trying to get AWS S3 to go get images from an S3 bucket, the code below is in two parts. The first (I think) creates the get object request after initiating the array to put the object in (arrayImages, it gets the count from arrayPointer). The second is the delegate method that puts the object (data) in arrayImages once it finishes. I would assume that each time the delegate method is called it would add a new object. Something is wrong here:
-(void)gets3ImageArray
{
if (arrayImages == nil) {
arrayImages = [[NSMutableArray alloc] initWithCapacity:[arrayPointer count]];
}
else
{
[arrayImages removeAllObjects];
}
AmazonS3Client *s3 = [[AmazonS3Client alloc] initWithAccessKey:ACCESS_KEY_ID withSecretKey:SECRET_KEY];
for (NSString *name in arrayPointer){
#try {
S3GetObjectRequest *gor = [[S3GetObjectRequest alloc]initWithKey:name withBucket:[Constants pictureBucket]];
[gor setDelegate:self];
[s3 getObject:gor];
}
#catch (AmazonClientException *exception) {
[Constants showAlertMessage:exception.message withTitle:#"Download Error"];
}
}
}
-(void)request:(AmazonServiceRequest *)request didReceiveData:(NSData *)data
{
UIImage *myImage = [[UIImage alloc] initWithData:data];
if(!myImage)
{
NSLog(#"NO IMAGE");
[arrayImages addObject:[UIImage imageNamed:#"placeholder.png"]];
}
else
{
[arrayImages addObject:[UIImage imageWithData:data]];
}

you need to append the data received in your ...
-(void)request:(AmazonServiceRequest *)request didReceiveData:(NSData *)data
...method as it can be called multipled times. declare a NSMutableData *responseData in your header file and append the data until your didCompleteWithResponse delegate is called.
[responseData appendData:data];
Make sure you alloc the mutable data before you call the AmazonS3Client.
responseData = [[NSMutableData data] retain]; // or autorelease or not depending on ARC etc
You will likely need to implement all these delegates: (but the amazon docs should be able to confirm)
didReceiveResponse: Sent when body data has been read. May be called multiple times.
didReceiveData: Sent when body data has been read. May be called multiple times.
didCompleteWithResponse: Sent when the entire response has been read and processed. The object sent to this method is the same object returned when making a non-asynchronous request.
didSendData: Sent when the request transmitted data.
didFailWithError: Sent when there was a basic failure with the underlying connection.
didFailWithServiceException: Sent when the service responded with an error message.

Related

unable to parse JSON data from a NSURLConnection response

I am getting a server response of the form:
results are:{
AverageMark = 40;
"Grade A" = 10;
"Grade B" = 20;
"Grade C" = 30;
"Grade D" = 20;
MaxMark = 99;
MinMark = 44;
ProfileGrade = "";
ProfileMark = 1;
}
However I am unable to save the response data into an Array.
This is my code inside didReceiveResponse:
{
NSString *jsonString = [[NSString alloc] initWithString:responseData];
NSArray *jsonResults = [jsonString JSONValue];
NSLog(#"results are:%#",jsonResults); //this log is shown above
for (int i=0; i<[jsonResults count]; i++)
{
NSDictionary *AllData=(NSDictionary *)[jsonResults objectAtIndex:i]; //Program is crashing here--//
NSMutableArray *DataArray=[[NSMutableArray alloc]init];
NSString *avgMarkString;
avgMarkString=(NSString *)[AllData objectForKey:#"MaxMark"];
[DataArray addObject:avgMarkString];
}
}
I want to save the response data into the array called "DataArray". But the program is crashing.
What am I doing wrong?
You likely don't have the complete data yet in -connection:didReceiveResponse:. Create an instance variable or property of the type NSMutableData and initialize the data ivar or property in
-connection:didReceiveResponse: if you get a valid statusCode (between 200-299 should be ok). Use appendData: on the data object in the -connection:didReceiveData: delegate method. Finally in -connectionDidFinishLoading: the data is complete and can be parsed into JSON.
Alternatively you could just use the AFNetworking library. The library got some convenience methods for dealing with XML, JSON, images, etc...
Read the following page to get an introduction into the capabilities of AFNetworking: http://engineering.gowalla.com/2011/10/24/afnetworking/
Some example code from one of my own projects for downloading using a queue using NSURLConnectionDelegate methods. The URL Request objects are a custom subclass of NSURLConnection for some block "callbacks":
#pragma mark - URL connection delegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSRange range = NSMakeRange(200, 99);
if (NSLocationInRange(httpResponse.statusCode, range));
{
self.data = [[NSMutableData alloc] init];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// inform caller that download is complete, provide data ...
if (_request.completionHandler)
{
_request.completionHandler(_data, nil);
}
[self removeRequest:_request];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
DLog(#"%#", error);
// inform caller that download failed, provide error ...
if (_request.completionHandler)
{
_request.completionHandler(nil, error);
}
[self removeRequest:_request];
}
that isn't json, try having a look at this http://json.org/example.html
Given JSON response is invalidate. Validate your JSON response here.

Reading Certificates on iOS Problem

I am trying to read certificates from various URLs in iOS. My code however is not working well - the array that should return the information I need always returns null.
What am I missing?
- (void)findCertificate:(NSString *)url
{
NSInputStream*input = [[NSInputStream inputStreamWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"https://store.writeitstudios.com"]]] retain];
[input setDelegate:self];
[input scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[input open];
NSLog(#"Status: %i",[input streamStatus]);
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
NSLog(#"handle Event: %i",eventCode);
if (eventCode == NSStreamStatusOpen)
{
NSArray *certificates = (NSArray*)CFReadStreamCopyProperty((CFReadStreamRef)aStream, kCFStreamPropertySSLPeerCertificates);
NSLog(#"Certs: %#",CFReadStreamCopyProperty((CFReadStreamRef)aStream, kCFStreamPropertySSLPeerCertificates));
if ([certificates count] > 0) {
SecCertificateRef certificate = (SecCertificateRef)[certificates objectAtIndex:0];
NSString *description = (NSString*)SecCertificateCopySubjectSummary(certificate);
NSData *data = (NSData *)SecCertificateCopyData(certificate);
NSLog(#"Description: %#",description);
}
}
}
And yes, I am aware that I am leaking memory. This is just a snippet.
Let me explain what you're doing here and why it's wrong:
You are loading the contents of the URL https://store.writeitstudios.com (i.e. the HTML) synchronously into an NSData (a data buffer). Note that you are not loading any certificates (well, technically NSURL will load them internally, but this code is most definitely not putting them into the NSData)
You are opening an input stream and sticking the data (a bit of HTML, no certificates!) into it.
You have implemented NSStream's delegate method stream:handleEvent: and are attempting to read the kCFStreamPropertySSLPeerCertificates property. This property will be empty since the stream contains only a bit of HTML data, nothing else.
You are casting the empty property to an NSArray.
The loop is not executed because the array is NULL.
Using NSStream/CFStream is not necessary for the task at hand. And most definitely you don't have to go through NSURLConnection first and then through NSStream.
To retrieve SSL server certificates, stick to a simple, asynchronous NSURLConnection and use its delegate methods to access the certificates:
// Method to begin the asynchronous download
- (void)beginCertificateDownload:(NSURL *)url
{
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
}
// NSURLConnection Delegate Methods
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return [[protectionSpace authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
// extract the certificates
SecTrustRef trustRef = [[challenge protectionSpace] serverTrust];
CFIndex count = SecTrustGetCertificateCount(trustRef);
for (CFIndex i = 0; i < count; i++) {
SecCertificateRef certRef = SecTrustGetCertificateAtIndex(trustRef, i);
CFStringRef certSummary = SecCertificateCopySubjectSummary(certRef);
NSLog(#"%#", certSummary);
// do whatever you need with the certificates here
// don't forget to copy them if you need to keep them
// around beyond the scope of this method
}
// I'm assuming you're not interested in actually loading the contents of the URL, so cancel
[[challenge sender] cancelAuthenticationChallenge:challenge];
// you'll also want to release the connection object at some point
}

How to return an object from a class that uses NSURLConnection and it's delegate classes?

I'm in the process of trying to move code from a UITableViewController class to a "helper" class.
The code utilizes NSURLConnection to grab and parse JSON and then populate an NSMutableArray.
What I'd like to do is call a method in my helper class that returns a NSMutableArray. What I don't understand is how to return the array from the connectionDidFinishLoading delegate class of NSURLConnection (where the array is actually built) as though it was from the originally called method that started the connection. In other words, how does the method that calls NSURLConnection get control back so it can return a value from the whole operation?
Here are the relevant methods from the helper class. How do I get the getMovies method to return the listOfMovies that is built in the connectionDidFinishLoading delegate class?
-(NSMutableArray)getMovies:(NSURL*)url {
responseData = [[NSMutableData data] retain];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//NSURLRequest* request = [NSURLRequest requestWithURL: url cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval: 30.0];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
//TODO error handling for connection
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//---initialize the array---
listOfMovies = [[NSMutableArray alloc] init];
tmdbMovies = [[NSArray alloc] init];
posters = [[NSArray alloc] init];
thumbs = [[NSDictionary alloc] init];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
SBJsonParser *json = [[SBJsonParser new] autorelease];
tmdbMovies = [json objectWithString:responseString];
// loop through all the top level elements in JSON
for (id movie in tmdbMovies) {
// 0 - Name
// 1 - Meta
// 2 - Url
if ((NSNull *)[movie objectForKey:#"name"] != [NSNull null]) {
if (![[movie objectForKey:#"name"] isEqualToString:#""]) {
name = [movie objectForKey:#"name"];
}
}
if ((NSNull *)[movie objectForKey:#"info"] != [NSNull null]) {
if (![[movie objectForKey:#"info"] isEqualToString:#""]) {
meta = [movie objectForKey:#"info"];
}
}
if ((NSNull *)[movie objectForKey:#"thumb"] != [NSNull null]) {
if (![[movie objectForKey:#"thumb"] isEqualToString:#""]) {
thumbUrl = [movie objectForKey:#"thumb"];
}
}
NSLog(#"Name: %#", name);
NSLog(#"Info: %#", meta);
NSLog(#"Thumb: %#", thumbUrl);
NSMutableArray *movieData = [[NSMutableArray alloc] initWithObjects:name,meta,thumbUrl,nil];
// add movieData array to listOfJMovies array
[listOfMovies addObject:movieData];
[movieData release];
}
//FIXME: Connection warning
if (connection!=nil) {
[connection release];
}
[responseData release];
[responseString release];
}
What you really need to do here is create a #protocol that creates a delegate for your helper class. Then change -(NSMutableArray)getMovies:(NSURL*)url to -(void)getMovies:(NSURL*)url
The class that is calling your helper method needs to implement your helper method's delegate.
Then - (void)connectionDidFinishLoading:(NSURLConnection *)connection calls the delegate method(s). It's best to have a one for success and one for failure.
=Update Begin=
You will need to also define an id delegate in your helper file which the calling class sets to self after init but before calling -(void)getMovies:(NSURL*)url. That way the helper file knows where to call back to.
getMovies *movieListCall = [[getMovies alloc] init];
movieListCall.delegate = self;
[movieListCall getMovies:<your NSURL goes here>];
You will see some additional lines for the inclusion of a delegate in both the getMovies.h and getMovies.m files.
=Update End=
in your getMovies.h file add:
#protocol getMoviesDelegate
#required
- (void)getMoviesSucceeded:(NSMutableArray *)movieArray;
- (void)getMoviesFailed:(NSString *)failedMessage;
#end
#interface getMovies : NSOBject {
id delegate;
}
#property (nonatomic, assign) id delegate;
in your getMovies.m file add:
#synthesize delegate;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
//TODO error handling for connection
if ([delegate respondsToSelector:#selector(getMoviesFailed:)]) {
[delegate getMoviesFailed:[error localizedDescription]];
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//finishes with
if ([delegate respondsToSelector:#selector(getMoviesSucceeded:)]) {
[delegate getMoviesSucceeded:listOfMovies];
}
}
update your calling class .h file to use getMoviesDelegate:
#interface MoviesView : UIViewController <getMoviesDelegate>{
.
.
.
}
add the getMoviesDelegate methods to your calling class .m file
- (void)getMoviesSucceeded:(NSMutableArray *)movieArray {
//deal with movieArray here
}
- (void)getMoviesFailed:(NSString *)failedMessage {
//deal with failure here
}
This is not tested but hopefully gives you a road map to work with.
Protocols are nice because you can make both required and optional delegate methods and it helps in refining your helper methods to become very reusable across projects. The compiler will also warn you if you have implemented a protocol but not implemented the protocol's required delegate methods. If you follow this path be sure to use conformsToProtocol: and respondsToSelector:
Fundamentally, what's happening is that you're starting an asynchronous network load (asynchronous is the right way to do this, almost assuredly), and then you need some way to resume whatever operation you were doing before the load began. You have a few options:
Create your own delegate protocol. Your UITableViewController would then set itself as the helper's delegate, and the helper would call helperDidLoad or whatever you named that method. There's more information on writing delegates in the Cocoa Programming Guide.
Use blocks and continuation passing style. This is a bit more advanced but I like it. In your UITableViewController you'd write something like this:
[helper doSomething:^ (id loaded) {
[modelObject refresh:loaded]; // or whatever you need to do
}];
And then in your helper you'd write:
- (void)doSomething:(void ^ (id))continuation {
_continuation = continuation;
//kick off network load
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
_continuation(_data);
}
Use notifications. Read the NSNotificationCenter docs.
Use KVO. The KVO programming guide has a lot of good info on Key-Value Observing.
How to i get the getMovies method to return the listOfMovies that is built in the connectionDidFinishLoading delegate class?
I'm going to argue that you should not do that.
Network requests should be made asynchronously. If your getMovies were to make a synchronous request and return only when it had data you would block that entire thread while you waiting for a network connection to finish. This is a bad idea in general and a terrible idea if your main thread is calling getMovies. Blocking the main thread will prevent you from responding to touches or updating the UI, your app will appear frozen, and the OS will terminate it if your users don't quit in frustration first.
Instead have the helper class notify the caller when data is available (or when it failed to retrieve data) through a delegate call back, notification, KVO, or whatever mechanism you prefer.
Here are the steps, pseudocode like style:
[helperInstance setDelegate:self]; // where self is your UITableViewController class
in your helper class, in the connectionDidFinishLoading do something like this:
[delegate finishedLoadingData:JSONData];
Also you can define a protocol for your delegate, and the declare the delegate like this in your helper class:
#property (nonatomic, assign) id<YourProtocol> delegate;
Hope this helps,
Moszi

Static variable for communication among like-typed objects

I have a method that asynchronously downloads images. If the images are related to an array of objects (a common use-case in the app I'm building), I want to cache them. The idea is, I pass in an index number (based on the indexPath.row of the table I'm making by way through), and I stash the image in a static NSMutableArray, keyed on the row of the table I'm dealing with.
Thusly:
#implementation ImageDownloader
...
#synthesize cacheIndex;
static NSMutableArray *imageCache;
-(void)startDownloadWithImageView:(UIImageView *)imageView andImageURL:(NSURL *)url withCacheIndex:(NSInteger)index
{
self.theImageView = imageView;
self.cacheIndex = index;
NSLog(#"Called to download %# for imageview %#", url, self.theImageView);
if ([imageCache objectAtIndex:index]) {
NSLog(#"We have this image cached--using that instead");
self.theImageView.image = [imageCache objectAtIndex:index];
return;
}
self.activeDownload = [NSMutableData data];
NSURLConnection *conn = [[NSURLConnection alloc]
initWithRequest:[NSURLRequest requestWithURL:url] delegate:self];
self.imageConnection = conn;
[conn release];
}
//build up the incoming data in self.activeDownload with calls to didReceiveData...
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Finished downloading.");
UIImage *image = [[UIImage alloc] initWithData:self.activeDownload];
self.theImageView.image = image;
NSLog(#"Caching %# for %d", self.theImageView.image, self.cacheIndex);
[imageCache insertObject:image atIndex:self.cacheIndex];
NSLog(#"Cache now has %d items", [imageCache count]);
[image release];
}
My index is getting through okay, I can see that by my NSLog output. But even after my insertObject: atIndex: call, [imageCache count] never leaves zero.
This is my first foray into static variables, so I presume I'm doing something wrong.
(The above code is heavily pruned to show only the main thing of what's going on, so bear that in mind as you look at it.)
You seem to never initialize the imageCache and probably got lucky with it having the value 0. The initialization would best be done in the class' initialization, e.g.:
#implementation ImageDownloader
// ...
+(void)initialize {
imageCache = [[NSMutableArray alloc] init];
}
// ...

iPhone NSURLConnection: connectionDidFinishLoading - how to return a string to the calling method

I have reviewed similar stackoverflow questions/answers to this but I am still stumped.
I'm a beginner and I'm really struggling with this. With the iPhone, I can download XML from a URL but I cannot store the result string in a NSString variable and see it from the calling function.
I have the following declarations in a custom made class:
#interface comms : NSObject {
NSString *currURL, *receivedString;
NSMutableData *receivedData;
NSURLConnection *conn;
}
#property (copy, readwrite) NSString *currURL;
#property (copy, readonly) NSString *receivedString;
#property (nonatomic, assign) NSMutableData *receivedData;
#property (nonatomic, assign) NSURLConnection *conn;
-(void) getContentURL;
I have the following method in the comms class:
-(void) getContentURL
{
NSLog( #"Begin getContentURL." );
// Request data related.
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init]
autorelease];
[request setURL:[NSURL URLWithString: currURL]];
// Content-Type related.
[request setValue:#"application/x-www-form-urlencoded"
forHTTPHeaderField:#"Content-Type"];
// Create Connection.
conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (conn) {
// The connection was established.
receivedData = [[NSMutableData data] retain];
NSLog( #"Data will be received from URL: %#", request.URL );
}
else
{
// The download could not be made.
NSLog( #"Data could not be received from: %#", request.URL );
}
// PROBLEM - receivedString is NULL here.
NSLog( #"From getContentURL: %#", receivedString );
}
I have created the required delegates in the comms class as per the following:
-(void)connection:(NSURLConnection *)connection didReceiveResponse:
(NSURLResponse *)response
{
// Discard all previously received data.
[receivedData setLength:0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:
(NSData *)data
{
// Append the new data to the receivedData.
[receivedData appendData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Connection succeeded in downloading the request.
NSLog( #"Succeeded! Received %d bytes of data", [receivedData length] );
// Convert received data into string.
receivedString = [[NSString alloc] initWithData:receivedData
encoding:NSASCIIStringEncoding];
NSLog( #"From connectionDidFinishLoading: %#", receivedString );
// release the connection, and the data object
[conn release];
[receivedData release];
}
I can successfully output the receivedString string using NSLog in the connectionDidFinishLoading delegate.
// Convert received data into string.
receivedString = [[NSString alloc] initWithData:receivedData
encoding:NSASCIIStringEncoding];
NSLog( #"From connectionDidFinishLoading: %#", receivedString );
However, when I output the receivedString string in the getContentURL it's null (and hence is also null from the ViewController.m class which I call the comms class from).
// PROBLEM - receivedString is NULL here.
NSLog( #"From getContentURL: %#", receivedString );
Any ideas on how I can see the value of receivedString in getContentURL and from the ViewController.m class?
NSURLConnection is an asynchronous API. When you start the request, the object will spawn a new thread, and only update your main one via the callback/delegate methods. Your current method will return most likely before the request is finished, and so the string of the result will not have downloaded yet!
If you want to do this synchronously, you will have two options:
Use the built in synchronous download method. Note that as this blocks, it will not allow the user to interact with the UI.
Use the C functions CFRunLoopRun() and CFRunLoopStop() to start the run loop inside your calling function, wait until the download is complete or failed, then return control back to the calling method with CFRunLoopStop().
To return data from the delegates method, please use Code Block.
See Apple's Blocks documentation.