NSURLConnection and JSON Data - iphone

I am stuck with something crazy. I used ASIHTTPRequest to receive my data from a web service and everything worked fine. I switched to using a NSURLConnection and I am receiving the same data and parsing it the same way but my code won't recognize the data with the NSURLConnection.
Here is the data I am receiving (from NSLog)
Did receive data: {"d":"[{\"id\":1.0,\"Category\":1,\"hPlan\":0.0,\"Tip\":\"It takes 3500
calories to gain a pound. If you want to lose a pound per week, reduce your calorie
intake by 250 calories and incorporate daily physical activity that will burn 250
calories.\",\"TipDate\":\"2012-05-12T00:00:00\",\"TimeStamp\":\"AAAAAAAAB9I=\"}]"}
2012-06-06 09:42:11.809 StaticTable[27488:f803] Jsson Array: 0
2012-06-06 09:42:11.809 StaticTable[27488:f803] Jsson Array: (null)
Code:
#import "UYLFirstViewController.h"
#import "MBProgressHUD.h"
#import "JSON.h"
#interface UYLFirstViewController ()
#end
#implementation UYLFirstViewController
#pragma mark -
#pragma mark === UIViewController ===
#pragma mark -
#synthesize MessageField;
#synthesize jsonArray = _jsonArray;
#synthesize TipLabelField;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = NSLocalizedString(#"Tickle!", #"Tickle!");
self.tabBarItem.image = [UIImage imageNamed:#"heart_plus"];
[self GetTipOfDay];
}
return self;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
-(BOOL)GetTipOfDay{
NSDate *date = [NSDate date];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc]init];
[dateFormat setDateFormat:#"EEEE MMMM d, YYYY"];
NSString *dateString = [dateFormat stringFromDate:date];
NSString *yourOriginalString = #"Tip of the Day for ";
yourOriginalString = [yourOriginalString stringByAppendingString:dateString];
TipLabelField.text = yourOriginalString;
NSURL *url = [NSURL URLWithString:#"http://www.mysite.com/api/GetHealth.asmx/getTipOfDay"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[NSURLConnection connectionWithRequest:request delegate:self];
// Clear text field
MessageField.text = #"";
// Start hud
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Gathering Tip of the Day...";
return TRUE;
}
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[MBProgressHUD hideHUDForView:self.view animated:YES];
NSLog(#"Did receive data: %#", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
NSDictionary *responseDict = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] JSONValue];
NSString *jsonResponse = [responseDict objectForKey:#"d"];
self.jsonArray = [jsonResponse JSONValue];
NSLog(#"Jsson Array: %d", [jsonArray count]);
NSLog(#"Jsson Array: %#", jsonArray);
NSEnumerator *myEnumerator;
myEnumerator = [jsonArray objectEnumerator];
int i;
i=0;
id myObject;
while (myObject = [myEnumerator nextObject])
{
NSDictionary *itemAtIndex = (NSDictionary *)[self.jsonArray objectAtIndex:i];
NSLog(#"Checking for games");
NSString *myCheck = [itemAtIndex objectForKey:#"FName"];
if ([myCheck length] != 0)
{
// NSLog(myCheck);
MessageField.text = myCheck;
}
}
}
- (void)viewDidUnload {
[self setMessageField:nil];
[self setTipLabelField:nil];
[super viewDidUnload];
}
#end
#import <UIKit/UIKit.h>
#interface UYLFirstViewController : UIViewController{
NSMutableArray *jsonArray;
}
#property (weak, nonatomic) IBOutlet UILabel *MessageField;
#property (weak, nonatomic) NSMutableArray *jsonArray;
#property (weak, nonatomic) IBOutlet UILabel *TipLabelField;
-(BOOL)GetTipOfDay;
#end

-didRecieveData can be called multiple times as the bytes and chunks come in. You should move your logic to -connectionDidFinishLoading. This will let you know when the connection is completely done and the data is ready to be parsed.

You're only implementing one of the NSURLConnectionDelegate methods. Try adding this
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
//set up *receivedMutableString as instance variable in .h
if (!receivedMutableString) {
self.receivedMutableString = [[NSMutableString alloc] initWithData:data encoding:NSUTF8StringEncoding];
} else {
NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[receivedMutableString appendString:dataString];
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//Now receivedMutableString contains all json data
...continue with your code
}

NSURLConnection is a bit of overkill if you're just doing a simple GET request (and you're developing for an iOS version that supports blocks). You can do this in a dispatch_async block:
- (void) getData
{
dispatch_async(<some_queue>, ^{
NSError * error = nil;
NSString * response = [NSString stringWithContentsOfURL: stringWithContentsOfURL: requestUrl error: &error];
// process JSON
dispatch_async(dispatch_get_main_queue(), ^{
// Update UI on main thread
}
});
}
As you can see from my example code, you can also perform your JSON processing on the background queue (provided the method you're calling is thread safe). Just pass back to the main queue to update the UI.

Seems like the issue had nothing to do with fetching from the webservice. I had to define my array as __strong. Thanks for all the help. I did get some good ideas on how to do things better.

Related

Reading JSON from URL and adding MKAnnotations

I've been through several different tutorials trying to get this working, but they seem to gloss over some crucial steps that a beginner might not know.
I have a JSON file at a URL with name, latitude, and longitude listed. How can I import that to an array or dictionary (I don't know the difference), and then iterate over it and create a new annotation with each iteration.
IOS6, Storyboards
_ Added Code _
ViewController.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#interface ViewController : UIViewController {}
#property (weak, nonatomic) IBOutlet MKMapView *mapView;
#property (nonatomic, strong) NSMutableData *downloadData;
#end
ViewController.m
#import "ViewController.h"
#import "MapViewAnnotation.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
_downloadData = [NSMutableData new];
NSURL *requestURL = [NSURL URLWithString:#"OMITTED/apptest/locations.json"];
NSURLRequest *request = [NSURLRequest requestWithURL:requestURL];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
[connection start];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_downloadData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
id parsed = [NSJSONSerialization JSONObjectWithData:_downloadData options:kNilOptions error:nil];
for (NSDictionary *pointInfo in parsed)
{
NSLog([parsed objectForKey:#"name"]);
double xCoord = [(NSNumber*)[parsed objectForKey:#"lat"] doubleValue];
double yCoord = [(NSNumber*)[parsed objectForKey:#"lon"] doubleValue];
CLLocationCoordinate2D coords = CLLocationCoordinate2DMake(xCoord, yCoord);
MKPointAnnotation *point = [MKPointAnnotation new];
point.coordinate = coords;
point.title = [parsed objectForKey:#"name"];
[self.mapView addAnnotation:point]; // or whatever your map view's variable name is
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)viewDidUnload {
[super viewDidUnload];
// ARC Problem --- [_mapView release];
self.mapView = nil;
}
#end
With iOS 5 there's a JSONSerializer class that can convert the raw JSON data from your URL into an array or dictionary as appropriate.
You'll need to download the data from the server:
NSURL *requestURL = [NSURL URLWithString:#"<your url here>"];
NSURLRequest *request = [NSURLRequest requestWithURL:requestURL];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
[connection start];
Then you'll add these delegate methods:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_downloadData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
id parsed = [NSJSONSerialization JSONObjectWithData:_downloadData options:kNilOptions error:nil];
}
_downloadData is an instance variable or property of your class of type NSMutableData.
That parsed variable will contain your data from the server. It's probably an array if it's a list of points, so you can iterate through it using fast enumeration:
for (NSDictionary *pointInfo in parsed) {
double xCoord = [(NSNumber*)[parsed objectForKey:#"<key for lat coord>"] doubleValue];
double yCoord = [(NSNumber*)[parsed objectForKey:#"<key for long coord>"] doubleValue];
CLLocationCoordinate2D coords = CLLocationCoordinate2DMake(xCoord, yCoord);
MKPointAnnotation *point = [[MKPointAnnotation new] autorelease];
point.coordinate = coords;
point.title = [parsed objectForKey:#"<key for title>"];
[self.mapView addAnnotation:point]; // or whatever your map view's variable name is
}
I have an open source project on GitHub that uses the serializer with the NSCoding protocol so you can automatically create instances right from the JSON stream.
It's here.

Trouble using a custom NSObject class to simplify repetitive code in iPhone app

I am trying to store a REST connection model in a single object so that I don't keep having to use it over and over again. Here is the model I created:
//RestModel.h
#import "ASIHTTPRequest.h"
#interface RestModel : NSObject{
NSString* _baseUrl;
NSString* _modelUrl;
}
#property (nonatomic, retain) NSString* modelUrl;
- (id)initWithModel:(NSString*)model;
- (NSDictionary*)getById:(NSInteger*)ident;
- (NSDictionary*)getAll;
#end
//RestModel.m
#import "RestModel.h"
#implementation RestModel
#synthesize modelUrl = _modelUrl;
- (id)init
{
self = [super init];
if (self) {
_baseUrl = #"http://myRESTurl.com";
}
return self;
}
- (id)initWithModel:(NSString*)model
{
self = [super init];
if (self) {
_baseUrl = #"http://myRESTurl.com";
self.modelUrl = [NSString stringWithFormat:#"%#/%#/", _baseUrl, model];
}
return self;
}
- (NSDictionary*)HTTPRequest:(NSURL*)url
{
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request startSynchronous];
NSError *error = [request error];
if(!error){
NSData *responseData = [request responseData];
NSString *errorDesc = nil;
NSPropertyListFormat format;
[error release];
[request release];
return (NSDictionary*)[NSPropertyListSerialization propertyListFromData:responseData mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
}else{
NSLog(#"%#", error);
return nil;
}
}
- (NSDictionary*)getById:(NSInteger*)ident
{
NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#", self.modelUrl, ident]];
return [self HTTPRequest:url];
}
- (NSDictionary*)getAll
{
NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:#"%#", self.modelUrl]];
return [self HTTPRequest:url];
}
- (void)dealloc
{
[_modelUrl release];
// [_responseData release];
// [_responseDict release];
[super dealloc];
}
#end
Edit: Now it isn't crashing, but my local NSDictionary has a count of 0. I'm calling the following in -viewDidLoad in my controller:
RestModel* rm = [[RestModel alloc] initWithModel:#"user"];
self.dict = [rm getAll];
[rm release];
I planted NSLog of [self.dict count] throughout the controller. It is always 0. The RestModel rm is called, the functions are called (again more NSLogs), but no data. Any ideas?
[error release];
[request release];
Those should be auto-released objects, as you got them by convenience methods, so releasing them explicitly will make your application crash.
NSInteger isn't an object so it isn't necessary to pass it using NSInteger * and certainly you do not want to use the %# format specifier. Instead try this:
- (NSDictionary*)getById:(NSInteger)ident
{
NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%d", self.modelUrl, ident]];
return [self HTTPRequest:url];
}

NSArray with multiple nested requests

I have an app that uses a segmentedControl. First item is an "All" item, where the rest is created from an array based on result from webservice. When "All" is selected I want to request all the request.
How can I go about this,
NSArray *urls = [NSArray arrayWithObjects:#"http://service/group/1/",
#"http://service/group/2/", nil];
I want to collect all result from the calls into a collection and display it in a UITableView when the "All" item is selected and probably in viewDidLoad.
For the other segments only one of the request is issued and callback with an array that then is used in:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
I have tried to look at this example for making the request from the array MultipleDownloads
Thanks,
The method in my viewController to initiate the multiple download:
- (void)requestChildrenInBackground {
queue = [[NSOperationQueue alloc] init];
//Todo remove hard coded and get from previous request respons
NSArray *urls = [NSArray arrayWithObjects: #"http://service/1/children",
#"http://service/2/children",
#"http://service/3/children", nil];
NSLog(#"%#", urls);
for (NSString * url in urls)
{
GetSchedule *operation =
[GetSchedule urlDownloaderWithUrlString:url];
[queue addOperation:operation];
}
}
This is how the multiple request gets handled:
#import "GetSchedule.h"
#import "JSON.h"
#import "Authentication.h"
#import "AttendanceReportViewController.h"
#interface GetSchedule ()
- (void)finish;
#end
#implementation GetSchedule
#synthesize appDelegate;
#synthesize username;
#synthesize password;
#synthesize authenticationString;
#synthesize encodedLoginData;
#synthesize schedulesArray;
#synthesize url = _url;
#synthesize statusCode = _statusCode;
#synthesize data = _data;
#synthesize error = _error;
#synthesize isExecuting = _isExecuting;
#synthesize isFinished = _isFinished;
+ (id)urlDownloaderWithUrlString:(NSString *)urlString {
NSURL * url = [NSURL URLWithString:urlString];
GetSchedule *operation = [[self alloc] initWithUrl:url];
return [operation autorelease];
}
- (id)initWithUrl:(NSURL *)url {
self = [super init];
if (self == nil)
return nil;
_url = [url copy];
_isExecuting = NO;
_isFinished = NO;
return self;
}
- (void)dealloc
{
[username release];
[password release];
[encodedLoginData release];
[_url release];
[_connection release];
[_data release];
[_error release];
[super dealloc];
}
- (BOOL)isConcurrent
{
return YES;
}
- (void)start
{
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:#selector(start) withObject:nil waitUntilDone:NO];
return;
}
self.username = appDelegate.username;
self.password = appDelegate.password;
Authentication *auth = [[Authentication alloc] init];
authenticationString = (NSMutableString*)[#"" stringByAppendingFormat:#"%#:%#", username, password];
self.encodedLoginData = [auth encodedAuthentication:authenticationString];
[auth release];
NSLog(#"operation for <%#> started.", _url);
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
// Setup up the request with the url
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL:_url];
[request setHTTPMethod:#"GET"];
[request setValue:[NSString stringWithFormat:#"Basic %#", encodedLoginData] forHTTPHeaderField:#"Authorization"];
_connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
if (_connection == nil)
[self finish];
else {
_data = [[NSMutableData alloc] init];
}
}
- (void)finish
{
NSLog(#"operation for <%#> finished. "
#"status code: %d, error: %#, data size: %u",
_url, _statusCode, _error, [_data length]);
[_connection release];
_connection = nil;
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
#pragma mark -
#pragma mark NSURLConnection delegate
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response
{
//[_data release];
//_data = [[NSMutableData alloc] init];
[_data setLength:0];
NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)response;
_statusCode = [httpResponse statusCode];
}
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data
{
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Parse the responseData of json objects retrieved from the service
SBJSON *parser = [[SBJSON alloc] init];
NSString *jsonString = [[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding];
NSDictionary *jsonData = [parser objectWithString:jsonString error:nil];
NSMutableArray *array = [jsonData objectForKey:#"Children"];
schedulesArray = [NSMutableArray array];
[schedulesArray addObject:array];
// Callback to AttendanceReportViewController that the responseData finished loading
[attendanceReportViewController loadSchedule];
[self finish];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
_error = [error copy];
[self finish];
}
#end
When all data is received I want to call back to my ViewController and get an array with all data from all request made.
Create a downloader
Create a downloader class using NSURLConnection.
Keep a member variable called downloaderObjectId.
Write a delegate method for this object. This method will pass the downloaded data and downloaderObjectId back to the delegate.
In the delegate.
Create multiple downloader objects(As per your ncessity) with unique value for the downloaderObjectId.
Store these objects in a NSMutableDictionary
Key for each object will be downloaderObjectId. so that when the delegate method is called after download you take the exact object back from the NSMutableDictionary using this key.
Main point.
Each time delegate is called. You should remove the object from dictionary(The object who is done with the download and called his delgate. You can identify this object by the key downloaderObjectId he holds. )
Then check the count of dictionary. If it is zero you can make sure that your downloads are completed. So you can call your viewcontroller.

Memorymanagement when getting a NSMutableArray from NSObject class to UIViewController class

I have problem with the following code leaking memory...
#property (nonatomic, retain) NSMutableArray *childrensArray;
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"Connection finished loading.");
// Dismiss the network indicator when connection finished loading
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
// Parse the responseData of json objects retrieved from the service
SBJSON *parser = [[SBJSON alloc] init];
NSString *jsonString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSDictionary *jsonData = [parser objectWithString:jsonString error:nil];
childrensArray = [jsonData objectForKey:#"Children"];
// Callback to AttendanceReportViewController that the responseData finished loading
[attendanceReportViewController loadChildren];
[connection release];
[responseData release];
[jsonString release];
[parser release];
}
In the viewController the following also leaks memory...
#property (nonatomic, retain) NSMutableArray *childrensArray;
- (void)loadChildren {
// Retrieve a array with dictionaries of children from ServiceGetChildren
self.childrensArray = [[serviceGetChildren.childrensArray copy] autorelease];
int total = [childrensArray count];
totalLabel.text = [NSString stringWithFormat:#"%d", total];
[theTableView reloadData];
}
You only release childrensArray when the instance is deallocated. You should also release the instance variable before setting it:
- (void)loadChildren {
// Retrieve a array with dictionaries of children from ServiceGetChildren
[childrensArray release];
childrensArray = [serviceGetChildren.childrensArray copy];
}
A better way would be to actually use your property:
- (void)loadChildren {
// Retrieve a array with dictionaries of children from ServiceGetChildren
self.childrensArray = [[serviceGetChildren.childrensArray copy] autorelease];
}
(Note the autorelease)
This has the benefit of triggering KVO-Notifications should you ever use them.

Downloading a Large File - iPhone SDK

I am using Erica Sadun's method of Asynchronous Downloads (link here for the project file: download), however her method does not work with files that have a big size (50 mb or above). If I try to download a file above 50 mb, it will usually crash due to a memory crash. Is there anyway I can tweak this code so that it works with large files as well? Here is the code I have in the DownloadHelper Classes (which is already in the download link):
.h
#protocol DownloadHelperDelegate <NSObject>
#optional
- (void) didReceiveData: (NSData *) theData;
- (void) didReceiveFilename: (NSString *) aName;
- (void) dataDownloadFailed: (NSString *) reason;
- (void) dataDownloadAtPercent: (NSNumber *) aPercent;
#end
#interface DownloadHelper : NSObject
{
NSURLResponse *response;
NSMutableData *data;
NSString *urlString;
NSURLConnection *urlconnection;
id <DownloadHelperDelegate> delegate;
BOOL isDownloading;
}
#property (retain) NSURLResponse *response;
#property (retain) NSURLConnection *urlconnection;
#property (retain) NSMutableData *data;
#property (retain) NSString *urlString;
#property (retain) id delegate;
#property (assign) BOOL isDownloading;
+ (DownloadHelper *) sharedInstance;
+ (void) download:(NSString *) aURLString;
+ (void) cancel;
#end
.m
#define DELEGATE_CALLBACK(X, Y) if (sharedInstance.delegate && [sharedInstance.delegate respondsToSelector:#selector(X)]) [sharedInstance.delegate performSelector:#selector(X) withObject:Y];
#define NUMBER(X) [NSNumber numberWithFloat:X]
static DownloadHelper *sharedInstance = nil;
#implementation DownloadHelper
#synthesize response;
#synthesize data;
#synthesize delegate;
#synthesize urlString;
#synthesize urlconnection;
#synthesize isDownloading;
- (void) start
{
self.isDownloading = NO;
NSURL *url = [NSURL URLWithString:self.urlString];
if (!url)
{
NSString *reason = [NSString stringWithFormat:#"Could not create URL from string %#", self.urlString];
DELEGATE_CALLBACK(dataDownloadFailed:, reason);
return;
}
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url];
if (!theRequest)
{
NSString *reason = [NSString stringWithFormat:#"Could not create URL request from string %#", self.urlString];
DELEGATE_CALLBACK(dataDownloadFailed:, reason);
return;
}
self.urlconnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (!self.urlconnection)
{
NSString *reason = [NSString stringWithFormat:#"URL connection failed for string %#", self.urlString];
DELEGATE_CALLBACK(dataDownloadFailed:, reason);
return;
}
self.isDownloading = YES;
// Create the new data object
self.data = [NSMutableData data];
self.response = nil;
[self.urlconnection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void) cleanup
{
self.data = nil;
self.response = nil;
self.urlconnection = nil;
self.urlString = nil;
self.isDownloading = NO;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)aResponse
{
// store the response information
self.response = aResponse;
// Check for bad connection
if ([aResponse expectedContentLength] < 0)
{
NSString *reason = [NSString stringWithFormat:#"Invalid URL [%#]", self.urlString];
DELEGATE_CALLBACK(dataDownloadFailed:, reason);
[connection cancel];
[self cleanup];
return;
}
if ([aResponse suggestedFilename])
DELEGATE_CALLBACK(didReceiveFilename:, [aResponse suggestedFilename]);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData
{
// append the new data and update the delegate
[self.data appendData:theData];
if (self.response)
{
float expectedLength = [self.response expectedContentLength];
float currentLength = self.data.length;
float percent = currentLength / expectedLength;
DELEGATE_CALLBACK(dataDownloadAtPercent:, NUMBER(percent));
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// finished downloading the data, cleaning up
self.response = nil;
// Delegate is responsible for releasing data
if (self.delegate)
{
NSData *theData = [self.data retain];
DELEGATE_CALLBACK(didReceiveData:, theData);
}
[self.urlconnection unscheduleFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self cleanup];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
self.isDownloading = NO;
NSLog(#"Error: Failed connection, %#", [error localizedDescription]);
DELEGATE_CALLBACK(dataDownloadFailed:, #"Failed Connection");
[self cleanup];
}
+ (DownloadHelper *) sharedInstance
{
if(!sharedInstance) sharedInstance = [[self alloc] init];
return sharedInstance;
}
+ (void) download:(NSString *) aURLString
{
if (sharedInstance.isDownloading)
{
NSLog(#"Error: Cannot start new download until current download finishes");
DELEGATE_CALLBACK(dataDownloadFailed:, #"");
return;
}
sharedInstance.urlString = aURLString;
[sharedInstance start];
}
+ (void) cancel
{
if (sharedInstance.isDownloading) [sharedInstance.urlconnection cancel];
}
#end
And finally this is how I write the file with the two classes above it:
- (void) didReceiveData: (NSData *) theData
{
if (![theData writeToFile:self.savePath atomically:YES])
[self doLog:#"Error writing data to file"];
[theData release];
}
If someone could help me out I would be so glad!
Thanks,
Kevin
Replace the in-memory NSData *data with an NSOutputStream *stream. In -start create the stream to append and open it:
stream = [[NSOutputStream alloc] initToFileAtPath:path append:YES];
[stream open];
As data comes in, write it to the stream:
NSUInteger left = [theData length];
NSUInteger nwr = 0;
do {
nwr = [stream write:[theData bytes] maxLength:left];
if (-1 == nwr) break;
left -= nwr;
} while (left > 0);
if (left) {
NSLog(#"stream error: %#", [stream streamError]);
}
When you're done, close the stream:
[stream close];
A better approach would be to add the stream in addition to the data ivar, set the helper as the stream's delegate, buffer incoming data in the data ivar, then dump the data ivar's contents to the helper whenever the stream sends the helper its space-available event and clear it out of the data ivar.
I have a slight modification to the above code.
Use this function, it works fine for me.
- (void) didReceiveData: (NSData*) theData
{
NSOutputStream *stream=[[NSOutputStream alloc] initToFileAtPath:self.savePath append:YES];
[stream open];
percentage.hidden=YES;
NSString *str=(NSString *)theData;
NSUInteger left = [str length];
NSUInteger nwr = 0;
do {
nwr = [stream write:[theData bytes] maxLength:left];
if (-1 == nwr) break;
left -= nwr;
} while (left > 0);
if (left) {
NSLog(#"stream error: %#", [stream streamError]);
}
[stream close];
}
Try AFNetworking. And:
NSString *yourFileURL=#"http://yourFileURL.zip";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:yourFileURL]];
AFURLConnectionOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSString *cacheDir = [NSSearchPathForDirectoriesInDomains
(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [cacheDir stringByAppendingPathComponent:
#"youFile.zip"];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:filePath append:NO];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
//show here your downloading progress if needed
}];
[operation setCompletionBlock:^{
NSLog(#"File successfully downloaded");
}];
[operation start];