Как да използвате NSCache с CoreData

Разработих проект, при който потребител рисува изображение върху платно, аз го съхранявам във файла с помощта на CoreData, имам връзка "един към много", наречена папка към файлове. Така че тук всичко са изображения. Извличам изображенията от файлове, преоразмерявам според височината на клетката на моята таблица и го показвам на маса. След като се покаже, искам да кеширам изображенията.

Имам и някои етикети в клетката на папката, които ми дават някаква информация относно моите файлове, които актуализирам в движение. Също така плъзгам клетките, за да го маркирам завършен и го премествам в долната част на клетката.

Също така показвам едни и същи файлови изображения в различни изгледи в зависимост от това как потребителят ги пита.

Искам да знам кой е най-добрият метод за това, четох в мрежата, има много методи, GCD, NSOperationQueue и много други.

Кой метод ще е най-подходящ за мен.

Искам да покажа някакъв код

- (UITableViewCell *)tableView:(FMMoveTableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    FolderCell *tableCell = (FolderCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (tableCell == nil)
    {
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"FolderCell" owner:self options:nil];
        tableCell = [nib objectAtIndex:0];
    }

    NSMutableArray *categoryArray = [[self.controller fetchedObjects]mutableCopy];

    Folder *category = [categoryArray objectAtIndex:[indexPath row]];

    [tableCell configureCellWithNote:category]; //This function is written in my FolderCell.m function        

}
    return tableCell; 
}


-(void)configureCellWithNote:(Folder *)category
{    
    self.category = category;


    UIImage *image1 = [UIImage imageWithData:category.noteImage];
    CGSize newSize;


    if(image1.size.width == 620 && image1.size.height == 200)
   {              
       newSize = CGSizeMake(300, 97);
   }     

    UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);

    [image1 drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];


    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();     

    self.notesImage.image = newImage;    
 }

Това, което се случва тук, е, че configureCellWithNote отнема много време, защото преоразмерява изображения. Моля, помогнете ми да реша как може да се реши този проблем с производителността.

Поздрави Rajit


person Ranjit    schedule 11.12.2013    source източник
comment
Мисля, че ipmcc отговори на въпроса за кеша. Между другото, може и да съм склонен да прокарам тази логика за преоразмеряване на изображението в категория UIImage. Освен това какво ще стане, ако изображението не е 620x200, това няма да работи правилно. По-безопасно е да използвате общ алгоритъм за преоразмеряване, за който можете да използвате UIViewContentModeScaleAspectFill или UIViewContentModeScaleAspectFit, напр. stackoverflow.com/questions/10491080/   -  person Rob    schedule 11.12.2013


Отговори (1)


Ако просто искате да разбъркате операцията за преоразмеряване във фонова нишка, можете да направите нещо подобно:

- (void)configureCellWithNote:(Folder *)category
{
    self.category = category;

    UIImage *image1 = [UIImage imageWithData:category.noteImage];
    CGSize newSize;

    if(image1.size.width == 620 && image1.size.height == 200)
    {
        newSize = CGSizeMake(300, 97);
    }

    dispatch_async(dispatch_get_global_queue(0,0), ^{
        UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);

        [image1 drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];

        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

        UIGraphicsEndImageContext();

        dispatch_async(dispatch_get_main_queue(), ^{
            self.notesImage.image = newImage;
        });
    });

}

Ако искате да кеширате резултатите, тогава трикът ще бъде да излезете с добър кеш ключ. За съжаление е трудно да се каже от това, което сте публикували, кой би бил добър кеш ключ. Със сигурност ще трябва да включва размера, но също така ще трябва да включва нещо, което го свързва с категорията. Предполагам, че ако не друго, можете да използвате NSManagedObjectID за категорията, но мисля, че това ще бъде специфично за всеки контекст на управляван обект, който имате. Ако приемем, че има свойство на Folder, наречено uniqueName, внедряването на кеширане може да изглежда така:

