Jak buforować za pomocą NSURLSession i NSURLCache. Nie działa

Mam testową konfigurację aplikacji i pomyślnie pobiera zawartość z sieci, nawet jeśli użytkownik przełącza aplikacje podczas pobierania. Świetnie, teraz mam pliki do pobrania w tle. Teraz chcę dodać buforowanie. Nie ma sensu pobierać obrazów więcej niż raz, b/c projektowania systemu, biorąc pod uwagę adres URL obrazu, mogę powiedzieć, że zawartość tego adresu URL nigdy się nie zmieni. Więc teraz chcę buforować wyniki mojego pobrania za pomocą wbudowanej w pamięci/na dysku pamięci podręcznej apple, którą mam przeczytaj tyle o (w przeciwieństwie do mnie zapisanie pliku ręcznie w NSCachesDirectory i następnie sprawdzenie tam przed złożeniem nowego żądania, ick). W próba aby uruchomić buforowanie na szczycie tego kodu roboczego, dodałem następujący kod:

- (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;
}

Podczas tworzenia sesji dodałem dwie nowe linie (URLCache i 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;
}

Następnie, aby być Ultra redundantnym, próbując zobaczyć sukces buforowania, zmieniłem linię NSURLRequest z

// NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL]; // Old line, I've replaced this with...
NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:2*60]; // New line

Teraz, kiedy idę pobrać przedmiot po raz drugi, doświadczenie jest exaclty jak pierwszy!! Pobranie zajmuje dużo czasu, a pasek postępu jest animowany powoli i stabilnie, jak oryginalne pobieranie. Natychmiast chcę mieć dane w pamięci podręcznej!! Co przegapiłem???

----------------------------aktualizacja----------------------------

Ok, dzięki odpowiedzi Thorstena, dodałem następujące dwie linijki kodu do mojej metodydidFinishDownloadingToURL delegate:

- (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
    */
}
To jest świetne. Potwierdza pamięć podręczną rośnie. Zakładam, że skoro używam funkcji downloadTask(pobieranie do pliku w przeciwieństwie do pamięci), to dlatego diskcache rośnie, a nie Pamięć podręczna? Pomyślałem, że wszystko pójdzie do pamięci podręcznej, dopóki nie przepełni się, a następnie pamięć podręczna dysku zostanie użyta i może pamięć podręczna została zapisana na dysk, zanim system operacyjny zabije aplikację w tle, aby zwolnić pamięć. Nie rozumiem, jak działa pamięć podręczna Apple?

To jest krok do przodu na pewno, ale po raz drugi pobieram plik trwa tak długo, jak za pierwszym razem (może 10 sekund lub więcej) i następująca metoda jest wykonywana ponownie:

- (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?
}

Co sądzisz o moich edycjach powyżej? Dlaczego nie widzę pliku zwracanego bardzo szybko przy kolejnych żądaniach?

Jak mogę potwierdzić, czy plik jest obsługiwany z pamięci podręcznej na drugim żądaniu?

Author: John Erck, 2014-02-22

3 answers

Zauważ, że poniższy post pomógł mi rozwiązać mój problem: Czy NSURLCache jest trwały w przypadku uruchomień?

- (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!
}

Oprócz linii sleep(1), zwróć również uwagę na rozmiar mojej pamięci podręcznej; 500MB. Potrzebujesz rozmiaru pamięci podręcznej, który jest znacznie większy niż to, co próbujesz buforować. Na przykład, jeśli chcesz mieć możliwość buforowania obrazu 10MB, rozmiar pamięci podręcznej 10MB lub nawet 20MB nie wystarczy. Jeśli chcesz mieć możliwość buforowania 10MB, polecam 100MB cache. Mam nadzieję, że to pomoże!! Czy sztuczka dla mnie.

 31
Author: John Erck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:23

Rozwiązanie - najpierw zdobądź wszystkie informacje, których potrzebujesz, coś takiego

- (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;
}

Po pobraniu KAŻDEGO pliku - w moim przypadku-wykonaj parsowanie odpowiedzi i pobierz obrazy. Również przed złożeniem żądania musisz sprawdzić, czy cache ma już odpowiedź na twoje żądanie-coś takiego

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];
}

Następnie możesz również sprawdzić wynik za pomocą analizatora sieci xCode.

Należy również zauważyć, jak wspomniano przez @ jcaron i udokumentowane przez Apple

NSURLSession nie próbuje buforować pliku większego niż 5% pamięci podręcznej Rozmiar

Wynik coś jak

Tutaj wpisz opis obrazka

 8
Author: gbk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-30 15:48:13

Po ustawieniu pamięci podręcznej i sesji, powinieneś użyć metod sesji do pobrania danych:

- (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
}

Po pierwszym wywołaniu obraz jest buforowany.

 2
Author: Thorsten,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-23 04:29:00