iOS: TouchJSON & retrieved data size - ios

I want to display a UIProgressView indicating the amount of data received as I request JSON data using touchJSON. I was wondering if there was a way to listen to the size of the data being received.
I request data using the following:
- (NSDictionary *)requestData
{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:apiURL]];
NSError *error = nil;
NSDictionary *result = [[CJSONDeserializer deserializer] deserializeAsDictionary:data error:&error];
if(error != NULL)
NSLog(#"Error: %#", error);
return result;
}

You will have to introduce some more code to include a download status indicator bar. At the moment you download the data with [NSData dataWithConentsOfURL:...]. Instead, you will make a class that uses a NSURLConnection object to download data, accumulate that data in an MSMutableData object, and update your UI accordingly. You should be able to use the ContentLength HTTP header and the - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data; updates to determine the status of the download.
Here are some relevant methods:
- (void) startDownload
{
downloadedData = [[NSMutableData alloc] init];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
}
- (void)connection:(NSURLConnection *)c didReceiveResponse:(NSURLResponse *)response
{
totalBytes = [response expectedContentLength];
}
// assume you have an NSMutableData instance variable named downloadedData
- (void)connection:(NSURLConnection *)c didReceiveData:(NSData *)data
{
[downloadedData appendData: data];
float proportionSoFar = (float)[downloadedData length] / (float)totalBytes;
// update UI with proportionSoFar
}
- (void)connection:(NSURLConnection *)c didFailWithError:(NSError *)error
{
[connection release];
connection = nil;
// handle failure
}
- (void)connectionDidFinishLoading:(NSURLConnection *)c
{
[connection release];
connection = nil;
// handle data upon success
}
Personally, I think the simplest way to do this is to create a class that implements the above methods to do generic data downloads and interface with that class.
This should be enough to get you what you need.

Related

NSURLConnection and NSURLConnectionDataDelegate: connection callbacks are not fired

I want to implement file downloading with progress from my server.
I my code I'm using a custom class which is delegated by
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://example.com"]];
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"6.0")) {
DownloadCallback *dc = [[DownloadCallback alloc] initWithCallbackProgress:^(long long res){
NSLog(#"%lld", res);
} withCallbackReady:^(long long res){
NSLog(#"READY %lld", res);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
}];
} withCallbackError:^(NSError * error) {
NSLog(#"READY %#", error.domain);
}];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:dc];
// [connection setDelegateQueue:[[NSOperationQueue alloc] init]];
[connection start];
header:
#interface DownloadCallback: NSObject<NSURLConnectionDataDelegate>{
#private void (^_progressHandler)(long long someParameter);
#private void (^_readyHandler)(long long someParameter);
#private void (^_errorHandler)(NSError *someParameter);
}
-(id) initWithCallbackProgress:(void(^)(long long))handler withCallbackReady:(void(^)(long long))handlerReady withCallbackError:(void(^)(NSError*))handlerError;
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
#end
body:
#implementation DownloadCallback
-(id) initWithCallbackProgress:(void(^)(long long))handler withCallbackReady:(void(^)(long long))handlerReady withCallbackError:(void(^)(NSError*))handlerError{
self = [super init];
if (self) {
_progressHandler = [handler copy];
_readyHandler = [handlerReady copy];
_errorHandler = [handlerError copy];
}
return self;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// self.expectedTotalSize = response.expectedContentLength;
// Call completion handler.
if (_readyHandler != nil)
_readyHandler(response.expectedContentLength);
// Clean up.
// [_completionHandler release];
_readyHandler = nil;
_progressHandler = nil;
_errorHandler = nil;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// self.recievedData += data.length;
if (_progressHandler != nil)
_progressHandler(data.length);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
if (_errorHandler != nil)
_errorHandler(error);
}
#end
But the callback events are not fired! At all!
The simple synch code work prefectly:
// Send a synchronous request
NSURLRequest * urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://example.com"]];
NSURLResponse * response = nil;
NSError * error = nil;
NSData * data = [NSURLConnection sendSynchronousRequest:urlRequest
returningResponse:&response
error:&error];
if (error == nil) {
// Parse data here
}
But I need a callback! How to resolve it? I've not found in stackoverflow a solution.
Futhermore, if I'm using a simple delegate to major class instead of DownloadCallback the same: the connection callbacks are not fired too.
Add the dealloc method to your callback class and out a breakpoint or log statement in it. See if it is deallocated before the callbacks are called.
If this is the case, your callback class instance is destroyed too soon. Make it a property of a class that will for sure live longer then the request.
Also, you should make sure that this code:
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:dc];
[connection start];
is called on a thread outlives the connection and has a runloop. The easiest way to achieve this is to call that code on the main-queue. Your code-example does not show on which queue that is called. If it is not working I assume it is because your calling it on a background queue.
You can dispatch to a background queue from the delegate callbacks of you want/need to.
As a sidenote, if you are building something new, you should try and use NSURLSession instead of NSURLConnection. NSURLSession is more secure, easier to use and not deprecated. NSURLConnection is deprecated.

