-(void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
if([recievedData length]) [ recievedData setLength:0 ];
download_size =[response expectedContentLength];
}
I have this code. download_size is NSInteger.
expectedContentLenght always return: -1.
Maybe someone know why? I tried use long, but effect was the same.
Thanks for help.
The expected content length is only set when the server provides it, such as by a Content-Length response header. A -1 size means the expected content size is unknown.
If you set Accept-Encoding: gzip on your request, the URL loading system will fib and tell you the expected size is -1, no matter what Content-Length the server sends. This is because it decompresses the data before passing it to you, but it can't know the final uncompressed size till all the data has been downloaded, which is well after you receive this callback.
As long as I know, NSURLResponse does not update that property. You need to use NSHTTPURLResponse instead...
Related
I'm using AFNetworking and SDURLCache for all my networking operations.
I have SDURLCache set like this:
SDURLCache *urlCache = [[SDURLCache alloc]
initWithMemoryCapacity:1024*1024*2 // 2MB mem cache
diskCapacity:1024*1024*15 // 15MB disk cache
diskPath:[SDURLCache defaultCachePath]];
[urlCache setMinCacheInterval:1];
[NSURLCache setSharedURLCache:urlCache];
All my request are using cachePolicy NSURLRequestUseProtocolCachePolicy, which according to apple docs works like this:
If an NSCachedURLResponse does not exist for the request, then the
data is fetched from the originating source. If there is a cached
response for the request, the URL loading system checks the response
to determine if it specifies that the contents must be revalidated. If
the contents must be revalidated a connection is made to the
originating source to see if it has changed. If it has not changed,
then the response is returned from the local cache. If it has changed,
the data is fetched from the originating source.
If the cached response doesn’t specify that the contents must be revalidated, the maximum age or expiration specified in the response
is examined. If the cached response is recent enough, then the
response is returned from the local cache. If the response is
determined to be stale, the originating source is checked for newer
data. If newer data is available, the data is fetched from the
originating source, otherwise it is returned from the cache.
So everything works perfectly even in airplane mode as long as the cache is not stale. When the cache expires (max-age and others), the failure block gets called.
I've been digging a little inside the SDURLCache and this method returns a response with valid data (I've parsed the data to a string and it contains the cached information)
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
request = [SDURLCache canonicalRequestForRequest:request];
NSCachedURLResponse *memoryResponse =
[super cachedResponseForRequest:request];
if (memoryResponse) {
return memoryResponse;
}
NSString *cacheKey = [SDURLCache cacheKeyForURL:request.URL];
// NOTE: We don't handle expiration here as even staled cache data is
// necessary for NSURLConnection to handle cache revalidation.
// Staled cache data is also needed for cachePolicies which force the
// use of the cache.
__block NSCachedURLResponse *response = nil;
dispatch_sync(get_disk_cache_queue(), ^{
NSMutableDictionary *accesses = [self.diskCacheInfo
objectForKey:kAFURLCacheInfoAccessesKey];
// OPTI: Check for cache-hit in in-memory dictionary before to hit FS
if ([accesses objectForKey:cacheKey]) {
response = [NSKeyedUnarchiver unarchiveObjectWithFile:
[_diskCachePath stringByAppendingPathComponent:cacheKey]];
if (response) {
// OPTI: Log entry last access time for LRU cache eviction
// algorithm but don't save the dictionary
// on disk now in order to save IO and time
[accesses setObject:[NSDate date] forKey:cacheKey];
_diskCacheInfoDirty = YES;
}
}
});
// OPTI: Store the response to memory cache for potential future requests
if (response) {
[super storeCachedResponse:response forRequest:request];
}
return response;
}
So at this point I have no idea what to do, because I believe that the response is handled by the OS and then AFNetworking receives a
- (void)connection:(NSURLConnection *)__unused connection
didFailWithError:(NSError *)error
inside AFURLConnectionOperation.
Well I've finally reached a not so ugly workaround:
First
If you're using IOS5/IOS6 you can drop SDURLCache and use the native one:
//Set Cache
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
diskCapacity:20 * 1024 * 1024
diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];
But remember that in IOS5 https requests wont be cached in IOS6 they will.
Second
We need to add the following frameworks to our Prefix.pch so AFNetworking can start monitoring our internet connection.
#import <MobileCoreServices/MobileCoreServices.h>
#import <SystemConfiguration/SystemConfiguration.h>
Third
We need and AFHTTPClient instance so we can intercept every outgoing request and change his cachePolicy
-(NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters {
NSMutableURLRequest * request = [super requestWithMethod:method path:path parameters:parameters];
if (request.cachePolicy == NSURLRequestUseProtocolCachePolicy && self.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) {
request.cachePolicy = NSURLRequestReturnCacheDataDontLoad;
}
if (self.networkReachabilityStatus == AFNetworkReachabilityStatusUnknown) {
puts("uknown reachability status");
}
return request;
}
With these peaces of code we can now detect when the wifi/3g is unavailable and the specify the request to use always the cache no matter what. (Offline Mode)
Notes
I still don't know what to do when the networkReachabilityStatus is AFNetworkReachabilityStatusUnknown This can happen is a request is made as soon as the application starts and AF has not obtained the internet status yet.
Remember that in order for this to work the server has to set the correct cache headers in the http response.
UPDATE
Looks like IOS6 has some problems loading cached responses in no-internet situations, so even if the request is cached and the request cache policy is seted to NSURLRequestReturnCacheDataDontLoad the request will fail.
So an ugly workaround is to modify (void)connection:(NSURLConnection __unused *)connection
didFailWithError:(NSError *)error in AFURLConnectionOperation.m to retrieve the cached response if the request fails but only for specific cache policies.
- (void)connection:(NSURLConnection __unused *)connection
didFailWithError:(NSError *)error
{
self.error = error;
[self.outputStream close];
[self finish];
self.connection = nil;
//Ugly hack for making the request succeed if we can find a valid non-empty cached request
//This is because IOS6 is not handling cache responses right when we are in a no-connection sittuation
//Only use this code for cache policies that are supposed to listen to cache regarding it's expiration date
if (self.request.cachePolicy == NSURLRequestUseProtocolCachePolicy ||
self.request.cachePolicy == NSURLRequestReturnCacheDataElseLoad ||
self.request.cachePolicy == NSURLRequestReturnCacheDataDontLoad) {
NSCachedURLResponse * cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request];
if (cachedResponse.data.length > 0) {
self.responseData = cachedResponse.data;
self.response = cachedResponse.response;
self.error = nil;
}
}
}
Can't tell much without your HTTP headers -- but the most common reason for this is NSURLProtocol forcing revalidation before delivering cached response to WebView.
Please take a look here :
http://robnapier.net/blog/offline-uiwebview-nsurlprotocol-588
It sounds like you want the request to succeed, even though the cache says the data has expired and should be retrieved from the server. You may have some luck setting the cache policy (different policy for online vs. offline) of certain requests where you'd rather use stale data than fail.
NSMutableURLRequest -> setCachePolicy
It looks like NSURLRequestReturnCacheDataDontLoad is the policy you want for offline mode.
Hope that helps!
i need to download large pdf from web and i created a progress bar controller.
But, how i set max progress value it i don't know the pdf size before downloading?
Is there a way to get file size and use it to increment progress bar?
I'm using
myPDFremoteUrl = "http://www.xasdaxxssxx.pdf";
- (float) getFileSize {
NSFileManager *man = [[NSFileManager alloc] init];
NSDictionary *attrs = [man attributesOfItemAtPath: myPDFremoteUrl error: NULL];
UInt32 result = [attrs fileSize];
return (float)result;
}
but i don't able to check remote size in this way...
any idea?
Thanks
I would use ASIHTTPRequest for your data retrieval. It has progress monitoring built in.
If you really want to stick with what you've got, you can look at the HTTP header. It has a property called "Content-Length" that will help you out.
Good luck.
ASIHTTPRequest has a downloadProgressDelegate that can handle this for you- its very easy to use.
Solved.
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
dimensione = (float)[response expectedContentLength];
NSLog(#"content-length: %f bytes", dimensione);
}
I forget didReceiveResponse!
good.
I've been using NSURLConnection to do a HTTP post to establish the connection. I've also implemented the didReceiveData delegate to process incoming bytes as they become available.
As incoming data comes in via didReceiveData, I add the NSData to a data buffer and try parsing the bytesteam if enough data has come in to complete a message segment. I'm having a hard time managing the data buffer (NSMutableData object) to remove bytes that have been parsed to structs. Was curious if there's an easier way. My didReceiveData delegate is below.
It works, but I don't think I'm managing memory correctly after I copy the message segment (currMsg) out of the responseData buffer and call processMsg. I get double free errors when running under the Simulator -- the program doesn't crash.
NSMutableData/NSData provide methods for appending bytes to the end but I didn't see any methods for removing bytes from the beginning (bytes representing whats already been parsed. I would appreciate some advice on how to best remove the parsed bytes from the responseData buffer. I come from a mostly C background so I'm not sure if there are better ways of manipulating the NSData bytes pointer. I'd like to avoid copying if possible -- just want to process a portion of the responseData buffer and leave the rest in responseData for next time enough bytes are in it for parsing.
Thanks
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSData *tmpBuffer = nil;
NSInteger currMsgSize = 10;
[responseData appendData:data];
NSInteger rspDataLen = [responseData length];
while(rspDataLen >= 10) {
currMsg = [[NSData alloc] initWithBytesNoCopy:(void *)[responseData bytes] length:currMsgSize];
[self processMsg:currMsg];
[currMsg release];
[responseData getBytes:tmpBuffer range:NSMakeRange(currMsgSize, rspDataLen - currMsgSize)];
[responseData release];
responseData = [[NSMutableData alloc] initWithBytesNoCopy:(void *)tmpBuffer length:rspDataLen - currMsgSize];
rspDataLen = rspDataLen - currMsgSize;
}
}
Where do you allocate the first responseData?
What is [self processMsg:currMsg] doing with the data? If it is expecting the data to be around after -processMsg: returns, and it isn't explicitly making a copy, then you are in trouble.
Infact, unless you have finished with the received data before didReceiveData: returns, you need to make a copy of it somewhere, which isn't visible in the code shown.
You need to allocate the storage for tempBuffer, not pass in an uninitialised pointer;
You should look probably for a pre-rolled implementation of a simple ring buffer. There are plenty around.
I'm trying to download several images in response to a single http request. On the server side (java) I'm using oreilly multipart response and I'm getting my datas in my iPhone Simulator in didReceiveData (approximately one call for each image) after a call to didReceiveResponse (approximately one call for each image as well) in my delegate.
The problem is this approximately... Has anyone ever managed to handle correctly multipart/x-mixed-re with iPhone SDK ? If yes what is the best strategy here ? Should I play with the expected length ? on server side ? on client side ? should I wait until I've received everything... mmmh that doesn't even seen enough as the calls to didReceiveData happens in a random order (I'm asking picture1,picture2 and I'm sometimes receiving picture2,picture1 even though the order is respected on server side !). Should i temporize between pictures on server side ?
Or should I drop multipart/x-mixed-replace ? what would be the easiest then ?
That's a lot of questions but I'm really stuck here ! Thanks for you help !
I'm not sure what your final use for the images is, but the intended purpose of the multipart/x-midex-replace content type is for each received part to completely replace the previously received responses. Think of it like frames of a video; only one picture is displayed at a time and the previous ones are discarded.
Temporizing is almost never a foolproof solution. Especially on the iPhone you're going to encounter an unimaginable variety of network situations and relying on a magic number delay between frames will probably still fail some of the time.
Since you have control of the server, I'd recommend dropping the multipart. Make sure when you are sending multiple requests to the server that you don't block the main thread of your iPhone app. Use NSOperations or an alternative HTTP library (like ASIHTTPRequest) to make your image fetch operations asynchronous.
I did that successfully using this code. The important thing is to create 2 buffers to receive your data. If you use only one you will have some double access problems (stream access and jpg CODEC access) and corrupted JPG data.
Do not hesitate to ask me for more details.
- (IBAction)startDowload:(id)sender {
NSURL *url = [NSURL URLWithString:#"http://210.236.173.198/axis-cgi/mjpg/video.cgi?resolution=320x240&fps=5"];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
[req setHTTPMethod:#"GET"];
/*
I create 2 NSMutableData buffers. This points is very important.
I swap them each frame.
*/
receivedData = [[NSMutableData data] retain];
receivedData2 = [[NSMutableData data] retain];
currentData = receivedData;
urlCon = [[NSURLConnection alloc] initWithRequest:req delegate:self];
noImg = YES;
frame = 0;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)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 declared as a method instance elsewhere
UIImage *_i;
#try
{
_i = [UIImage imageWithData:currentData];
}
#catch (NSException * e)
{
NSLog(#"%#",[e description]);
}
#finally
{
}
CGSize _s = [_i size];
[imgView setImage:_i];
[imgView setNeedsDisplay];
[[self view] setNeedsDisplay];
}
/*
Buffers swap
*/
if (currentData == receivedData)
{
currentData = receivedData2;
}
else
{
currentData = receivedData;
}
[currendData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// append the new data to the currentData (NSData buffer)
[currendData appendData:data];
}
I'm trying to code up an async image downloader. I use NSURLConnection to get the data into an NSMutableData and use that data once it is complete to initialize a UIImage.
I checked the bytes and it downloads the entire image correctly (right number of bytes at least), however; when I call
[UIImage imageWithData:data]
and then check the properties of the image, it is zero width and a garbage number for height, in fact, same number no matter what the image is. I tried with bunch of different images, png, jpg, different urls, it always downloads the image completely but UIImage can't initialize with that data. What could I be doing wrong here?
Thanks.
Code is really as you'd expect it to look like:
Connection Delegate:
-(void)connectionDidFinishLoading:(NSURLConnection*)theConnection {
[[ImageManager sharedInstance] dataDownloadedBy:self]; }
ImageManager:
-(void)dataDownloadedBy:(WebConnection *)connection{
WebImage *image = [[WebImage alloc] initWithLink:connection.url];
[image setImageFromData:connection.data];
[images addObject:image];
[connection release];}
WebImage:
-(void)setImageFromData:(NSMutableData *)data{
image = [[UIImage alloc] initWithData:data];}
First, I'm sure the UIImage will not initialize with garbage data. The constructor initWithData analyzes the data to determine the file format. If your data is corrupted, the image returned will be nil. Check this at first.
-(void)dataDownloadedBy:(WebConnection *)connection{
WebImage *image = [[WebImage alloc] initWithLink:connection.url];
[image setImageFromData:connection.data];
if (image.image != nil) { [images addObject:image]; }
[connection release];
}
Second, make sure you append the data during the download process. Here is the callback method:
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.receivedData appendData:data];
}
Finally, your code must absolutely includes the second case: a download failure.
- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[connection release];
}
Your problem description lacks some important pieces of code.
The URLCache demo from Apple is a very good project to understand async image download.
http://developer.apple.com/iphone/library/samplecode/URLCache/Introduction/Intro.html
I hope this will help you!
I can't tell what is wrong, it could be you don't use the release properly, say i don't know why you release connection at the end of dataDownloadedBy, it is supposed to be image ?
it could help if you post more your code here.
I used to do the same thing, you can have a look the post here
http://blog.163.com/lionyue#126/blog/static/1079307120096895433277/
Hope it helps
If initWithData is failing, most likely the image data you're getting is corrupt. You should save it to a file like this:
[data writeToFile:#"/tmp/foo.jpg" atomically:NO];
and then try to open it in Preview.app.
a different problem, but that gives teh same painful error is if You ask for a file that is not reachable: HTTP server will give back error 404 message, your code read that bytes (they are NOT valid bytes for an image, AND You will got NIL.