在应用程序启动时将图像预加载到内存缓存中,使用 iOS 的 Objective c

Posted

技术标签:

【中文标题】在应用程序启动时将图像预加载到内存缓存中,使用 iOS 的 Objective c【英文标题】:Preloading images into the memory cache at application launch, using objective c for iOS 【发布时间】:2015-01-12 12:49:05 【问题描述】:

我遇到的问题是尝试在 UITableViewCells 列表中显示缩略图。我有 200 个缩略图要显示。

我的应用程序从我的远程服务器下载图像的 zip 文件,将内容解压缩到 NSDocumentDirectory。每周更新一次后,应用程序会使用 [UIImage imageWithContentsOfFile:] 显示缩略图 有一次,我知道这已被缓存,我使用 [UIImage imageNamed:] 显示缩略图

我的问题是,当我使用显示 200 个缩略图时 [UIImage imageWithContentsOfFile:] 更新后的第一个显示事件,应用有时会在几分钟后冻结,说打开的图像文件太多。

ImageIO: CGImageRead_mapData 'open' failed '/Users/cdesign/Library/Application Support/iPhone Simulator/6.1/Applications/B458A3F5-5B21-49CD-B4D8-17E5189678FA/Documents/91.png' error = 24(打开的文件太多)

一旦图像缓存在内存中,这种情况就不会发生。

然后,当我尝试单击 UITableViewCell 以继续我的“DetailController”时,我收到以下错误:

由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“无法在包中加载 NIB:“NSBundle(已加载)”,名称为“9ka-eC-wSa-view-LRp-aI-LGN”和目录“MainStoryboard” .storyboardc'' *** 首先抛出调用堆栈: (0x1bd0012 0x1473e7e 0x1bcfdeb 0x5d3ef9 0x4987e7 0x498dc8 0x5e728e 0x498ff8 0x499232 0x4994da 0x4b08e5 0x4b09cb 0x4b0c76 0x4b0d71 0x4b189b 0x4b1e93 0x4b1a88 0x80de63 0x7ffb99 0x7ffc14 0x467249 0x4674ed 0xe715b3 0x1b8f376 0x1b8ee06 0x1b76a82 0x1b75f44 0x1b75e1b 0x16ef7e3 0x16ef668 0x3b7ffc 0x2a1d 0x2945) libc++abi.dylib:终止调用抛出异常

这很奇怪,因为我没有名为“MainStoryboard.storyboardc”的文件。 我的应用没有本地化。我的项目甚至没有“en.lproj”文件夹。我的根目录中只有“MainStoryboard.storyboard”。

我不确定 2 错误是否相关。或者,如果第二个错误对第一个错误负责。这表明我的图像没有问题?

我必须说,当我测试我的应用程序时,通过使用仅返回缓存图像的修改后的“getImage”方法,第一个错误消失了,但关于“MainStoryboard.storyboard”的第二个错误仍然偶尔会发生,如果我让应用闲置超过几分钟,在“ListController”屏幕上。

这两个问题都出现在“ListController”屏幕上。该应用程序始终成功启动以显示“主”屏幕。在主屏幕上有一个指向“ListController”屏幕的链接。

但是,假设图像显示是问题,有没有办法在应用程序启动期间预加载图像,以将新更新的图像从文档文件夹预加载到内存缓存中,这样我就不必使用 [UIImage imageWithContentsOfFile:] 显示图片在 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath?

如果可以的话,我是否使用 NSCache 来实现图像预加载?

而且,如果我使用 NSCache,是用同名文件覆盖 NSCache 中的条目,还是必须先删除同名文件?

抱歉,抢先回答我最初的问题,但这可能会节省时间?

    我的应用程序包含一个故事板,名为 MainStoryboard.storyboard 目标设备:iPhone 5 Xcode 版本:4.6.3

以下是一些相关代码:

AppDelegate.m

- (BOOL)hasImageBeenUpdated:(NSString *)postureid
    BOOL result = NO;
    YTAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
    if (appDelegate.imagesupdated == nil) 
        appDelegate.imagesupdated = [[NSMutableArray alloc] init];
    
    NSString *imageid = [NSString string];
    for (int i = 0; i < appDelegate.imagesupdated.count; i++) 
        imageid = [appDelegate.imagesupdated objectAtIndex:i];
        if ([imageid isEqualToString: postureid]) 
            result = YES;
        
    
    return result;



