NSURLConnectionDelegate's connection:willCacheResponse: randomly called - ios

Here is the really simple call I make :
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:500];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection start];
I try with 2 random pdf urls found on google (searching "truc filetype:pdf") :
A) NSString *urlString = #"http://www.eatletruc.com/letruc.menu0411.pdf";
B) NSString *urlString = #"http://www.botruc.com/boat-specs/C-Truc-7.pdf";
They both have similar headers (using allHeaderFields in connection:didReceiveResponse:) :
A)
"Accept-Ranges" = bytes;
Connection = "Keep-Alive";
"Content-Length" = 2641705;
"Content-Type" = "application/pdf";
Date = "Thu, 11 Apr 2013 08:53:39 GMT";
Etag = "\"19a7b55-284f29-4a0a5e94ae1a7\"";
"Keep-Alive" = "timeout=5, max=100";
"Last-Modified" = "Mon, 11 Apr 2011 15:05:50 GMT";
Server = Apache;
B)
"Accept-Ranges" = bytes;
Connection = "Keep-Alive";
"Content-Length" = 343793;
"Content-Type" = "application/pdf";
Date = "Thu, 11 Apr 2013 08:55:38 GMT";
Etag = "\"b6864a-53ef1-49400c1d95800\"";
"Keep-Alive" = "timeout=5, max=100";
"Last-Modified" = "Mon, 01 Nov 2010 17:01:20 GMT";
Server = "Apache/2.2.22 (Unix) mod_ssl/2.2.22 OpenSSL/1.0.0-fips mod_auth_passthrough/2.1 mod_bwlimited/1.4 FrontPage/5.0.2.2635";
But connection:willCacheResponse: is only called for url B. And I find only url B in the Cache.db sqlite database.
Why isn't url A cached?

Ok, so the problem comes from the size of the file.
It seems that NSURLCache won't cache files that are bigger than 5% of the disk capacity it has.
My NSURLCache was set with 50MB of disk capacity, so files bigger than 2.5MB aren't cached.
Extending the disk capacity solved my problem.
ps : you can extend the disk capacity to 2GB max, so files in cache can't be bigger than 100MB.

Related

How to create a UIImage from Facebook response data?

I use the following code to download the profile picture from a Facebook user’s friends array:
NSString *urlString = friendData[#"picture"][#"data"][#"url"];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:2.0f];
[NSURLConnection sendAsynchronousRequest:urlRequest
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *resp, NSData *data, NSError *error) {
UIImage *image = [UIImage imageWithData:data];
}];
urlString is valid, i.e. I can open it in Safari, and I do see the picture there.
In the completion handler, error is nil, and data has 1583 bytes.
However, image is initialized as nil, i.e. image could not be initialized from the specified data.
What is wrong with my code?
EDIT (due to the comment of rckoenes):
resp contains the following data:
{ status code: 200, headers {
"Access-Control-Allow-Origin" = "*";
"Cache-Control" = "max-age=1209600, no-transform";
"Content-Length" = 1583;
"Content-Type" = "image/jpeg";
Date = "Thu, 01 Oct 2015 09:19:41 GMT";
Expires = "Thu, 15 Oct 2015 08:08:18 GMT";
"Last-Modified" = "Thu, 01 Oct 2015 06:36:27 GMT";
"timing-allow-origin" = "*";
} }
Try this code
Download AsyncImageView Class Here
.M File
#import "AsyncImageView.h"
NSString *urlString = friendData[#"picture"][#"data"][#"url"];
AsyncImageView *DescimageRight = [[AsyncImageView alloc]initWithFrame:CGRectMake(4,4,146,146)];
DescimageRight.contentMode = UIViewContentModeScaleAspectFill;
DescimageRight.backgroundColor=[UIColor clearColor];
DescimageRight.tag=999;
DescimageRight.imageURL=[NSURL URLWithString:urlString];
[self.view addSubview:DescimageRight];
I found the problem:
The code is correct, and the image is also loaded correctly.
The problem was that I set a breakpoint to the last line of the code, i.e. to
}]; // breakpoint was set here
Apparently, when the debugger stops there, it left already the scope of the completion handler, and image was already nil.
After I inserted a dummy statement behind the assignment to image, and set the breakpoint to this dummy statement, everything was OK.
Sorry for bothering you.

