AFNetworking + NsOperationQueue - Download duizenden afbeeldingen

Ik ben bezig met een taak (alleen iOS5 +) waarbij duizenden afbeeldingen van de server worden gedownload. De afbeeldingen behoren tot bepaalde categorieën en elke categorie kan honderden afbeeldingen bevatten. Wat ik moet doen is dit: -

1) Zorg ervoor dat de app ontbrekende afbeeldingen op de achtergrond downloadt als de app actief is (zelfs wanneer de gebruiker door een aantal andere delen van de app bladert die geen verband houden met foto's).

2) Wanneer de gebruiker op een fotocategorie klikt, moeten de afbeeldingen in die categorie als hoge prioriteit worden gedownload, omdat dit degenen zijn die onmiddellijk zichtbaar moeten zijn.

Al het bovenstaande gebeurt alleen als de afbeelding niet al offline beschikbaar is. Nadat het is gedownload, wordt de afbeelding gebruikt vanuit de lokale opslag.

Om dit op te lossen, is de logica die ik gebruik dit:

1) In AppDelegate.m begin ik in applicationDidBecomeActive om ontbrekende afbeeldingen bij te werken. Om dit te doen, maak ik een kerngegevensquery, zoek uit welke afbeeldingen ontbreken en begin ze te downloaden in een thread met prioriteit BACKGROUND. Iets zoals dit :-

 dispatch_queue_t imageDownloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(imageDownloadQueue, ^{
    [DataDownloader downloadMissingImages];
});
dispatch_release(imageDownloadQueue);

De code downloadMissingImages ziet er als volgt uit: -

NSOperationQueue *downloadQueue = [[NSOperationQueue alloc] init];
        downloadQueue.maxConcurrentOperationCount = 20;

        for(MyImage *img in matches)
        {
            NSURLRequest *request = [NSURLRequest requestWithURL:img.photoUrl];
            AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request success:^(UIImage *image) {

                [MyImage imageFromAPI:image inManagedObjectContext:document.managedObjectContext];

                NSLog(@"Successfully downloaded image for %@", img.title);      
            }];

            [downloadQueue addOperation:operation];
        }

Dit werkt, maar het blokkeert de hoofdgebruikersinterface en de app crasht na een tijdje. Dit is het moment waarop ik ongeveer 700 afbeeldingen probeer te downloaden. Met meer afbeeldingen zou het zeker crashen.

2) Wanneer een gebruiker op een categorie klikt, moet ik die afbeeldingen eerst downloaden omdat deze onmiddellijk aan de gebruiker moeten worden getoond. Ik weet niet zeker hoe ik de missingImages-aanroep kan onderbreken en zeg dat hij bepaalde afbeeldingen moet downloaden voordat deze door anderen worden geopend.

Dus eigenlijk moet ik alle ontbrekende afbeeldingen op de achtergrond downloaden, maar als de gebruiker door de fotocategorie bladert, moeten die afbeeldingen hoge prioriteit krijgen in de downloadwachtrij.

Ik heb geen idee hoe ik dit efficiënt kan laten werken. Wat denk jij?

De crashlogboeken zien er als volgt uit

PAPP(36373,0xb065f000) malloc: *** mmap(size=16777216) failed (error code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
PAPP(36373,0xb065f000) malloc: *** mmap(size=16777216) failed (error code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
Jun 24 11:39:45 MacBook-Pro.local PAPP[36373] : ImageIO: JPEG    Insufficient memory (case 4)

Bij voorbaat dank.

8

1 antwoord

Over de crash, ik vermoed dat uw app is gedood vanwege een van de twee opties:

  1. de app reageert niet meer (en reageert dus niet op het iOS-sentinelproces);

  2. te veel geheugen gebruikt in de lus, waardoor meer dan 700 aanvraagbewerkingen zijn gemaakt

Om te verduidelijken wat er echt gebeurt, moet u meer informatie over de crash verstrekken (het consolelogboek). In ieder geval laadt de fix de afbeeldingen in stukken van elk 10 of 20 (je zou zelfs 1 voor 1 kunnen gaan, als je wilt, ik zie daar niet veel problemen mee).

Over het tweede punt, hoe zit het met dit:

  1. download een afbeelding met een hogere prioriteit in de hoofdthread (uiteraard via een async-download om te voorkomen dat de gebruikersinterface wordt geblokkeerd);

  2. voordat u begint met het downloaden van een "offline" afbeelding, controleert u of de afbeelding al is gedownload in de tussentijd via een "hogere prioriteit" -download.

Om punt 2 goed te behandelen, moet u waarschijnlijk uw aangepaste bewerking in de wachtrij plaatsen in plaats van een AFImageRequestOperation om de controle uit te voeren vóór de eigenlijke download.

BEWERK:

Over het downloaden van afbeeldingen in brokken, wat u zou kunnen doen is het gebruik van verzendgroepen om uw netwerkactiviteiten te groeperen:

dispatch_group_t group = dispatch_group_create();



for (...) {
    dispatch_group_enter(group);

        NSURLRequest *request = [NSURLRequest requestWithURL:img.photoUrl];
        AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request success:^(UIImage *image) {
            [MyImage imageFromAPI:image inManagedObjectContext:document.managedObjectContext];
            NSLog(@"Successfully downloaded image for %@", img.title);

            dispatch_group_leave(group);     //<== NOTICE THIS
        }];

}

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);