- (UIImage *)getImage:(NSString *)postureid
    self.nsLogsOn = YES;
    YTAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
    UIImage *result = [[UIImage alloc] init];
    UIImageView *imageview = [[UIImageView alloc] init];
    NSString *imagePath = [NSString stringWithFormat:@"%@.png",postureid];
    if (self.nsLogsOn) 
        NSLog(@"appDelegate.imagesHaveBeenDownloaded: %i",appDelegate.imagesHaveBeenDownloaded);
    
    if(!appDelegate.imagesHaveBeenDownloaded)
        imageview.image = [UIImage imageNamed:imagePath];
        if (self.nsLogsOn) 
            NSLog(@"image cached no download: %@",imagePath);
        
    
    else
        if (appDelegate.imagesupdated == nil) 
            appDelegate.imagesupdated = [[NSMutableArray alloc] init];
        
        BOOL imageHasBeenUpdated = [self hasImageBeenUpdated:postureid];
        if(!imageHasBeenUpdated)
           imageview.image = [UIImage imageWithContentsOfFile:[[self documentsFilePath] stringByAppendingPathComponent:imagePath]];
            [appDelegate.imagesupdated addObject:postureid];
            if (self.nsLogsOn) 
                NSLog(@"image download: %@",imagePath);
            
        
        else
           imageview.image = [UIImage imageNamed:imagePath];
            if (self.nsLogsOn) 
                NSLog(@"image cached download: %@",imagePath);
            
        
    
    if(imageview.image==nil)
        imageview.image = [UIImage imageWithContentsOfFile:[[self documentsFilePath] stringByAppendingPathComponent:imagePath]];
        if (self.nsLogsOn) 
            NSLog(@"image cache cleared by ios: %@",imagePath);
        
    
    if(imageview.image==nil)
        imageview.image = [UIImage imageNamed:@"logo-lotus-imageview-white-c.png"];
        if (self.nsLogsOn) 
            NSLog(@"image cannot be found: %@",imagePath);
        
    
    result = imageview.image;    
    return result;

ListController.m

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

    return [keys count];


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

    NSString *key = [keys objectAtIndex:section];
    NSArray *nameSection = [posturegroups objectForKey:key];
    return [nameSection count];


- (NSString *)tableView:(UITableView *)tableView
titleForHeaderInSection:(NSInteger)section 
    NSString *key = [keys objectAtIndex:section];
    return [key capitalizedString];


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

    NSString *identifier = @"plainCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    NSString *key = [keys objectAtIndex:indexPath.section];
    NSArray *nameSection = [posturegroups objectForKey:key];
    YTPosture *thePosture = [nameSection objectAtIndex:indexPath.row];
    YTAppDelegate *appDelegate = [[YTAppDelegate alloc] init];    
    UIImageView *imageview = (UIImageView *)[cell viewWithTag:3];
    imageview.image = [appDelegate getImage:thePosture.postureid];
    imageview.backgroundColor = [appDelegate defaultColor];
    UILabel *cellLabel1 = (UILabel *)[cell viewWithTag:1];
    NSString *aStr = [NSString string];
    NSString *bStr = [NSString string];
    NSString *title = [NSString string];
    if([selection valueForKey:@"symptomExists"])
        aStr = [NSString stringWithFormat:@"%@ ",thePosture.memberOrder];
        bStr = thePosture.title;
        title = [aStr stringByAppendingString:bStr];
    
    else
        title = thePosture.title;
    
    cellLabel1.text = title;
    UILabel *cellLabel2 = (UILabel *)[cell viewWithTag:2];
    cellLabel2.text = thePosture.sanskritTransliteration;
    YTKeyController *keyController = [[YTKeyController alloc] init];
    NSMutableArray *branch = [keyController compileData];
    NSString *branchid = thePosture.branchid;
    NSString *list = @"1.0,1.0,1.0";
    NSArray *listItems = [list componentsSeparatedByString:@","];    
    for (int i = 0; i < [branch count]; i++) 
        YTBranch *theBranch = [branch objectAtIndex:i];
        if ([branchid isEqualToString:theBranch.branchid]) 
            list = theBranch.key;
            listItems = [list componentsSeparatedByString:@","];
            break;
        
        
    [cell setBackgroundColor:[UIColor colorWithRed:[[listItems objectAtIndex:0] floatValue] green:[[listItems objectAtIndex:1] floatValue] blue:[[listItems objectAtIndex:2] floatValue] alpha:1]];
    return cell;