totalBytesExpectedToWrite is -1 in NSURLSessionDownloadTask

I faced with a strange problem. I load file from the Internet using NSURLSession and NSURLSessionDownloadTask. Here is the code
NSURLSessionConfiguration *sessionConfiguration =
[NSURLSessionConfiguration backgroundSessionConfiguration:kSessionId];
self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:[NSOperationQueue new]];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithRequest:request];
[downloadTask resume];
My class is declared as NSURLSessionDownloadDelegate and I get callbacks well. But when the system calls the delegate method
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
NSLog(#"totalBytesExpectedToWrite: %lld", totalBytesExpectedToWrite);
NSLog(#"%lld", totalBytesWritten);
}
totalBytesExpectedToWrite always equal -1 and I have no ability to show a progress to user because I don't know the downloading file's size.
Could you prompt me where I made a mistake?
-1 is NSURLSessionTransferSizeUnknown, which means that the http server did not provide
a "Content-Length" header (and the data is sent using "Transfer-Encoding: chunked").
There is probably not much that you can do. You could try if the workaround from https://stackoverflow.com/a/12599242/1187415 works in your case as well:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:anURL];
[request addValue:#"" forHTTPHeaderField:#"Accept-Encoding"];
The web service may not be providing the total size in the header field Content-Length.
If the total size is not provided there is no way for your app to know the length and this provide a progress bar.
Check what is coming from the web server with a analyzer such as Charles Proxy.
The Content-Length can be non 0 and totalBytesExpectedToWrite:-1
//TRACK PROGRESS - MOVED DOWN as also used in BACKGROUND REFRESH > DOWNLOAD FILE > CALL DELEGATE
-(void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
//to see response header
NSLog(#"downloadTask.response:%#\n", downloadTask.response);
// { status code: 200, headers {
// "Cache-Control" = "no-cache";
// "Content-Disposition" = "attachment; filename=Directory.zip";
// "Content-Encoding" = gzip;
// "Content-Length" = 33666264;
// "Content-Type" = "application/octet-stream";
// Date = "Tue, 27 Oct 2015 15:50:01 GMT";
// Expires = "-1";
// Pragma = "no-cache";
// Server = "Microsoft-IIS/8.5";
// "X-AspNet-Version" = "4.0.30319";
// "X-Powered-By" = "ASP.NET";
// } }
NSDictionary *responseHeaders = ((NSHTTPURLResponse *)downloadTask.response).allHeaderFields;
NSString * contentLengthString = responseHeaders[#"Content-Length"];
double contentLengthDouble = 0.0f;
if (contentLengthString) {
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
NSNumber *contentLengthNumber = [f numberFromString:contentLengthString];
contentLengthDouble = [contentLengthNumber doubleValue];
}else{
}
NSLog(#"contentLengthString:[%#]", contentLengthString);
//You can get progress her
NSLog(#"bytesWritten:%lld", bytesWritten);
NSLog(#"totalBytesWritten:%lld", totalBytesWritten);
//DONT USE CAN BE ALWAYS -1 for Gzip
NSLog(#"totalBytesExpectedToWrite:%lld", totalBytesExpectedToWrite);
//avoid DIV by 0
if (contentLengthDouble > 0.0) {
double percentage1 = (totalBytesWritten / contentLengthDouble);
double percentage = percentage1 * 100.0;
NSLog(#"PERCENTAGE DOWNLOADED:[%f%%]", percentage);
}else{
NSLog(#"PERCENTAGE DOWNLOADED:[contentLengthDouble is 0]");
}
NSLog(#"=========");
}
The following is Output over and over as zip is downloaded.
but totalBytesExpectedToWrite:-1
So you need to check Content-Length in downloadTask.response
2015-10-27 16:04:18.580 ClarksonsDirectory[89873:15495901] downloadTask.response:<NSHTTPURLResponse: 0x7f9eabaae750> { URL: http://asset10232:50/api/1/dataexport/ios/?lastUpdatedDate=01012014000000 } { status code: 200, headers {
"Cache-Control" = "no-cache";
"Content-Disposition" = "attachment; filename=Directory.zip";
"Content-Encoding" = gzip;
"Content-Length" = 33666264;
"Content-Type" = "application/octet-stream";
Date = "Tue, 27 Oct 2015 16:03:55 GMT";
Expires = "-1";
Pragma = "no-cache";
Server = "Microsoft-IIS/8.5";
"X-AspNet-Version" = "4.0.30319";
"X-Powered-By" = "ASP.NET";
} }
contentLengthString:[33666264]
bytesWritten:47278
totalBytesWritten:33606690
totalBytesExpectedToWrite:-1
PERCENTAGE DOWNLOADED:[99.823045%]

AFNetworking 2 Upload Plist Data to Apache Server

I am writing a sample app to test how AFNetworking can be used as a replacement for ASIHTTPLib.. The old library made it simple to upload a file to an Apache server (provided the user has write access to a URL/directory). No other server side support is used..
This code has some problem, but I have not pinpointed it: executing the method reports an upload success, but the plist file on the cloud side does not change…
-(void)uploadReminders:(NSArray*)reminders
{
NSLog(#"AppDelegate Synch Reminders to cloud");
//NSLog(#"Data: %#", reminders);
[self persistReminders:reminders atCustomPath:nil];
NSString *cacheDirectoryPath = [self cachesDirectoryPath];
NSString *urlString = [NSString stringWithFormat:#"%#/%#",kStandardCloudAreaAccessURL,kStandardLocalCachePlistFile];
//NSLog(#"URL: %#", urlString);
NSString *remindersPlistFile = [NSString stringWithFormat:#"%#/%#",cacheDirectoryPath,kStandardLocalCachePlistFile];
//NSLog(#"Filepath: %#",remindersPlistFile);
NSURLCredential *defaultCredential = [NSURLCredential credentialWithUser:kStandardCloudAreaAccessUsername password:kStandardCloudAreaAccessUserPW persistence:NSURLCredentialPersistenceNone];
/**/
NSURL *url = [NSURL URLWithString:urlString];
NSString *host = [url host];
NSInteger port = [[url port] integerValue];
NSString *protocol = [url scheme];
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:nil authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
NSURLCredentialStorage *credentials = [NSURLCredentialStorage sharedCredentialStorage];
[credentials setDefaultCredential:defaultCredential forProtectionSpace:protectionSpace];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
[configuration setURLCredentialStorage:credentials];
[configuration setHTTPAdditionalHeaders: #{#"Accept": #"text/plain"}];
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
[manager.securityPolicy setAllowInvalidCertificates:YES];
manager.responseSerializer = [AFPropertyListResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"text/plain"];
NSURL *URL = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURL *filePath = [NSURL fileURLWithPath:remindersPlistFile];
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:filePath progress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(#"Upload Error: %#", error);
} else {
//NSLog(#"Upload Success");
NSLog(#"Upload Success: %# %#", response, responseObject);
}
}];
[uploadTask resume];
}
The console shows this:
Upload Success: <NSHTTPURLResponse: 0x8d4ec50> { URL: https://[.....]/CodeTests/Reminders/Reminders.plist } { status code: 200, headers {
"Accept-Ranges" = bytes;
Connection = "Keep-Alive";
"Content-Length" = 1324;
"Content-Type" = "text/plain";
Date = "Mon, 10 Mar 2014 14:48:13 GMT";
Etag = "\"3f06e5-52c-4f430180dae80\"";
"Keep-Alive" = "timeout=5, max=100";
"Last-Modified" = "Sun, 09 Mar 2014 17:48:26 GMT";
Server = "Apache/2.2.17 (Unix) mod_ssl/2.2.17 OpenSSL/0.9.7l DAV/2";
} } {
reminders = (
{
completed = 1;
created = "2014-03-09 17:47:41 +0000";
description = "";
title = Reminder;
updated = "2014-03-09 17:47:41 +0000";
},
{
completed = 1;
created = "2014-03-08 09:47:58 +0000";
description = "Orza!!! Ma orza in fretta... Ah: funziona? Ebbene, s\U00ec! O no?\n";
title = "Reminder Orza";
updated = "2014-03-08 11:39:43 +0000";
},
{
completed = 0;
created = "2014-03-07 11:09:59 +0000";
description = "Whatever you like; and of course you can even make it quite long.\n\nYeooww..\nReally long!\n\n\n\n";
title = "Reminder A";
updated = "2014-03-08 11:34:24 +0000";
}
);
version = "1.0";
}
The only catch is that when I reopen the app, it jumps back to the test reminders I did manually put on the server: the Reminders.plist gets never changed.
Thanks!
I'm assuming your NSLog of the responseObject is confirming that the plist was successfully received by the server. If so, then that may eliminate the above "upload" code as the source of the problem. You may want to inspect the data on your server (not through the app, but manually inspect it yourself) and see whether your new list of reminders is there or not. It seems that there a couple of possible logical possibilities:
If the updated data is not there, then look at your server's "save" logic, as it would appear to be failing.
If it is there, then you should look at your client's "retrieve" logic. I wonder, for example, if your app is caching the responses to its requests, and thus when you attempt to download again, perhaps you're getting the cached original response. I'd try turning off caching.
In these cases, a tool like Charles can be useful, where you can inspect the requests and responses that you're getting. That can be helpful in narrowing down precisely where the problem is occurring.
Taking a closer look at your request, I notice that you're not specifying the request type. I would have thought that your request would be a POST:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
[request setHTTPMethod:#"POST"];
[request setValue:#"text/plain" forHTTPHeaderField:#"Accept"]; // I'd also personally set the `Accept` here at the request, not at the `NSURLSessionConfiguration`
Having said that, given that your web service is successfully reporting your plist data back at you, I would have inferred that it successfully received it on the basis of the evidence you shared with us thus far. But maybe the failure to make the request a POST request means that the web service concluded it didn't need to save anything.
Generally, when interfacing with a web service, rather than tweaking the request, like I have above, I'd encourage you to post data using one of the standard AFHTTPSessionManager variations of the POST method (one is for multipart/form-data requests, the other is for other requests). But I can't figure out what your server is doing on the basis of what you've provided thus far (e.g. it makes no sense that the server is sending the body of your request back to you at all; it makes no sense that the server would appear to have received your request, but doesn't do anything with it and doesn't report some error; etc.). So maybe try making the request a POST request and see if that fixes it. If not, run Charles on your old ASIHTTP source code, and you'll see precisely what the old request looks like and you should be able to reproduce it with AFNetworking.

How to config my server to providing the video file play with MPMoviePlayerViewController?

I want to using MPMoviePlayerViewController to play the video file on my server. But It not works.
My iOS code are as bellow:
NSURL *mediaURL = [[NSURL alloc] initWithString:[[NSString alloc] initWithFormat:#"http://127.0.0.1:3000/media/%#", #"sample_iTunes.mov"]];
self.player = [[MPMoviePlayerViewController alloc] initWithContentURL: mediaURL];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(movieFinishedCallback:) name:MPMoviePlayerPlaybackDidFinishNotification object:self.player.moviePlayer];
self.player.moviePlayer.shouldAutoplay = YES;
self.player.moviePlayer.movieSourceType = MPMovieSourceTypeStreaming;
[self presentMoviePlayerViewControllerAnimated:self.player];
[self.player.moviePlayer prepareToPlay];
[self.player.moviePlayer play];
-(void)movieFinishedCallback:(NSNotification*)aNotification
{
NSLog(#"%#",[aNotification valueForKey:#"userInfo"]);
MPMoviePlayerViewController* theMovie = [aNotification object];
}
And I got the error message from NSNotification is:
MPMoviePlayerPlaybackDidFinishReasonUserInfoKey = 1;
error = "Error Domain=MediaPlayerErrorDomain Code=-11850 \"Operation Stopped\" UserInfo=0x1d51b640 {NSLocalizedDescription=Operation Stopped}";
Then I changed the URL to
http://km.support.apple.com/library/APPLE/APPLECARE_ALLGEOS/HT1211/sample_iTunes.mov
But it works well. Since the two files are exactly the same file. I think there must be something wrong with my remote server. And here is the response headers from my server :
HTTP/1.1 200 OK
Content-Length: 3284257
Date: Mon, 14 Jan 2013 06:06:12 GMT
Last-Modified: Sun, 13 Jan 2013 10:44:02 +0000
Accept-Ranges: bytes
Content-Type: video/quicktime
Connection: close
Server: Jetty(7.6.1.v20120215)
In additional, The video can play on my macbook with Chrome/Safari, But it can't play on iPhone with browsers too.
After correcting the content URL, if the Error Domain=MediaPlayerErrorDomain Code=-11850 \"Operation Stopped\" error still happens, it may be because your HTTP server does not support byte-range requests. See MPMoviePlayerPlaybackDidFinishNotification is called immediately for more information.
You are pointing your MPMoviePlayerController at 127.0.0.1, which is the current device's IP. So your iOS app is looking for a web server hosting that file on your iPhone. Your code looks fine otherwise, you've just got to host the .mov somewhere valid.

Posting from iOs with RestKit to WCF

Im trying to make a post from Xcode (with RestKit) to a WCF Service. Here is my code:
NSDictionary *queryParams;
queryParams = [NSDictionary dictionaryWithObjectsAndKeys:
#"1.0",#"oauth_version",
#"33333",#"oauth_nonce",
#"HMAC-SHA1",#"oauth_signature_method",
#"1323",#"oauth_timestamp",
#"rrr", #"oauth_consumer_key",
#"t672hpIerersdsfc", #"oauth_signature", nil];
RKObjectManager *objectManager = [RKObjectManager sharedManager];
RKURL *baseURL = [RKURL URLWithBaseURLString:#"http://myServer:80/appccservice.svc"];
objectManager = [RKObjectManager objectManagerWithBaseURL:baseURL];
objectManager.client.baseURL = baseURL;
objectManager.client.authenticationType = RKRequestAuthenticationTypeOAuth1;
objectManager.client.username = #"lsantos";
objectManager.client.password = #"clave";
NSString *resourcePath = [#"/json/post" stringByAppendingQueryParameters:queryParams];
[RKObjectManager sharedManager].serializationMIMEType = RKMIMETypeJSON;
RKObjectMapping *userSerialization = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
[userSerialization mapKeyPath:#"name" toAttribute:#"nombreNovia"];
[userSerialization mapKeyPath:#"tel" toAttribute:#"fecha"];
RKObjectMapping *serialize = [userSerialization inverseMapping];
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:serialize forClass:[User class]];
[[RKObjectManager sharedManager].router routeClass:[User class] toResourcePath:resourcePath forMethod:RKRequestMethodPOST];
User *data = [User new];
data.nombreNovia = #"joaquin";
data.fecha = #"4344";
[[RKObjectManager sharedManager] postObject:data delegate:self];
I used the RKLogConfigureByName and got this:
Request:
URLRequest '<NSMutableURLRequest http://myServer:80/appccservice.svc/json/post?oauth_timestamp=32432304&oauth_nonce=123123&oauth_version=1.0&oauth_consumer_key=key&oauth_signature_method=HMAC-SHA1&oauth_signature=t672hasdfdsfsdsdceVBFqKc>'. HTTP Headers: {
Accept = "application/json";
"Accept-Encoding" = gzip;
Authorization = "OAuth oauth_signature=\"bEmMvO3sdfsdfsdfcdc\"";
"Content-Length" = 31;
"Content-Type" = "application/json";
}. HTTP Body: {"tel":"4344","name":"joaquin"}.
I think that everything is ok with that Request, the data is in the HTTP Body.
Meanwhile in the reponse:
Connection = "Keep-Alive";
"Content-Length" = 7;
"Content-Type" = "application/json; charset=utf-8";
Date = "Thu, 27 Sep 2012 12:34:08 GMT";
"Proxy-Connection" = "Keep-Alive";
Server = "Microsoft-IIS/7.5";
Via = "1.1 ISASERVERSM";
"X-Powered-By" = "ASP.NET";
}
My method return empty when the object is null, so the WCF is not seeing the data.
2012-09-27 08:34:05.056 RESTPRUEBA2[6698:fb03] T restkit.network:RKResponse.m:231 Read response body: "empty"
And im getting this error on the didFailLoadWithError
2012-09-27 09:11:25.500 RESTPRUEBA2[6698:fb03] error encontrado: Error Domain=JKErrorDomain Code=-1 "Expected either '[' or '{'." UserInfo=0x88d89c0 {JKAtIndexKey=7, JKLineNumberKey=1, NSLocalizedDescription=Expected either '[' or '{'.}
The funny part is that i proved my WCF using RestClient addon in firefox with the same URL and data and everything worked fine.
This issue indicates that the JSON returned by your remote system does not parse correctly. The JKErrorDomain is from JSONKit, the JSON parser in use. Take your response body and validate it on http://jsonlint.com/ and fix the parse errors.

Resources