how to update json file in ios during runtime from the server

I want to update my json file in ios app which is offline compiled in the app. When the app is refreshed the file should get updated from the server : localhost:8888/ios/ios_app/Service/data.json
Please help...
Thank you in advance
I use SOAP when i need get value that can be serialized to a string type. But if all what you need is json file, look at NSURLConnection class.
- (void)downloadJSONFromURL {
NSURLRequest *request = ....
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
// ...
}
NSData *urlData;
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
urlData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[urlData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSError *jsonParsingError = nil;
id object = [NSJSONSerialization JSONObjectWithData:urlData options:0 error:&jsonParsingError];
if (jsonParsingError) {
DLog(#"JSON ERROR: %#", [jsonParsingError localizedDescription]);
} else {
DLog(#"OBJECT: %#", [object class]);
}
}

Same code showing different behaviour in XCode

-(IBAction)onButtonClick:(id)sender
{
DowloadFilesManager* downManager1=[[DowloadFilesManager alloc] init];
DowloadFilesManager* downManager2=[[DowloadFilesManager alloc] init];
DowloadFilesManager* downManager3=[[DowloadFilesManager alloc] init];
[downManager1 downloadURL:#"http://localhost/banners/banner1.jpg" destPath:#"/Users/varunisac/Desktop/samples/godisgreat.jpg"];
[downManager2 downloadURL:#"http://localhost/banners/banner1.jpg" destPath:#"/Users/varunisac/Desktop/samples/godisgreat1.jpg"];
[downManager3 downloadURL:#"http://localhost/banners/banner1.jpg" destPath:#"/Users/varunisac/Desktop/samples/godisgreat2.jpg"];
NSLog(#"Finished Succesfully");
}
1)The Above code works Perfect
2) It downloads the jpgs after firing the following event function.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Succeeded! Received %d bytes of data",[receivedData length]);
[receivedData writeToFile:toFile atomically:YES];
theConnection = nil;
receivedData = nil;
}
But It DOES NOT fires any event methods while i tried on another programme after importing the "DowloadFilesManager.h" and "DowloadFilesManager.m" which runs on the same XCode on the same Mac machine with the server URLs reachable.Can anyone suggest a solution ? Am i missing anything? I tried Clean etc...but doesnt work. Following is the DowloadFilesManager class which i used:
#import "DowloadFilesManager.h"
#implementation DowloadFilesManager
#synthesize toFile;
#synthesize toURL;
#synthesize theConnection;
-(void) downloadURL:(NSString *) urlStr destPath:(NSString *) destPath
{
toURL=urlStr;
toFile=destPath;
// Create the request.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:toURL]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
receivedData = [[NSMutableData alloc] init];
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
// [receivedData dataWithCapacity: 0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (!theConnection) {
// Release the receivedData object.
receivedData = nil;
// Inform the user that the connection failed.
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Append the new data to receivedData.
// receivedData is an instance variable declared elsewhere.
[receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
theConnection = nil;
receivedData = nil;
// inform the user
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Succeeded! Received %d bytes of data",[receivedData length]);
[receivedData writeToFile:toFile atomically:YES];
theConnection = nil;
receivedData = nil;
}
#end
Within - (void)downloadURL:destPath:, you have created a local variable called theConnection:
NSURLConnection *theConnection = [...];
and when the method ends, this will go out of scope and be destroyed.
What you want is the following, to use your property to persist the connection object:
self.theConnection = [...];
Also, a better approach to signal failure would be to make that method return BOOL and use this statement:
return self.theConnection != nil;

Initializing NSString 's length limit

In the code below, I am looking for a way to limit the length of connected string. Let's say I only want to retrieve the first 100 characters. But I do not want to do the processing connected after retrieving. Is there a way to initialize NSString with certain length?
NSError* error = nil;
NSString *connected = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://www.somesite.com"] encoding:NSASCIIStringEncoding error:&error];
You're going to have to retrieve the data yourself instead of using NSString's convenience method to do it. If you use, say, NSURLConnection or ASIHTTPRequest you can close the connection when you've received as much data as you want.
You could use NSString methods to retrieve the first 100 characters but you would have wasted bandwidth anyway to get all the data. So why download all when you want only 100 chars.
So to get only a slice of the data coming off the server, you need count the data stream that the url response gives. For this you could use NSURLConnection -
- (void)viewDidLoad {
[super viewDidLoad];
responseData = [[NSMutableData data] retain];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://someurl.com/data.json"]];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
if([responseData length] <= 100)
[responseData appendData:data];
else //break connection
[self connectionDidFinishLoading:connection];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
label.text = [NSString stringWithFormat:#"Connection failed: %#", [error description]];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
}
So you need to put your logic in didReceiveData. For here, you want only 100 chars, so break-off the connection after that number is reached.

Image blank when using NSURLConnection but not when using dataWithContentsOfURL

When I use NSURLConnection asynchronously to try and get a NSData with my image data in it the image comes back as blank, but when I use dataWithContentsOfURL synchronously I have no problems and I get the image data correctly. Is there any reason why my asynchronous method would be failing?
This works:
NSData *data = [NSData dataWithContentsOfURL: url];
NSLog(#"TEST %#", data);
UIImage *map = [UIImage imageWithData:data];
mapView.image = map;
This doesn't:
//
// MapHttpRequest.m
// GTWeb
//
// Created by Graphic Technologies on 6/21/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "MapHttpRequest.h"
#implementation MapHttpRequest
#synthesize receivedData;
#synthesize dataString;
#synthesize vc;
- (void)request:(NSString *)url fromView:(UIViewController *) theVC
{
vc = theVC;
// Create the request.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSLog(#"URL: %#", url);
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
receivedData = [[NSMutableData data] retain];
} else {
// Inform the user that the connection failed.
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// This method is called when the server has determined that it
// has enough information to create the NSURLResponse.
// It can be called multiple times, for example in the case of a
// redirect, so each time we reset the data.
// receivedData is an instance variable declared elsewhere.
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Append the new data to receivedData.
// receivedData is an instance variable declared elsewhere.
[receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// release the connection, and the data object
[connection release];
// receivedData is declared as a method instance elsewhere
[receivedData release];
// inform the user
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// do something with the data
// receivedData is declared as a method instance elsewhere
[vc mapImageConnectionFinished:receivedData];
// release the connection, and the data object
[dataString release];
[connection release];
[receivedData release];
}
#end
It looks like you started looking at the guide on http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLConnection.html but didnt finish reading it :)
You need to implement the methods that will received infomation about the data being received.
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
Its all described in the link i provided.
According to your code, you didn't schedule the connection to run:
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
Did you put traces in your delegate's callbacks to make sure they are being called?
Turns out it was giving me a bad request because there was a invisible carriage return or white space. Trimming it off with:
url = [url stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
fixed my problems.

Resources