#pragma mark - Table view delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

    // Navigation logic may go here. Create and push another view controller.
    /*
     DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:@"Nib name" bundle:nil];
     // ...
     // Pass the selected object to the new view controller.
     [self.navigationController pushViewController:detailViewController animated:YES];
     */


- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 
    UIViewController *destination = segue.destinationViewController;
    if ([destination respondsToSelector:@selector(setDelegate:)]) 
        [destination setValue:self forKey:@"delegate"];
    
    if ([destination respondsToSelector:@selector(setSelection:)]) 
        NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
        NSUInteger section = [indexPath section];
        NSString *key = [keys objectAtIndex:section];
        NSArray *nameSection = [posturegroups objectForKey:key];
        YTPosture *thePosture = [nameSection objectAtIndex:indexPath.row];
        id object = thePosture;
        NSDictionary *aselection = [NSDictionary dictionaryWithObjectsAndKeys:
                                   indexPath, @"indexPath",
                                   object, @"object",
                                   nil];
        [destination setValue:aselection forKey:@"selection"];
    

【问题讨论】:

不考虑使用SDWebImage之类的东西,甚至只是缓存部分,SDImageCache? github.com/rs/SDWebImageAFNetworking 也有图片加载功能。从头开始这样做会为您节省很多痛苦 最大。感谢您的回复,但我不完全确定这是图像问题还是我的应用程序试图找到不存在的“MainStoryboard.storyboardc”的问题。在图像方面,我真的很想知道 NSCache 是否将文件加载到内存中。那么,如果我把图片存入NSCache,是不是和[UIImage imageNamed:]效果一样? 老实说我知道如何快速编写一个将图像存储在 NSCache 中的函数,但我想知道 NSCache 是否将文件加载到内存中? 在 UITableView 中动态显示 200 个缩略图应该不会有任何问题。使用 imageWithContentsOfFile 应该按原样工作,而不需要管理内存缓存。您是否尝试过简化代码并简单地尝试在 tableView:CellForRowAtIndexPath: 中使用 imageWithContentOfFile 获取图像? [UIImage imageNamed:] 从资源包中加载并自动处理缓存 【参考方案1】:

我找到了问题所在。我觉得有点尴尬!

第一点是“MainStoryboard.storyboardc”应该存在。末尾的“c”代表“MainStoryboard.storyboard”的编译版本。

好吧,图像问题与我试图调用一个方法有关,除其他外,它正在从以下方法内部编译 SQLite 语句:

(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

另外,我还调用了另一个方法,该方法从同一方法内部循环了大约 200 个项目。

实际上,这两个例程正在为每个滚动到视图中的 UITableViewCell 运行。

这两个问题是系统崩溃和烧毁。

一旦我消除了这些问题,我决定使用 [UIImage imageWithContentsOfFile] 调用图像,没有任何问题。事实上,“ListController”现在有大约 200 个缩略图,它滚动起来像丝绸一样流畅。

我觉得浪费你的时间真是个白痴。毫无疑问,我从中吸取了很大的教训。我需要更彻底地调试和检查,并考虑每一行代码及其编写的上下文。事实上,我已经了解到,加载到上述方法中的唯一信息应该是已经索引的数据。

Champoul 建议在 Memory Leaks 模式下使用 Instruments,这就是我找到此问题根源的方法。非常感谢您的帮助...

【讨论】:

以上是关于在应用程序启动时将图像预加载到内存缓存中,使用 iOS 的 Objective c的主要内容,如果未能解决你的问题,请参考以下文章

SpriteKit:播放前将声音文件预加载到内存中?

使用 Glide v4 预加载图像不起作用

Elasticsearch将数据预加载到文件系统缓存中

当必须首先在服务器上生成图像时,将图像异步预加载到浏览器缓存中

你如何启动cropper以在fengyuanchencropper中裁剪图像

如何使用预加载内存启动应用程序/容器的“n”个实例