In dit voorbeeld gebruik ik een verzendgroep om een ​​aantal asynchroonbewerkingen te groeperen en te wachten tot ze allemaal worden uitgevoerd; wanneer dispatch_group_wait terugkeert, kunt u een volgende ronde uitvoeren (query uitvoeren op kerngegevens en vervolgens ops verzenden).

Over uw andere vraag (hoe controleer ik of een wachtrij met een hogere prioriteit al een bepaalde afbeelding heeft gedownload), moet u een kerngegevensquery uitvoeren voordat u elke AFImageRequestOperation uitvoert; een mogelijkheid is om je eigen klasse ervan af te leiden en de start -methode te overschrijven om de controle uit te voeren.

In beide accounts zou je de logica van dit alles kunnen vereenvoudigen door de afbeeldingen één voor één te downloaden (dwz de voor (...) lus zou er niet zijn, je vraagt ​​simpelweg om de volgende afbeelding om het te downloaden en te downloaden; voor het downloaden controleer je of het er nog niet is.

Ik zou willen voorstellen om dit gemakkelijker pad te gaan.

Hoop dat het helpt.

3
toegevoegd
voor zover ik begrijp, voert u dit allemaal uit in dispatch_async (imageDownloadQueue, , dus gebruikersinterface mag niet worden geblokkeerd ...
toegevoegd de auteur sergio, de bron
gebruikt u de "chunked" -versie of uw originele code? in ieder geval begrijp ik het niet goed ... weet u zeker dat de netwerkbewerking de UI blokkeert (hoofdthema) of dat de achtergrondbewerkingen de processors te zwaar belasten (zoals in: te veel verwerking, zodat de gebruikersinterface minder tijd krijgt?) . Zou het de kerngegevens kunnen zijn die de gebruikersinterface blokkeren? misschien kunt u proberen om maxConcurrentOperationCount naar 1 te verkleinen om te zien of dingen veranderen ...
toegevoegd de auteur sergio, de bron
@ d.ennis: verzendgroepen zijn handig wanneer u een manier nodig hebt om een ​​aantal taken als een enkele eenheid af te handelen en een andere taak op die groep taken die wordt voltooid, te synchroniseren; als je de afbeeldingen gewoon een voor een download, zie ik geen verzendgroep meer nodig. Als u bijvoorbeeld 30 gelijktijdige afbeeldingsdownloads uitvoert en u een manier nodig hebt om te weten wanneer al die taken zijn voltooid, is een groep handig. ik hoop dat dit helpt.
toegevoegd de auteur sergio, de bron
@sergio Zou u ook de dispatch_group - en dispatch_group_wait -oproepen gebruiken als u de afbeelding op dat moment downloadt?
toegevoegd de auteur d.ennis, de bron
Hallo Sergio, heb je een codevoorbeeld om gespleten downloads te demonstreren zoals je suggereert?
toegevoegd de auteur Anuj Gakhar, de bron
Ook, hoe controleer ik of een wachtrij met een hogere prioriteit al een bepaalde afbeelding heeft gedownload? Om het even welke codesteekproeven voor dat?
toegevoegd de auteur Anuj Gakhar, de bron
Heb de vraag aangepast om crashlogs op te nemen - ik genereer miniaturen van elke afbeeldingdownload..dus ik denk dat 700 afbeeldingen hier zeker het geheugenlek veroorzaken ..
toegevoegd de auteur Anuj Gakhar, de bron
bedankt voor de code ... Ik merk dat AFHTTpClient wachtrij heeft ingesteldBatchOfHTTPRequestOperations die ook verzendgroepen gebruikt, dus misschien zou ik dat kunnen gebruiken ... zal deze code op de achtergrond worden uitgevoerd zonder de UI te blokkeren ... momenteel lijkt het de gebruikersinterface te blokkeren ..
toegevoegd de auteur Anuj Gakhar, de bron
Ik gebruik dispatch_async maar terwijl de download bezig is, wordt de gebruikersinterface geblokkeerd..cant klik overal
toegevoegd de auteur Anuj Gakhar, de bron
Ik probeer het als volgt te doen: - 1) In de rasterweergave in de herbruikbare cel, download ik de afbeelding voor die cel naarmate de cel in beeld komt. Dit wordt afgehandeld door GMGridView, dus ik doe hier niet veel behalve dat ik controleer of de afbeelding voor die cel niet bestaat en download het dan. 2) Vervolgens probeer ik in het AppDelegate een achtergrondproces uit te voeren dat alle afbeeldingen in stilte downloadt (zodat de gebruiker niet hoeft te klikken en naar elke foto hoeft te bladeren om deze te downloaden). Dit achtergrondproces wordt afgevuurd met behulp van dispatch_async en downloadt de ontbrekende afbeeldingen. Dit is wat de gebruikersinterface blokkeert.
toegevoegd de auteur Anuj Gakhar, de bron
En ik probeerde maxConcurrentOperationCount = 1 en probeerde ook gecomprimeerde downloads (alleen 20 operaties toevoegen aan de wachtrij BatchOfHTTPRequestOperations tegelijk) en dan wachten tot het voltooid was. Ui nog steeds geblokkeerd. Dit is geen probleem met de simulator, omdat het ook de gebruikersinterface op het apparaat blokkeert. Ik begin te denken dat dit een geval is van processors die worden opgevijzeld ...
toegevoegd de auteur Anuj Gakhar, de bron