I have a task to do without ARC. Previously I didn't use it (started studying ios development recently). I have a class that represents http request, it conforms to NSURLSessionDownloadDelegate protocol. And also I have following code:
-(void)executeWithRelativeUrl:(NSString *)relativeUrl andSuccessBlock: (void(^) (NSData*))successBlock {
NSURL *url = [[NSURL alloc] initWithString:relativeUrl relativeToURL:self.baseUrl];
[self setSuccessBlock:successBlock];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request];
[downloadTask resume];
[request release];
[url release];
}
that creates url session and starts download task. I'm dealing with task results in following method:
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSData *data = [NSData dataWithContentsOfURL:location];
dispatch_async(dispatch_get_main_queue(), ^{
self.successBlock(data);
});
}
Now the question is: do I need to release session, download task and location url in the end of the last method? Or it will be done for me? I'm asking this because I created it in the first method (except for url), and as I understand the one who is responsible for releasing the object is also me. Thanks!
The Golden Rule is very simple. Did you say alloc or copy or retain? No? Then you don't need to say release (and you must not do so).
(You need to release the url and the request for that reason, and you are doing so. So memory management is now complete.)
Related
What is the best way to convert NSURLConnection to NSURLSession ?
error message: (Xcode)
ViewController.m:342:45: 'stringByAddingPercentEscapesUsingEncoding:' is deprecated: first deprecated in iOS 9.0 - Use -stringByAddingPercentEncodingWithAllowedCharacters: instead, which always uses the recommended UTF-8 encoding, and which encodes for a specific URL component or subcomponent since each URL component or subcomponent has different rules for what characters are valid.
my code :
-(void)downloadZip
{
NSLog(#"Start Downloading Zip File");
NSDate *myDate = (NSDate *)[[NSUserDefaults standardUserDefaults] objectForKey:#"LastUpdate"];
NSString *path = [NSString stringWithFormat:phpLinkgetZip, myDate];
NSURL *url = [NSURL URLWithString:[path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSLog(#"Checking update at Zip File : %#", path);
NSLog(#"Checking update Time : %#", myDate);
responseData = [[NSMutableData alloc] init];
NSURLRequest* updateRequest = [NSURLRequest requestWithURL: url];
NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:updateRequest delegate:self];
[connection start];
NSLog(#"Zip Downloading start...");
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[responseData setLength:0];
filesize = [[NSNumber numberWithLong: [response expectedContentLength] ] retain];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self updateZipDownloaded];
[filesize release];
[connection release];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[responseData appendData:data];
NSNumber* curLength = [NSNumber numberWithLong:[responseData length] ];
float progress = [curLength floatValue] / [filesize floatValue] ;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"Zip Downloading error");
}
If you want to overcome above error please use below code
NSCharacterSet *setPath = [NSCharacterSet URLPathAllowedCharacterSet];
NSString *strURL = [path stringByAddingPercentEncodingWithAllowedCharacters:setPath];
stringByAddingPercentEncodingWithAllowedCharacters:
Returns a new string made from the receiver by replacing all
characters not in the specified set with percent-encoded characters.
Characters passed to set below methods
(NSCharacterSet *)URLUserAllowedCharacterSet;
(NSCharacterSet *)URLPasswordAllowedCharacterSet;
(NSCharacterSet *)URLHostAllowedCharacterSet;
(NSCharacterSet *)URLPathAllowedCharacterSet;
(NSCharacterSet *)URLQueryAllowedCharacterSet;
(NSCharacterSet *)URLFragmentAllowedCharacterSet;
If you want to go NSURLSession from NSURLConnetion,do the following things
NSURL *URL = [NSURL URLWithString:#"http://example.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
// ...
}];
[task resume];
NSURLSession class and related classes provide an API for downloading content via HTTP. This API provides a rich set of delegate methods for supporting authentication and gives your app the ability to perform background downloads when your app is not running or, in iOS, while your app is suspended.
To use the NSURLSession API, your app creates a series of sessions,
each of which coordinates a group of related data transfer tasks. For
example, if you are writing a web browser, your app might create one
session per tab or window. Within each session, your app adds a series
of tasks, each of which represents a request for a specific URL (and
for any follow-on URLs if the original URL returned an HTTP redirect).
Like most networking APIs, the NSURLSession API is highly
asynchronous. If you use the default, system-provided delegate, you
must provide a completion handler block that returns data to your app
when a transfer finishes successfully or with an error. Alternatively,
if you provide your own custom delegate objects, the task objects call
those delegates’ methods with data as it is received from the server
(or, for file downloads, when the transfer is complete).
I have implemented custom protocol with session delegate as below -
- (void)startLoading {
NSMutableURLRequest *mReq = [self.request mutableCopy];
NSURL *url = [[self request] URL];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
...
...
if(!_mySession) {
_mySession = [NSURLSession sessionWithConfiguration:config
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
}
.....
}
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler{
NSURLAuthenticationChallenge* challengeWrapper = [[NSURLAuthenticationChallenge alloc] initWithAuthenticationChallenge:challenge sender:[[CustomAuthChallengeWrappers alloc] initWithSessionCompletionHandler:completionHandler]];
if(self.client){
if([self.client respondsToSelector:#selector(URLProtocol:didReceiveAuthenticationChallenge:)]) {
debugLog("auth-challenge");
[self.client URLProtocol:self didReceiveAuthenticationChallenge:challengeWrapper];
}
}
}
If client uses NSURLConnection, it is working fine. If I use NSURLSession on client side, its forwarding the Auth challenge but not receiving back in custom protocol.
Challenge wrapper is implemented as per this link:
NSURLProtocol Challenge wrapper
Am I missing anything for NSURLSession?
Just to update it is working fine with iOS 10 and xcode 8 beta version. Looks like there was some problem in iOS 9.3 which has been fixed in this release.
I am a beginer in iOS programming. I have some problem with NSURLConnection: I have installed SWRevealViewController https://github.com/John-Lluch/SWRevealViewController and when my app is loading Data from server, I can't use interaction with screen. I can't open my SWR-menu while Data is loading.
Here is my SWR in viewDidLoad:
SWRevealViewController *revealViewController = self.revealViewController;
if ( revealViewController ) {
[self.openMenyItmet setTarget: self.revealViewController];
[self.openMenyItmet setAction: #selector( revealToggle: )];
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
After that, I called Get method in viewDidLoad:
[self GetQUIZ];
Method detail:
- (void)GetQUIZ {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
NSString *url = [NSString stringWithFormat:#"http://stringlearning.com/api/v1/user-quiz?token=%#",[[NSUserDefaults standardUserDefaults] stringForKey:#"token"]];
[request setURL:[NSURL URLWithString: url]];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setValue:[UIDevice currentDevice].name forHTTPHeaderField:#"device"];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSLog(#"Left menu, User details: %#", [[NSString alloc] initWithData:[request HTTPBody] encoding:NSUTF8StringEncoding]);
NSLog(#"%#", [request allHTTPHeaderFields]);
if(conn) {
NSLog(#"Connection Successful");
} else
NSLog(#"Connection could not be made");
And then I use data in connectionDidFinishLoading:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSError *deserr = nil;
NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:responseData options: 0 error: &deserr];
I read that i should use async methods, but I never use it before. Would you write some detail solution ?
Maybe, does have different path?
I would be very grateful for the help!
I'd suggest starting with NSURLSession, which is a modern API that will accomplish the same thing, asynchronously.
To use NSURLSession, you need a few piece of the puzzle:
A web address to reach, and optionally any payload or custom headers.
An instance of NSURL: where you're downloading from and an NSURLRequest to wrap it in.
An NSURLSessionConfiguration, which handles things like caching, credentials and timeouts.
The session itself.
You need an NSURLSessionTask instance. This is the closest object to your NSURLConnection. It has callbacks via delegate or a completion block, if you just need to know when it finishes.
Here's how this would look in code:
// 1. The web address & headers
NSString *webAddress = [NSString stringWithFormat:#"http://stringlearning.com/api/v1/user-quiz?token=%#",[[NSUserDefaults standardUserDefaults] stringForKey:#"token"]];
NSDictionary <NSString *, NSString *> *headers = #{
#"device" : [UIDevice currentDevice].name,
#"Content-Type" : #"application/x-www-form-urlencoded"
};
// 2. An NSURL wrapped in an NSURLRequest
NSURL* url = [NSURL URLWithString:webAddress];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 3. An NSURLSession Configuration
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
[sessionConfiguration setHTTPAdditionalHeaders:headers];
// 4. The URLSession itself.
NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:sessionConfiguration];
// 5. A session task: NSURLSessionDataTask or NSURLSessionDownloadTask
NSURLSessionDataTask *dataTask = [urlSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}];
// 5b. Set the delegate if you did not use the completion handler initializer
// urlSession.delegate = self;
// 6. Finally, call resume on your task.
[dataTask resume];
This will run asynchronously, allowing your UI to remain responsive as your app loads data.
When you send a request on the main thread, like you are doing now, your UI, which is always performed on the main thread, is blocked, waiting for the request to finish and process. So you should perform all your network on a background thread, asynchronously. I would recommend first to check the networking library AFNetworking , it could simplify most of your networking problems.
Welcome to SO. You should know that NSURLConnection was deprecated in iOS 9. You should be using NSURLSession instead. The approach is very similar. You can take the NSURLRequest you've created and pass it to the sharedSession object, which is set up for async requests. The simplest way to deal with it is to use the call dataTaskWithRequest:completionHandler:, which takes a completion block. In your completion block you provide code that handles both success and failure.
I want to do upload the data that downloaded from the server in the share extension, but the upload task is delayed, it will proceed in the next time I push post button in my share extension. I found this message in Document describes "discretionary", does it mean that the upload task is started when the APP is in the background status, so the system might delay do upload.
For transfers started while your app is in the background, the system always starts transfers at its discretion—in other words, the system assumes this property is YES and ignores any value you specified.
Here are the codes:
- (void)didSelectPost {
NSURLSession *session = [self configureSession];
NSString *downLoadStr = [NSString stringWithFormat:#"https://apis.live.net/v5.0/%#/content?access_token=%#", [shareProfile objectForKey:#“PATH”], accesToken];
mDownloadTask = [session downloadTaskWithURL:downUrl];
[mDownloadTask resume];
[self.extensionContext completeRequestReturningItems:[self.extensionContext inputItems] completionHandler:nil];
}
- (NSURLSession *) configureSession {
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration* config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:#"com.test.background_shareext"];
// To access the shared container you set up, use the sharedContainerIdentifier property on your configuration object.
config.sharedContainerIdentifier = #"group.test.background”;
session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
});
return session;
}
- (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
if (location != nil) {
NSString *str = [NSString stringWithContentsOfFile:[location path] encoding:NSUTF8StringEncoding error:nil];
char *utfString = [[#"" stringByAppendingFormat:#"%#\n%#\n", str, #"test"] UTF8String];
NSData *uploadData = [NSData dataWithBytes:utfString length:strlen(utfString)];
NSString *uploadUrlStr = [NSString stringWithFormat:#"https://apis.live.net/v5.0/%#/files/%#?suppress_response_codes=true&overwrite=true&access_token=%#", path, file, accesToken];
NSURL *uploadUrl = [NSURL URLWithString:uploadUrlStr];
NSMutableURLRequest *uploadReq = [NSMutableURLRequest requestWithURL:uploadUrl cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:HTTP_REQUEST_TIMEOUT_INTERVAL];
[uploadReq setHTTPMethod:#"PUT"];
[uploadReq setHTTPBody:uploadData];
NSURLSession *session = [self configureSession];
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithStreamedRequest:uploadReq];
[uploadTask resume];
}else{
NSLog(#"download task location is nil");
}
}
Update:
After I share some times, the download task might stop, and runs until I share later.
I have a test app setup and it successfully downloads content from the network even if the user switches apps while a download is in progress. Great, now I have background downloads in place. Now I want to add caching. There is no point to me downloading images more than once, b/c of system design, given an image URL I can tell you the content behind that URL will never change. So, now I want to cache the results of my download using apple's built in in-memory/on-disk cache that I've read so much about (as opposed to me saving the file manually in NSCachesDirectory and then checking there before making new request, ick). In an attempt to get caching working on top of this working code, I added the following code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Set app-wide shared cache (first number is megabyte value)
[NSURLCache setSharedURLCache:[[NSURLCache alloc] initWithMemoryCapacity:60 * 1024 * 1024
diskCapacity:200 * 1024 * 1024
diskPath:nil]];
return YES;
}
When I create my session, I've added two NEW lines (URLCache and requestCachePolicy).
// Helper method to get a single session object
- (NSURLSession *)backgroundSession
{
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:#"com.example.apple-samplecode.SimpleBackgroundTransfer.BackgroundSession"];
configuration.URLCache = [NSURLCache sharedURLCache]; // NEW LINE ON TOP OF OTHERWISE WORKING CODE
configuration.requestCachePolicy = NSURLRequestReturnCacheDataElseLoad; // NEW LINE ON TOP OF OTHERWISE WORKING CODE
session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
});
return session;
}
Then, just to be ultra redundant in an attempt to see caching success I switched my NSURLRequest line from
// NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL]; // Old line, I've replaced this with...
NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:2*60]; // New line
Now, when I go to download the item a 2nd time, the experience is exaclty like the first!! Takes a long time to download and progress bar is animated slow and steady like an original download. I want the data in the cache immediately!! What am I missing???
----------------------------UPDATE----------------------------
Okay, thanks to Thorsten's answer, I've added the following two lines of code to my didFinishDownloadingToURL delegate method:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)downloadURL {
// Added these lines...
NSLog(#"DiskCache: %# of %#", #([[NSURLCache sharedURLCache] currentDiskUsage]), #([[NSURLCache sharedURLCache] diskCapacity]));
NSLog(#"MemoryCache: %# of %#", #([[NSURLCache sharedURLCache] currentMemoryUsage]), #([[NSURLCache sharedURLCache] memoryCapacity]));
/*
OUTPUTS:
DiskCache: 4096 of 209715200
MemoryCache: 0 of 62914560
*/
}
This is great. It confirms the cache is growing. I presume since I'm using a downloadTask (downloads to file as opposed to memory), that that's why DiskCache is growing and not memory cache first? I figured everything would go to memory cache until that overflowed and then disk cache would be used and that maybe memory cache was written to disk before the OS kills the app in the background to free up memory. Am I misunderstanding how Apple's cache works?
This is a step forward for sure, but the 2nd time I download the file it takes just as long as the first time (maybe 10 seconds or so) and the following method DOES get executed again:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
// This shouldn't execute the second time around should it? Even if this is supposed to get executed a second time around then shouldn't it be lightning fast? It's not.
// On all subsequent requests, it slowly iterates through the downloading of the content just as slow as the first time. No caching is apparent. What am I missing?
}
What do you make of my edits above? Why am I not seeing the file returned very quickly on subsequent requests?
How can I confirm if the file is being served from the cache on the 2nd request?
Note that the following SO post helped me solve my problem: Is NSURLCache persistent across launches?
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Set app-wide shared cache (first number is megabyte value)
NSUInteger cacheSizeMemory = 500*1024*1024; // 500 MB
NSUInteger cacheSizeDisk = 500*1024*1024; // 500 MB
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:#"nsurlcache"];
[NSURLCache setSharedURLCache:sharedCache];
sleep(1); // Critically important line, sadly, but it's worth it!
}
In addition to the sleep(1) line, also note the size of my cache; 500MB.
According to docs you need a cache size that is way bigger than what you're trying to cache.
The response size is small enough to reasonably fit within the cache.
(For example, if you provide a disk cache, the response must be no
larger than about 5% of the disk cache size.)
So for example if you want to be able to cache a 10MB image, then a cache size of 10MB or even 20MB will not be enough. You need 200MB.
Honey's comment below is evidence that Apple is following this 5% rule. For an 8Mb he had to set his cache size to minimum 154MB.
Solution - first get all info u need it something like this
- (void)loadData
{
if (!self.commonDataSource) {
self.commonDataSource = [[NSArray alloc] init];
}
[self setSharedCacheForImages];
NSURLSession *session = [self prepareSessionForRequest];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[BaseURLString stringByAppendingPathComponent:#"app.json"]]];
[request setHTTPMethod:#"GET"];
__weak typeof(self) weakSelf = self;
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
NSArray *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
weakSelf.commonDataSource = jsonResponse;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf updateDataSource];
});
}
}];
[dataTask resume];
}
- (void)setSharedCacheForImages
{
NSUInteger cashSize = 250 * 1024 * 1024;
NSUInteger cashDiskSize = 250 * 1024 * 1024;
NSURLCache *imageCache = [[NSURLCache alloc] initWithMemoryCapacity:cashSize diskCapacity:cashDiskSize diskPath:#"someCachePath"];
[NSURLCache setSharedURLCache:imageCache];
}
- (NSURLSession *)prepareSessionForRequest
{
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
[sessionConfiguration setHTTPAdditionalHeaders:#{#"Content-Type": #"application/json", #"Accept": #"application/json"}];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
return session;
}
After you need to download each file - in my case - make parsing of response and download images. Also before making request you need to check if cache already have response for your request - something like this
NSString *imageURL = [NSString stringWithFormat:#"%#%#", BaseURLString ,sourceDictionary[#"thumb_path"]];
NSURLSession *session = [self prepareSessionForRequest];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:imageURL]];
[request setHTTPMethod:#"GET"];
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
if (cachedResponse.data) {
UIImage *downloadedImage = [UIImage imageWithData:cachedResponse.data];
dispatch_async(dispatch_get_main_queue(), ^{
cell.thumbnailImageView.image = downloadedImage;
});
} else {
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
UIImage *downloadedImage = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
cell.thumbnailImageView.image = downloadedImage;
});
}
}];
[dataTask resume];
}
After that you can also check result with xCode Network Analyzer.
Also note as mentionted by #jcaron and documented by Apple
NSURLSession won't attempt to cache a file larger than 5% of the cache
size
Result something like
Once you set the cache and the session, you should use the session-methods to download your data:
- (IBAction)btnClicked:(id)sender {
NSString *imageUrl = #"http://placekitten.com/1000/1000";
NSURLSessionDataTask* loadDataTask = [session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:imageUrl]] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
UIImage *downloadedImage = [UIImage imageWithData:data];
NSLog(#"ImageSize: %f, %f", downloadedImage.size.width, downloadedImage.size.height);
NSLog(#"DiskCache: %i of %i", [[NSURLCache sharedURLCache] currentDiskUsage], [[NSURLCache sharedURLCache] diskCapacity]);
NSLog(#"MemoryCache: %i of %i", [[NSURLCache sharedURLCache] currentMemoryUsage], [[NSURLCache sharedURLCache] memoryCapacity]);
}];
[loadDataTask resume]; //start request
}
After the first call, the image is cached.