- (UIImage*)imageForCategory: (Folder*)category atSize: (CGSize)size
{
    // A shared (i.e. global, but scoped to this function) cache
    static NSCache* imageCache = nil;
    // The following initializes the cache once, and only once
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        imageCache = [[NSCache alloc] init];
    });

    // Generate a cache key sufficient to uniquely identify the image we're looking for
    NSString* cacheKey = [NSString stringWithFormat: @"%@|%@", category.uniqueName, NSStringFromSize((NSSize)size)];
    // Try fetching any existing image for that key from the cache.
    UIImage* img = [imageCache objectForKey: cacheKey];

    // If we don't find a pre-existing one, create one
    if (!img)
    {
        // Your original code for creating a resized image...
        UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
        UIImage* image1 = [UIImage imageWithData:category.noteImage];
        [image1 drawInRect:CGRectMake(0,0,size.width,size.height)];
        img = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        // Now add the newly-created image to the cache 
        [imageCache setObject: img forKey: cacheKey];
    }

    // Return the image 
    return img;
}
person ipmcc    schedule 11.12.2013
comment
Здравей, @ipmcc, благодаря за отговора, имам атрибут, наречен име на папка. Така че кодът, който показахте, трябва ли да го напиша в tableCell.m или в моята функция ViewController.m CellForRowAtIndexPath. И само този код ли ще се справи с кеширането на изображения? Съжалявам, че питам това, но съм нов в това. Можете ли да обясните кода, който сте написали. - person Ranjit; 11.12.2013
comment
Вероятно има смисъл да бъде в класа ViewController, а не в класа TableCell. Този код трябва да управлява кеширането на изображенията. Ако кешът не съдържа изображение за конкретна папка и размер, той ще го генерира. - person ipmcc; 11.12.2013
comment
+1 за отличния отговор. Между другото, като се има предвид, че NSCache промени поведението си в iOS 7, може да искате да подкласирате NSCache, за да сте сигурни, че отговаряте на натиска на паметта. Вижте stackoverflow.com/questions/19546054/. Що се отнася до кеширането в контролер за изглед или клас клетки, ако има някаква възможност да имате нужда от тези преоразмерени изображения другаде, вероятно не го искате в нито един от тези класове, а по-скоро в някакъв модел контролер или сингълтон, който го прави достъпен за други контролери за преглед. - person Rob; 11.12.2013
comment
@ipmcc, Роб, искам го в клас клетка, защото аз правя всички операции тук, така че защо трябва да го пиша във viewController - person Ranjit; 11.12.2013
comment
Предполагам, че можете да го направите класов метод на вашия клетъчен клас. Практически погледнато, няма значение къде го поставяте. Философски казано, трябва да го поставите на най-конкретното място, където всички неща, които трябва да го извикат, могат да го достигнат. Ако е общ за всички клетки, но не се използва от нищо друго, тогава методът на клас в класа tableCell има смисъл. - person ipmcc; 11.12.2013
comment
@Ranjit Това е добре. Но тъй като приложението расте и започвате да използвате миниатюри навсякъде, може да не искате отделен кеш за миниатюрните изображения за всеки клас клетки (или за всеки контролер за изглед). Това е особено вярно, ако може да показвате конкретна миниатюра някъде другаде, освен този конкретен изглед на маса. Аз лично поддържам кешове за различни функционални категории (напр. миниатюри, изображения в пълен размер и т.н.), а не за конкретни индивидуални класове на потребителския интерфейс. Но можете да поставите своя кеш(ове) навсякъде, където има смисъл за вашата конкретна архитектура на приложението. - person Rob; 11.12.2013
comment
Добре, благодаря и на двама ви. Още нещо, може ли някой да ми обясни накратко този код - person Ranjit; 11.12.2013
comment
Прегледах и добавих коментари към кода, като бях възможно най-ясен. Ако имате нужда от повече обяснения от това, ще трябва да сте по-конкретни за това, което не е ясно. - person ipmcc; 11.12.2013
comment
@Rob, използвах процеса на кеширане, както е обяснено от ipmcc, но ако не стартирам метода drawRect във фонова нишка, тогава приложението ми все още ще бъде бавно. Но не разбирам, след като изображението се кешира, приложението няма да влезе. DrawRect метод, но все пак времевият профил показва, че отнема 60% от времето - person Ranjit; 12.12.2013
comment
@Ranjit Изглежда мислиш, че не съм съгласен с идеята за изобразяване на изображението във фонов режим. Точно обратното, мисля, че трябва да направите този скъп процес на фонова опашка. Само се карах с презумпцията, че клетъчният клас трябва да притежава кеша. - person Rob; 12.12.2013
comment
@Rob, Нищо подобно, просто исках да изчистя съмнението си. Малко ме е страх да използвам многопоточност, защото потребителският ми интерфейс не се зарежда правилно. Но както и да е, ще го разбера. Благодаря за ценния отговор. Ще използвам подхода на фоновата нишка - person Ranjit; 12.12.2013
comment
@ipmcc, работя върху приложение за ipad, което има раздели, така че тук е случаят, ако съм в различен раздел, наречен настройки, и натисна бутона за начална страница, след което отново отворя приложението, то ще ме отведе до страница с настройки, сега, когато превключвам раздела, който съдържа изображения, това, което се случва е, че не зарежда изображенията от кеша, защото получава нулеви изображения и затова отново ги създава, поради което мога да видя първо празната таблица и след това изображенията се зареждат върху нея. Това ли е защото кешът се инициализира само веднъж? Трябва ли да инициализираме кеша отново. - person Ranjit; 13.12.2013
comment
Невъзможно е да знам, без да го видя в действие, но една основна характеристика на NSCache е, че операционната система може да накара нещата да бъдат извадени от кеша по всяко време. Ако искате силна гаранция, че нещата, които сте поставили в кеша, все още ще бъдат там, NSCache не е правилният инструмент. - person ipmcc; 13.12.2013
comment
@Rob, ти ми предложи да подкласифицирам NSCache, защото не изгонва обекти поради предупреждение за паметта. Забелязах още един случай, когато приложението отиде на заден план, кешът става празен. така ли е - person Ranjit; 16.12.2013
comment
Да, изглежда, че това е нещо: stackoverflow.com/questions/13163480/nscache-and- фон - person ipmcc; 16.12.2013