Code Monkey home page Code Monkey logo

fastimagecache's Introduction

Fast Image Cache Logo


Carthage compatible

Fast Image Cache is an efficient, persistent, and—above all—fast way to store and retrieve images in your iOS application. Part of any good iOS application's user experience is fast, smooth scrolling, and Fast Image Cache helps make this easier.

A significant burden on performance for graphics-rich applications like Path is image loading. The traditional method of loading individual images from disk is just too slow, especially while scrolling. Fast Image Cache was created specifically to solve this problem.

Table of Contents

Version History

  • 1.0 (10/18/2013): Initial release
  • 1.1 (10/22/2013): Added ARC support and more robust Core Animation byte alignment
  • 1.2 (10/30/2013): Added support for image format styles and canceling image requests
  • 1.3 (03/30/2014): Significant bug fixes and performance improvements

What Fast Image Cache Does

  • Stores images of similar sizes and styles together
  • Persists image data to disk
  • Returns images to the user significantly faster than traditional methods
  • Automatically manages cache expiry based on recency of usage
  • Utilizes a model-based approach for storing and retrieving images
  • Allows images to be processed on a per-model basis before being stored into the cache

How Fast Image Cache Works

In order to understand how Fast Image Cache works, it's helpful to understand a typical scenario encountered by many applications that work with images.

The Scenario

iOS applications, especially those in the social networking space, often have many images to display at once, such as user photos. The intuitive, traditional approach is to request image data from an API, process the original images to create the desired sizes and styles, and store these processed images on the device.

Later, when an application needs to display these images, they are loaded from disk into memory and displayed in an image view or are otherwise rendered to the screen.

The Problem

It turns out that the process of going from compressed, on-disk image data to a rendered Core Animation layer that the user can actually see is very expensive. As the number of images to be displayed increases, this cost easily adds up to a noticeable degradation in frame rate. And scrollable views further exacerbate the situation because content can change rapidly, requiring fast processing time to maintain a smooth 60FPS.1

Consider the workflow that occurs when loading an image from disk and displaying it on screen:

  1. +[UIImage imageWithContentsOfFile:] uses Image I/O to create a CGImageRef from memory-mapped data. At this point, the image has not yet been decoded.
  2. The returned image is assigned to a UIImageView.
  3. An implicit CATransaction captures these layer tree modifications.
  4. On the next iteration of the main run loop, Core Animation commits the implicit transaction, which may involve creating a copy of any images which have been set as layer contents. Depending on the image, copying it involves some or all of these steps: 2
    1. Buffers are allocated to manage file IO and decompression operations.
    2. The file data is read from disk into memory.
    3. The compressed image data is decoded into its uncompressed bitmap form, which is typically a very CPU-intensive operation.3
    4. The uncompressed bitmap data is then used by Core Animation to render the layer.

These costs can easily accumulate and kill perceived application performance. Especially while scrolling, users are presented with an unsatisfying user experience that is not in line with the the overall iOS experience.


1 60FPS0.01666s per frame = 16.7ms per frame. This means that any main-thread work that takes longer than 16ms will cause your application to drop animation frames.

2 The documentation for CALayer's contents property states that "assigning a value to this property causes the layer to use your image rather than [creating] a separate backing store." However, the meaning of "use your image" is still vague. Profiling an application using Instruments often reveals calls to CA::Render::copy_image, even when the Core Animation Instrument has indicated that none of the images have been copied. One reason that Core Animation will require a copy of an image is improper byte alignment.

3 As of iOS 7, Apple does not make their hardware JPEG decoder available for third-party applications to use. As a result, only a slower, software decoder is used for this step.

The Solution

Fast Image Cache minimizes (or avoids entirely) much of the work described above using a variety of techniques:

Mapped Memory

At the heart of how Fast Image Cache works are image tables. Image tables are similar to sprite sheets, often used in 2D gaming. An image table packs together images of the same dimensions into a single file. This file is opened once and is left open for reading and writing for as long as an application remains in memory.

Image tables use the mmap system call to directly map file data into memory. No memcpy occurs. This system call merely creates a mapping between data on disk and a region of memory.

When a request is made to the image cache to return a specific image, the image table finds (in constant time) the location of the desired image data in the file it maintains. That region of file data is mapped into memory, and a new CGImageRef whose backing store is the mapped file data is created.

When the returned CGImageRef (wrapped into a UIImage) is ready to be drawn to the screen, iOS's virtual memory system pages in the actual file data. This is another benefit of using mapped memory; the VM system will automatically handle the memory management for us. In addition, mapped memory "doesn't count" toward an application's real memory usage.

In like manner, when image data is being stored in an image table, a memory-mapped bitmap context is created. Along with the original image, this context is passed to an image table's corresponding entity object. This object is responsible for drawing the image into the current context, optionally further configuring the context (e.g., clipping the context to a rounded rect) or doing any additional drawing (e.g., drawing an overlay image atop the original image). mmap marshals the drawn image data to disk, so no image buffer is allocated in memory.

Uncompressed Image Data

In order to avoid expensive image decompression operations, image tables store uncompressed image data in their files. If a source image is compressed, it must first be decompressed for the image table to work with it. This is a one-time cost. Furthermore, it is possible to utilize image format families to perform this decompression exactly once for a collection of similar image formats.

There are obvious consequences to this approach, however. Uncompressed image data requires more disk space, and the difference between compressed and uncompressed file sizes can be significant, especially for image formats like JPEG. For this reason, Fast Image Cache works best with smaller images, although there is no API restriction that enforces this.

Byte Alignment

For high-performance scrolling, it is critical that Core Animation is able to use an image without first having to create a copy. One of the reasons Core Animation would create a copy of an image is improper byte-alignment of the image's underlying CGImageRef. A properly aligned bytes-per-row value must be a multiple of 8 pixels × bytes per pixel. For a typical ARGB image, the aligned bytes-per-row value is a multiple of 64. Every image table is configured such that each image is always properly byte-aligned for Core Animation from the start. As a result, when images are retrieved from an image table, they are already in a form that Core Animation can work with directly without having to create a copy.

Considerations

Image Table Size

Image tables are configured by image formats, which specify (among other things) the maximum number of entries (i.e., individual images) an image table can have. This is to prevent the size of an image table file from growing arbitrarily.

Image tables allocate 4 bytes per pixel, so the maximum space occupied by an image table file can be determined as follows:

4 bytes per pixel × image width in pixels × image height in pixels × maximum number of entries

Applications using Fast Image Cache should carefully consider how many images each image table should contain. When a new image is stored in an image table that is already full, it will replace the least-recently-accessed image.

Image Table Transience

Image table files are stored in the user's caches directory in a subdirectory called ImageTables. iOS can remove cached files at any time to free up disk space, so applications using Fast Image Cache must be able to recreate any stored images and should not rely on image table files persisting forever.

Note: As a reminder, data stored in a user's caches directory is not backed up to iTunes or iCloud.

Source Image Persistence

Fast Image Cache does not persist the original source images processed by entities to create the image data stored in its image tables.

For example, if an original image is resized by an entity to create a thumbnail to be stored in an image table, it is the application's responsibility to either persist the original image or be able to retrieve or recreate it again.

Image format families can be specified to efficiently make use of a single source image. See Working with Image Format Families for more information.

Data Protection

In iOS 4, Apple introduced data protection. When a user's device is locked or turned off, the disk is encrypted. Files written to disk are protected by default, although applications can manually specify the data protection mode for each file it manages. With the advent of new background modes in iOS 7, applications can now execute in the background briefly even while the device is locked. As a result, data protection can cause issues if applications attempt to access files that are encrypted.

Fast Image Cache allows each image format to specify the data protection mode used when creating its backing image table file. Be aware that enabling data protection for image table files means that Fast Image Cache might not be able to read or write image data from or to these files when the disk is encrypted.

Requirements

Fast Image Cache requires iOS 6.0 or greater and relies on the following frameworks:

  • Foundation
  • Core Graphics
  • UIKit

Note: As of version 1.1, Fast Image Cache does use ARC.


The FastImageCacheDemo Xcode project requires Xcode 5.0 or greater and is configured to deploy against iOS 6.0.

Getting Started

Integrating Fast Image Cache

CocoaPods

For easy project integration, Fast Image Cache is available as a CocoaPod.

Manually

Initial Configuration

Before the image cache can be used, it needs to be configured. This must occur each launch, so the application delegate might be a good place to do this.

Creating Image Formats

Each image format corresponds to an image table that the image cache will use. Image formats that can use the same source image to render the images they store in their image tables should belong to the same image format family. See Image Table Size for more information about how to determine an appropriate maximum count.

static NSString *XXImageFormatNameUserThumbnailSmall = @"com.mycompany.myapp.XXImageFormatNameUserThumbnailSmall";
static NSString *XXImageFormatNameUserThumbnailMedium = @"com.mycompany.myapp.XXImageFormatNameUserThumbnailMedium";
static NSString *XXImageFormatFamilyUserThumbnails = @"com.mycompany.myapp.XXImageFormatFamilyUserThumbnails";

FICImageFormat *smallUserThumbnailImageFormat = [[FICImageFormat alloc] init];
smallUserThumbnailImageFormat.name = XXImageFormatNameUserThumbnailSmall;
smallUserThumbnailImageFormat.family = XXImageFormatFamilyUserThumbnails;
smallUserThumbnailImageFormat.style = FICImageFormatStyle16BitBGR;
smallUserThumbnailImageFormat.imageSize = CGSizeMake(50, 50);
smallUserThumbnailImageFormat.maximumCount = 250;
smallUserThumbnailImageFormat.devices = FICImageFormatDevicePhone;
smallUserThumbnailImageFormat.protectionMode = FICImageFormatProtectionModeNone;

FICImageFormat *mediumUserThumbnailImageFormat = [[FICImageFormat alloc] init];
mediumUserThumbnailImageFormat.name = XXImageFormatNameUserThumbnailMedium;
mediumUserThumbnailImageFormat.family = XXImageFormatFamilyUserThumbnails;
mediumUserThumbnailImageFormat.style = FICImageFormatStyle32BitBGRA;
mediumUserThumbnailImageFormat.imageSize = CGSizeMake(100, 100);
mediumUserThumbnailImageFormat.maximumCount = 250;
mediumUserThumbnailImageFormat.devices = FICImageFormatDevicePhone;
mediumUserThumbnailImageFormat.protectionMode = FICImageFormatProtectionModeNone;

NSArray *imageFormats = @[smallUserThumbnailImageFormat, mediumUserThumbnailImageFormat];

An image format's style effectively determines the bit depth of the images stored in an image table. The following styles are currently available:

  • 32-bit color plus an alpha component (default)
  • 32-bit color, no alpha component
  • 16-bit color, no alpha component
  • 8-bit grayscale, no alpha component

If the source images lack transparency (e.g., JPEG images), then better Core Animation performance can be achieved by using 32-bit color with no alpha component. If the source images have little color detail, or if the image format's image size is relatively small, it may be sufficient to use 16-bit color with little or no perceptible loss of quality. This results in smaller image table files stored on disk.

Configuring the Image Cache

Once one or more image formats have been defined, they need to be assigned to the image cache. Aside from assigning the image cache's delegate, there is nothing further that can be configured on the image cache itself.

FICImageCache *sharedImageCache = [FICImageCache sharedImageCache];
sharedImageCache.delegate = self;
sharedImageCache.formats = imageFormats;

Creating Entities

Entities are objects that conform to the FICEntity protocol. Entities uniquely identify entries in an image table, and they are also responsible for drawing the images they wish to store in the image cache. Applications that already have model objects defined (perhaps managed by Core Data) are usually appropriate entity candidates.

@interface XXUser : NSObject <FICEntity>

@property (nonatomic, assign, getter = isActive) BOOL active;
@property (nonatomic, copy) NSString *userID;
@property (nonatomic, copy) NSURL *userPhotoURL;

@end

Here is an example implementation of the FICEntity protocol.

- (NSString *)UUID {
    CFUUIDBytes UUIDBytes = FICUUIDBytesFromMD5HashOfString(_userID);
    NSString *UUID = FICStringWithUUIDBytes(UUIDBytes);

    return UUID;
}

- (NSString *)sourceImageUUID {
    CFUUIDBytes sourceImageUUIDBytes = FICUUIDBytesFromMD5HashOfString([_userPhotoURL absoluteString]);
    NSString *sourceImageUUID = FICStringWithUUIDBytes(sourceImageUUIDBytes);

    return sourceImageUUID;
}

- (NSURL *)sourceImageURLWithFormatName:(NSString *)formatName {
    return _sourceImageURL;
}

- (FICEntityImageDrawingBlock)drawingBlockForImage:(UIImage *)image withFormatName:(NSString *)formatName {
    FICEntityImageDrawingBlock drawingBlock = ^(CGContextRef context, CGSize contextSize) {
        CGRect contextBounds = CGRectZero;
        contextBounds.size = contextSize;
        CGContextClearRect(context, contextBounds);

        // Clip medium thumbnails so they have rounded corners
        if ([formatName isEqualToString:XXImageFormatNameUserThumbnailMedium]) {
            UIBezierPath clippingPath = [self _clippingPath];
            [clippingPath addClip];
        }
        
        UIGraphicsPushContext(context);
        [image drawInRect:contextBounds];
        UIGraphicsPopContext();
    };
    
    return drawingBlock;
}

Ideally, an entity's UUID should never change. This is why it corresponds nicely with a model object's server-generated ID in the case where an application is working with resources retrieved from an API.

An entity's sourceImageUUID can change. For example, if a user updates their profile photo, the URL to that photo should change as well. The UUID remains the same and identifies the same user, but the changed profile photo URL will indicate that there is a new source image.

Note: Often, it is best to hash whatever identifiers are being used to define UUID and sourceImageUUID. Fast Image Cache provides utility functions to do this. Because hashing can be expensive, it is recommended that the hash be computed only once (or only when the identifier changes) and stored in an instance variable.

When the image cache is asked to provide an image for a particular entity and format name, the entity is responsible for providing a URL. The URL need not even point to an actual resource—e.g., the URL might be constructed of a custom URL-scheme—, but it must be a valid URL.

The image cache uses these URLs merely to keep track of which image requests are already in flight; multiple requests to the image cache for the same image are handled correctly without any wasted effort. The choice to use URLs as a basis for keying image cache requests actually complements many real-world application designs whereby URLs to image resources (rather than the images themselves) are included with server-provided model data.

Note: Fast Image Cache does not provide any mechanism for making network requests. This is the responsibility of the image cache's delegate.

Finally, once the source image is available, the entity is asked to provide a drawing block. The image table that will store the final image sets up a file-mapped bitmap context and invokes the entity's drawing block. This makes it convenient for each entity to decide how to process the source image for particular image formats.

Requesting Images from the Image Cache

Fast Image Cache works under the on-demand, lazy-loading design pattern common to Cocoa.

XXUser *user = [self _currentUser];
NSString *formatName = XXImageFormatNameUserThumbnailSmall;
FICImageCacheCompletionBlock completionBlock = ^(id <FICEntity> entity, NSString *formatName, UIImage *image) {
    _imageView.image = image;
    [_imageView.layer addAnimation:[CATransition animation] forKey:kCATransition];
};

BOOL imageExists = [sharedImageCache retrieveImageForEntity:user withFormatName:formatName completionBlock:completionBlock];
    
if (imageExists == NO) {
    _imageView.image = [self _userPlaceholderImage];
}

There are a few things to note here.

  1. Note that it is an entity and an image format name that uniquely identifies the desired image in the image cache. As a format name uniquely identifies an image table, the entity alone uniquely identifies the desired image data in an image table.
  2. The image cache never returns a UIImage directly. The requested image is included in the completion block. The return value will indicate whether or not the image already exists in the image cache.
  3. -retrieveImageForEntity:withFormatName:completionBlock: is a synchronous method. If the requested image already exists in the image cache, the completion block will be called immediately. There is an asynchronous counterpart to this method called -asynchronouslyRetrieveImageForEntity:withFormatName:completionBlock:.
  4. If a requested image does not already exist in the image cache, then the image cache invokes the necessary actions to request the source image for its delegate. Afterwards, perhaps some time later, the completion block will be called.

Note: The distinction of synchronous and asynchronous only applies to the process of retrieving an image that already exists in the image cache. In the case where a synchronous image request is made for an image that does not already exist in the image case, the image cache does not block the calling thread until it has an image. The retrieval method will immediately return NO, and the completion block will be called later.

See the FICImageCache class header for a thorough explanation of how the execution lifecycle works for image retrieval, especially as it relates to the handling of the completion blocks.

Providing Source Images to the Image Cache

There are two ways to provide source images to the image cache.

  1. On Demand: This is the preferred method. The image cache's delegate is responsible for supplying the image cache with source images.

    - (void)imageCache:(FICImageCache *)imageCache wantsSourceImageForEntity:(id<FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(FICImageRequestCompletionBlock)completionBlock {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // Fetch the desired source image by making a network request
            NSURL *requestURL = [entity sourceImageURLWithFormatName:formatName];
            UIImage *sourceImage = [self _sourceImageForURL:requestURL];
            
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock(sourceImage);
            });
        });
    }    

    This is where the URL-based nature of how the image cache manages image requests is convenient. First, an image retrieval request to the image cache for an image that is already being handled by the image cache's delegate—e.g., waiting on a large image to be downloaded—is simply added to the first request's array of completion blocks. Second, if source images are downloaded from the Internet (as is often the case), the URL for such a network request is readily available.

    Note: The completion block must be called on the main thread. Fast Image Cache is architected such that this call will not block the main thread, as processing sources image is handled in the image cache's own serial dispatch queue.

  2. Manually: It is possible to manually insert image data into the image cache.

    // Just finished downloading new user photo
    
    XXUser *user = [self _currentUser];
    NSString *formatName = XXImageFormatNameUserThumbnailSmall;
    FICImageCacheCompletionBlock completionBlock = ^(id <FICEntity> entity, NSString *formatName, UIImage *image) {
        NSLog(@"Processed and stored image for entity: %@", entity);
    };
    
    [sharedImageCache setImage:newUserPhoto forEntity:user withFormatName:formatName completionBlock:completionBlock];

Note: Fast Image Cache does not persist source images. See Source Image Persistence for more information.

Canceling Source Image Requests

If an image request is already in progress, it can be cancelled:

// We scrolled up far enough that the image we requested in no longer visible; cancel the request
XXUser *user = [self _currentUser];
NSString *formatName = XXImageFormatNameUserThumbnailSmall;
[sharedImageCache cancelImageRetrievalForEntity:user withFormatName:formatName];

When this happens, Fast Image Cache cleans up its internal bookkeeping, and any completion blocks from the corresponding image request will do nothing at this point. However, the image cache's delegate is still responsible for ensuring that any outstanding source image requests (e.g., network requests) are cancelled:

- (void)imageCache:(FICImageCache *)imageCache cancelImageLoadingForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName {
    [self _cancelNetworkRequestForSourceImageForEntity:entity withFormatName:formatName];
}

Working with Image Format Families

The advantage of classifying image formats into families is that the image cache's delegate can tell the image cache to process entity source images for all image formats in a family when any image format in that family is processed. By default, all image formats are processed for a given family unless you implement this delegate and return otherwise.

- (BOOL)imageCache:(FICImageCache *)imageCache shouldProcessAllFormatsInFamily:(NSString *)formatFamily forEntity:(id<FICEntity>)entity {
    BOOL shouldProcessAllFormats = NO;

    if ([formatFamily isEqualToString:XXImageFormatFamilyUserThumbnails]) {
        XXUser *user = (XXUser *)entity;
        shouldProcessAllFormats = user.active;
    }

    return shouldProcessAllFormats;
}

The advantage of processing all image formats in a family at once is that the source image does not need to be repeatedly downloaded (or loaded into memory if cached on disk).

For example, if a user changes their profile photo, it probably makes sense to process the new source image for every variant at the same time that the first image format is processed. That is, if the image cache is processing a new user profile photo for the image format named XXImageFormatNameUserThumbnailSmall, then it makes sense to also process and store new image data for that same user for the image format named XXImageFormatNameUserThumbnailMedium.

Documentation

Fast Image Cache's header files are fully documented, and appledoc can be used to generate documentation in various forms, including HTML and Xcode DocSet.

HTML documentation can be found here.

Demo Application

Included with this repository is a demo app Xcode project. It demonstrates the difference between the conventional approach for loading and displaying images and the Fast Image Cache approach. See the requirements for running the demo app Xcode project.

Note: The demo application must either be supplied with JPEG images, or the included fetch_demo_images.sh script in the FastImageCacheDemo directory must be run.

Video

Fast Image Cache Demo App Video

Note: In this demo video, the first demonstrated method is the conventional approach. The second method is using image tables.

Statistics

The following statistics were measured from a run of the demo application:

Method Scrolling Performance Disk Usage RPRVT1
Conventional ~35FPS 568KB 2.40MB: 1.06MB + 1.34MB
Fast Image Cache ~59FPS 2.2MB 1.15MB: 1.06MB + 0.09MB

The takeaway is that Fast Image Cache sacrifices disk usage to achieve a faster framerate and overall less memory usage.


1 The first value is the the total RPRVT used by a method to display a screen's worth of JPEG thumbnails. The second value is the baseline RPRVT where all the table view cells and image views are on screen, but none of the image views have images set. The third value is how much additional RPRVT each method used beyond the baseline.

Contributors

Mallory Paine
Mallory Paine — Author and Original API Design
@mallorypaine


Michael Potter
Michael Potter — Documentation and API Refactoring
@LucasTizma

Credits

License

Fast Image Cache is made available under the MIT license:

The MIT License (MIT)

Copyright (c) 2013 Path, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

fastimagecache's People

Contributors

chakrit avatar ciphercom avatar cxa avatar dcaunt avatar dezinezync avatar donholly avatar epatey avatar indragiek avatar ishawnwang avatar jmah avatar jszumski avatar jverdi avatar mallorypaine avatar mortonfox avatar mviamari avatar olivercameron avatar rromanchuk avatar ruiaaperes avatar sega-zero avatar vkovtash avatar zbigy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fastimagecache's Issues

Is it possible to have multiple FICImageCache instances?

Hi,

Hope I will get an answer here since I'm facing a severe issue where metadata are written on disk but not imageTable.
I am using multiple instances of FICImageCache each used to deal with a single <FICEntity> class.

Images are visible in my application but there is only a metadata file.
Also sometimes when I retrieve images from a specific family, all metadata + imageTables are deleted for other families.

Thanks for your help!
Antoine

Failed to map chunk. errno=22

Hi,
I'd like to report some information about this error and ask about the reasons and possible ways to fix it.

One of users ran into an issue when my app doesn't show a downloaded image. The image was downloaded successfully but it was not added to FIC.

I found "Failed to map chunk. errno=22" and "ERROR: FastImageCache error occured with message: *** FIC Error: -[FICImageTable _chunkAtIndex:] failed to get chunk for index 5" log messages.

After a search in the sources (v1.3) I found mmap call that led to "Failed to map chunk. errno=22" message.
errno 22 - EINVAL. Something wrong with input parameters: probably, length or offset.

Thanks!

memory growth when using completionBlock in wantsSourceImageForEntity:

FastImageCache library and am running into a retain problem.

Specifically, when trying to provide UIImages (downloaded from a server) to the image cache. The images, when handed to the completion block, seems to remain retained. ARC is not releasing is.

When dealing with a very large number of large images, this leads to a dramatic memory growth.

- (void)imageCache:(FICImageCache *)imageCache wantsSourceImageForEntity:(id<FICEntity>)entity withFormatName:(NSString*)formatName completionBlock:(FICImageRequestCompletionBlock)completionBlock
{
    ...
    NSData * data = [NSURLConnection sendSynchronousRequest:urlRequest
                                              returningResponse:&response
                                                          error:&error];
    UIImage* image = [[UIImage alloc] initWithData:data];
    completionBlock(image); // <-----
    ...
}

I verified that not calling the completion block with the image, does not lead to the memory growth.

Also, not using the image in the drawing block itself, also avoids the memory growth. So i assume this is indeed related to the image being retained.

- (FICEntityImageDrawingBlock)drawingBlockForImage:(UIImage*)image withFormatName:(NSString*)formatName
{
    FICEntityImageDrawingBlock drawingBlock = ^(CGContextRef contextRef, CGSize contextSize) {
        CGRect contextBounds = CGRectZero;
        contextBounds.size = contextSize;
        CGContextClearRect(contextRef, contextBounds);
        CGContextSetInterpolationQuality(contextRef, kCGInterpolationMedium);
        UIGraphicsPushContext(contextRef);
        [image drawInRect:contextBounds]; // <---- 
        UIGraphicsPopContext();
    };

    return drawingBlock;
}

How can i avoid the image being retained or how can i force it to be released? I tried setting it to nil and adding a @autoreleasepool but that didn't help.

@autoreleasepool {
        NSData * data = [NSURLConnection sendSynchronousRequest:urlRequest
                                              returningResponse:&response
                                                          error:&error];

        UIImage* image = [[UIImage alloc] initWithData:data];
        completionBlock(image);
        image = nil;
        data = nil;

        [[NSURLCache sharedURLCache] removeAllCachedResponses];
    }

Android

Do you guys have something like FIC that you use for the Path Android app?

I love using FIC on iOS, but I haven't come across anything nearly as good for Android, so I was wondering what Path actually uses.

EXC_BAD_ACCESS drawInRect

Hello,

I've used in FICEntityImageDrawingBlock code from documentation:

UIGraphicsPushContext(context);
[image drawInRect:contextBounds];
UIGraphicsPopContext();

It worked excellent, but I could replicate EXC_BAD_ACCESS in drawInRect method. I reset image cache, but some pending operation was still in queue

Scenarios is following

threadX inside FICEntityImageDrawingBlock

UIGraphicsPushContext(context);
image = SomeImageTransformation

switch to main thread

[ imageCache reset ];

return back to threadX

[image drawInRect:contextBounds];
UIGraphicsPopContext();

Cancel image loading

Unless I'm completely missing something, there appears to be no way to cancel an image from loading (whether from cache or from network).

This is necessary for image loading in a table view/collection view, to cancel requests that are no longer on screen -- or at the very least, deprioritize them from images that that should be on the screen.

Loading multiple images at once

I've got a view that has 9 images in it. The first time the app launches, it goes to retrieve the images. The problem I'm running in to is that the completion blocks for all the images are being run at around the same time even though they are all processed by the image cache in order.

Is this expected behaviour? I was expecting the completion block to be called as soon as it's done being precessed, that way my UI can have the images populate one by one, not all at the end.

Retrieve image in a synchronous way without network call

Hi there,

Is it possible to expose public methods on FICImageCache that wrap calls to these two methods on FICImageTable?

- (BOOL)entryExistsForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID;
- (UIImage *)newImageForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID;

In fact I want to implement lazy loading as exposed in sample code from Apple.
Meaning loading (network calls) images only when the table view is not dragging nor decelerating.

I don't want to perform any retrieveImageForEntity or asynchronouslyRetrieveImageForEntity inside my cellForRowAtIndexPath:, I just want to check if the image is available, and load it (from disk/memory) if so.

Since retrieveImageForEntity is not designed to be 100% synchronous, I consider this API not relevant and don't want to use it due to indeterminate async behavior.

I have already worked on a wrapper (see below) on FICImageCache that make this available but I don't like the design since I need to recreate a FICImageTable each time I call these methods, which will load metadata each time, losing all the great improvements brought by your library.

My wrapper interface:

@interface YSUserImageProvider : NSObject

+ (instancetype)sharedProvider;

- (BOOL)hasPictureForUser:(YSUser *)user formatName:(NSString *)formatName;

// can return `nil` if there is no cached image
- (UIImage *)pictureForUser:(YSUser *)user formatName:(NSString *)formatName;

- (void)retrievePictureForUser:(YSUser *)user formatName:(NSString *)formatName completion:(void(^)(UIImage *image))completion;

- (void)cancelRetrievalPictureForUser:(YSUser *)user formatName:(NSString *)formatName;

- (void)deleteCache;

@end

And the implementation:

- (BOOL)hasPictureForUser:(YSUser *)user formatName:(NSString *)formatName
{
    NSParameterAssert(user != nil);
    NSParameterAssert(formatName != nil);

    FICImageFormat *format = [self.cache formatWithName:formatName];
    FICImageTable *table = [[[FICImageTable alloc] initWithFormat:format] autorelease];

    return [table entryExistsForEntityUUID:[user UUID] sourceImageUUID:[user sourceImageUUID]];
}

Since FICImageCache exposes methods to do nearly anything on images, from manually setting to deleting, I think it would totally make sense to add this feature.

I might create a PR if you agree with this design.

Thanks for your attention and help,
Antoine

Original images being fetched again on app update

I noticed something in my app when I update it on the App Store. The images in the collection view flash briefly because fast image cache is, for some reason, getting them from the original files on disk again instead of getting them from the cache. (logical conclusion because it does the exact same thing UI wise if you delete the cache files)

When the app gets updated the files in the ImageTables directory seem to be there and the same size before and after the app gets updated. However, I can't seem to figure out why FastImageCache would be getting the original image files when the image tables seem to still exist. This is hard to debug because it only seems to occur when the app updates. I was hoping someone else had run into this issue and could give me some guidance.

Thanks!

Keep crashing in FICImageCache

My project is not using ARC (don't ask me why, long story :P). I've set flag -fobjc-arc for all FIC source files.

It keeps crashing on last line of this method:

  • (void)_processImage:(UIImage *)image forEntity:(id )entity imageTable:(FICImageTable *)imageTable completionBlocks:(NSArray *)completionBlocks

of FICImageCache.m

I've tried to set imageDrawingBlock to nil (in the same method)
FICEntityImageDrawingBlock imageDrawingBlock = nil;

the crash doesn't happen. But of course, nothing to render on screen.

Please advice. Thanks in advance.

Video Linked in README Doesn't Display

When I click on the video image/link on the main Github project page, I get some XML file from AWS rather than the video. It says "Access Denied".

Thanks.

Reporting network error from delegate's wantsSourceImageForEntity up to the app's code

I've noticed in the following call chain:

[FICImageCache asynchronouslyRetrieveImageForEntity:withFormatName:completionBlock:]
->
[delegate wantsSourceImageForEntity:withFormatName:completionBlock:]
->
AFHTTPRequest operation
->
completionBlock(image)

There's no way to report a network layer error up to an app level caller. Passing nil to completionBlock simply ignores the lookup of the block in the dictionary inside of the FastImageCache.

Is there any way to overcome this design constraint?

Sharing formats between families?

The project I'm working on is setup similar to these families and formats.

  • portrait
    • small(75x75)
    • large(600x800)
  • landscape
    • small(75x75)
    • large(800x600)

In the app, there is only one entity and the family it uses is based on the orientation of the source image. All of these entities are displayed on the same screen.

To me, it doesn't make sense that the portraitSmall and landscapeSmall are stored in two different imageTables when they are identical. The main case that I can think of is when the portraitSmall imageTable is full and the landscapeSmall imageTable only has a few images. Adding a new portraitSmall image would remove and old image when there could be an even older image in the landscapeSmall imageTable.

I'm thinking about implementing this and have a couple ideas but I wanted to see if anyone else thinks this would be useful or if I'm just being silly 😃

Not working at all

I have downloaded the code but it's not working. It opens and show there is no image pop up after clicking pop up it gives bad access

Support for ALAssetsLibrary

Is there any plan to support local images from ALAssetsLibrary?

The current ALAssetsLibrary API only provides access to 4 different image sizes (two thumbnails, a "fullscreen" version and the original).

Anything in-between the "thumbnail" size and "fullscreen" size typically requires pulling in the "fullscreen" size and scaling it (which has obvious performance implications). A caching mechanism that stores a processed version of these assets for later retrieval would be pretty awesome :)

This would make FIC very appealing for apps that provide a custom camera roll experience 👍

Get a null image in FICImageCacheCompletionBlock

Hi,
I've tried to use FIC as the document said, but always get a null image in FICImageCacheCompletionBlock callback.

I think [imageTable setEntryForEntityUUID:entityUUID sourceImageUUID:sourceImageUUID imageDrawingBlock:imageDrawingBlock]; may fail to set the cache, but I can find a way to debug or find out how to fix it.

[sharedImageCache asynchronouslyRetrieveImageForEntity:photo withFormatName:_imageFormatName completionBlock:^(id<FICEntity> entity, NSString *formatName, UIImage *image) {
 NSLog(@"img: %@", image); // log (null)
}];

Thanks for you help!

`FICImageFormat` with variable image size

This is more of a question rather than an issue. But what's the motivation for limiting FICImageFormat to only support images of a fixed dimension? Is there any technical reason supporting unknown dimension images are problematic?

Example implementation of FICEntity is misleading.

- (NSString *)UUID {
    return _userID;
}

- (NSString *)sourceImageUUID {
    return [_userPhotoURL absoluteString];
}

This example illustrate that UUID can be arbitrary string, but in fact it should be properly formatted string representation of UUID, or you get zero filled UUID in FICImageTableEntry.

Crash if requestURL has blank space

I don't know if I'm doing something very wrong, but it crashed every time my request has a blank space (i.e. in the name of image, http://ralf.moblee.com.br/api/v0//uploads/20131021autoes111/entry/42lugar_b1383916456/lugar bonito.jpg)

The crash itself is:
Exception: EXC_BAD_ACCESS (code=1, address=0x0)) at method CFUUIDBytes FICUUIDBytesFromMD5HashOfString(NSString *MD5Hash)

When I debugged I found the error is when I call retrieveImageForEntity:(id )entity withFormatName:(NSString *)formatName completionBlock:(FICImageCacheCompletionBlock)completionBlock my entity is nil, so when it will create the UUID the [_sourceImageURL absoluteString] is nil

Now follow my code:

RLFPhoto *photo = [[RLFPhoto alloc] init];
[photo setSourceImageURL:[NSURL URLWithString:feed.imageSource]];
BOOL imageExists = [[FICImageCache sharedImageCache] retrieveImageForEntity:photo withFormatName:RLFImageFormatFeedNormal completionBlock:^(id entity, NSString *formatName, UIImage *image)

FICEntity supporting multiple images

Great work on this library. Unless I'm mistaken, outside of resizing an image it seems unable to support different images being associated with a FICEntity - is this by design?

Taking the documentation example of a Core Data user object implementing the FICEntity protocol, we might have a user object containing both a profile photo and a cover photo URL.

I've been unable to find a way to integrate FIC within such a core data object as (NSString *)sourceImageUUID only allows one sourceImageUUID to be returned. I'm therefore creating multiple associated objects implementing FICEntity which seems excessive.

(NSURL *)sourceImageURLWithFormatName:(NSString *)formatName provides the formatName allowing for an appropriate URL to be returned for the given formatName, in our example either the profile photo of cover photo. Should (NSString *)sourceImageUUID pass in the formatName so a sourceImageURL appropriate UUID can be returned?

Again great work on the library, a brilliant contribution to the community.

Best wishes,
-Jim

deleteImageForEntity can not change the size of .imageTable

thanks for this great Fast Image Cache. It help me to improve the experience!
here is a little issue.
when i delete an image cache by deleteImageForEntity, the image cache really DOES NOT exist, however, the size of .imageTable still keeps same.
How could i make the size of .imageTable decreased when an image cache is deleted?
thanks~

Is there a way to clear cache?

Since the table data grows, some user wish to have an option to clear it.
I've not find an API for this, is there?

Or can I just remove the table and meta data files in the cache folder and re-config FIC to implement this?

Thanks!

ImageTables not deleted

More issues from me :)

I notice that ImageTables folder or files inside it not deleted after I've done:

[[FICImageCache sharedImageCache] deleteImageForEntity:_mov withFormatName:XXPosterThumbnail];

or even after:
[[FICImageCache sharedImageCache] reset];

Is that by design?

The reason I wanna do that is for freeing up some space, just in case users notice that my app consume a lot of storage.

Thanks.

CGContextClearRect() intermittent crash inside of drawingBlockForImage:withFormatName:

One of my beta testers is getting a recurring crash inside of drawingBlockForImage:withFormatName:, specifically when CGContextClearRect() is called.

The crashed thread is:
com.path.FastImageCacheQueue.

The error given is:
EXC_BAD_ACCESS KERN_INVALID_ADDRESS at 0x1096d0000

The call stack is 7 or 8 frames deep inside the implementation of CGContextClearRect() when it crashes.

My drawing code is copied directly from the recommended sample code in the documentation, which includes the call to clear the passed-in context before performing the rest of the drawing code.

Attached below is a screenshot of the crashed thread:

screen shot 2014-01-06 at 6 47 37 pm

This is on an iPhone 5s running iOS 7.1.

FICImageFormat's maximumCount does not work?

Thank you for the great open source.
I want to limit the FICImageTable size, so I set the FICImageFormat's maximumCount. But it sometimes does not work. For example, in the FICImageCacheDemo, I only change "NSInteger squareImageFormatMaximumCount = 400;" from 400 to 4, then run the demo. In the debug console it will output:
"
2014-04-11 17:07:48.061 FastImageCacheDemo[33653:60b] *** FIC Warning: growing desired maximumCount (4) for format com.path.FastImageCacheDemo.FICDPhotoSquareImage32BitBGRAFormatName to fill a chunk (21)
...
2014-04-11 17:07:55.204 FastImageCacheDemo[33653:21b] FICImageTable - unable to evict entry from table 'com.path.FastImageCacheDemo.FICDPhotoSquareImage32BitBGRAFormatName' to make room. New index 99, desired max 21
"
and the com.path.FastImageCacheDemo.FICDPhotoSquareImage32BitBGRAFormatName.imageTable size is the same as before(when the maximumCount set 400).

I debugged the demo and found "- (NSString *)oldestEvictableEntityUUID" can not find the oldest evictableEntity UUID because all the entityUUID is in use.
I found the entityUUID will be free after the entryData dealloc.
"
[_inUseEntries addObject:entityUUID];
__weak FICImageTable *weakSelf = self;
[entryData executeBlockOnDealloc:^{
[weakSelf removeInUseForEntityUUID:entityUUID];
}];
"

I want to ask why change the demo maximumCount does not work? Is the demo must use 100 entityUUID or is the entryData not dealloc as soon as possible?

FICImageCache can not reset in background?

Thank you for the great open source.
I want to reset the cache when App did enter background, so I did like this:
"

  • (void)applicationDidEnterBackground:(UIApplication *)application
    {
    [[FICImageCache sharedImageCache] reset];
    }
    "

It can reset the cache, but when the app come back to foreground, it will crash with the debug console output
"
Terminating in response to backboardd's termination.
"

I debugged and found maybe the crash happened when executed this function call:
"
int result = ftruncate(_fileDescriptor, fileLength);
"

I want to ask is there any way to reset the FICImageCache correctly when the app did enter background? Thank you!

FICUUIDBytesFromMD5HashOfString causing crash

I am getting a crash on the MD5 line in FICDPhoto.m -> line 170

The crash occurred on scrolling a tableview with images from website after about four screen - fast scrolling the first time and crashed on slow scrolling. exc_bad_access - and then on another attempt without changing anything, it did not crash at all and the scrolling was much faster than what I had before FastImageCache.

Any help would be appreciated.

pragma mark - FICImageCacheEntity

  • (NSString *)UUID {
    if (_UUID == nil) {
    // MD5 hashing is expensive enough that we only want to do it once - CRASH LINE BELOW
    CFUUIDBytes UUIDBytes = FICUUIDBytesFromMD5HashOfString([_sourceImageURL absoluteString]);
    _UUID = FICStringWithUUIDBytes(UUIDBytes);
    }

    return _UUID;
    }

Display the network picture?

Hello,
thanks for the great source, it works like a charm.
however i have a litte problem : How do I display the network picture, please?

Jack

Feature Request: simple way to retrieve disk usage

It would be nice to have a simple way to get the number of bytes currently being occupied on disk from FastImageCache. I understand there is 4 bytes per pixel × image width in pixels × image height in pixels × maximum number of entries but i would like to ask shared cache this question programmatically

Use case question

Hope you guys don't mind my asking here.

Trying to determine whether or not it makes sense to use this library in a situation where you have a constant stream of new images to deal with. Think of a case where every 'tweet' had a different thumbnail associated with it where the network source was of random size, and you had an 'infiniti load' table view model where you didn't have all the data to process thumbnails up front.

The fact that you need to effectively resize every incoming image so it can be written to the image table makes me think if the images aren't as likely to be seen repeatedly, that this might be more of a performance impact up front than a more traditional method.

Just curious on the cases where you shouldn't use this library.

Cancel image retrieval when the same entity is being loaded?

I have a table view with images (e.g. user profiles). Each user has its own entity.

I was using cancelImageRetrievalForEntity on UITableViewCell's prepareForReuse. However, I realized that it'll cancel all retrievals for that profile.

Is there a way to cancel a specific retrieval (and not all of them)?

I could create an entity for each cell (instead of by user), but I suppose it's not the best way, as I suppose it'd store and fetch several times the same image.

Use the same image table for image formats with the same metadata

Proposoal

I'd like to propose that image formats that have identical values for the following properties use the same image table:

  • family
  • style
  • imageSize
  • devices
  • maximumCount (Maybe? An argument could be made to exclude this from the decision on whether to use the same image table as another format)

Motivation

I'm setting up all of the various FIC configuration during app launch. For simplicity, let's assume I'm doing that in application:didFinishLaunchingWithOptions:. This means that my startup code needs to know about all of the various bits of UI in my app that will be harnessing FIC. It not only needs to know about the existence of these bits of UI that show images, but it also needs to know what size each would like to display images at. (And image style, etc)

What I'd like to do is have each of the image-displaying classes generate their own FICImageFormat, since their expected size (and other information) should be an implementation detail that the App Delegate shouldn't know about. Here's an example of what I'd like to be able to do in my App Delegate:

NSArray* imageDisplayingClasses = @[ /* all my UI classes that harness FIC */ ];
NSMutableArray* imageFormats = [NSMutableArray new];

for (Class uiClass in imageDisplayingClasses) {
    [imageFormats addObject:[uiClass imageFormat]];
}

FICImageCache* imageCache = [FICImageCache sharedImageCache];
imageCache.formats = imageFormats;

However, this falls down a little bit when more than one UI class intends to show the same image at the same size. For instance, there are several unrelated bits of UI in my app that show user avatars. Usually, they show at different sizes but sometimes the design might call for the avatar in a User Post cell be the same size as the avatar in the User Comment cell, for example. (Let's assume these cells are completely de-coupled, since the design might change and call for a different size image). If I use the example code I've included, what I'll get (as far as I can tell) are two separate image tables that are exactly the same. (Especially if I return YES in response to imageCache:shouldProcessAllFormatsInFamily:forEntity:!)

The enhancement I'm proposing is to de-duplicate these image tables. If the image table is the same in every way, use the same image table for the two identical formats.

Problems getting drawingBlockForImage protocol to work in RubyMotion

Hi there - I'm not sure if you'll be able to help, but I'm pulling my hair out trying to get this to work on RubyMotion. The issue is that the block that I return from drawingBlockForImage in my class that implements the FICEntity protocol cannot be invoked by the FastImageCache framework without causing the app to die cold.

My RubyMotion implementation of that method looks like this:

    def drawingBlockForImage(image, withFormatName:formatName)
      lambda do |context, contextSize|
        contextBounds = CGRectZero
        contextBounds.size = contextSize
        CGContextClearRect(context, contextBounds)
        # Clip medium thumbnails so they have rounded corners
        if formatName == "imageFormatNameMediumThumbnail"
          clippingPath = self._clippingPath
          clippingPath.addClip
        end
        UIGraphicsPushContext(context)
        image.drawInRect(contextBounds)
        UIGraphicsPopContext()
      end
    end

And whenever that returned block is called, for example in the FICImageTable method setEntryForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID imageDrawingBlock:(FICEntityImageDrawingBlock)imageDrawingBlock (line 276), the sim just crashes code:

            // Call drawing block to allow client to draw into the context
            imageDrawingBlock(context, [_imageFormat imageSize]);

And I have no idea why. Does anyone have any clues?

Here's a link to the discussion on the RubyMotion groups:

https://groups.google.com/forum/?fromgroups=#!topic/rubymotion/IBeC-p5uY1s

Thanks,
M@

Unable to delete images

I'm constantly adding images to the table and at the same time removing them when I have used them for our feature. However, our image table keeps growing in size and calling delete on the entity doesn't actually remove the data.

When does the data get deleted?

What happens to existing UIImage's when its image table memory is reused for another image?

I may be missing something. I'm tracking down a bug that causes the wrong image to be rendered from time to time. My suspicion is that it's related to reusing entries in the image tables when I exceed the maximum count for a particular format.

If I understand correctly, the whole point of FIC is to provide UIImage's on top of memory mapped files without copying the data. It seems that there's no protection from overwriting the on disk bits of image A with the bits for image B when the cache size is exceeded. If a UIImage was in use for image A, how is this supposed to work?

Am I supposed to set my FICImageFormat maximumCount to be the greatest number that will ever have UIImages allocated concurrently?

BTW, it's very easy for me to reproduce this problem by setting maximumCount down to a very small number. When I do that, I see the wrong images all over my UI.

_processImage/newImageForEntityUUID:sourceImageUUID Failing

Since I switched to FIC I noticed randomly my images would "fail" to load even when the HTTP request was successful.

It looks like the issue is in FICImageCache in the method _processImage:forEntity:imageTable:completionBlocks:.

On line 338 when newImageForEntityUUID:sourceImageUUID: is called, it returns nil even though _processImage was called with a valid image.

I can't find a scenario that consistently reproduces it, it seems to happen at random and most frequently presents itself when multiple images of the same format are loading at the same time.

Run demo error

I download the zip, run demo ,show some errors?
xcode4.6 llvm4.2
Command /Applications/Xcode4.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang failed with exit code 1

Causing the App black

If an image is switched out while Cocoa framework is drawing it, sometimes will cause a tablecell or the entire app black.

Crash in iPad and iOS 5.0

Hi Lucas,

Thanks for sharing your code, it really good and its clear my most of the concept regarding caching image.

Apart from that the application is crashed in iPad version and iOS 5.0.

For these I shared the crash log please find the below.

-[UILabel attributedText]: unrecognized selector sent to instance 0x6bb2410
2013-10-21 14:30:38.090 FastImageCacheDemo[1101:c07] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UILabel attributedText]: unrecognized selector sent to instance 0x6bb2410'

Please let me know if you require anything for trace above crash log.

saveMetadata is called too frequently - causes a significant performance issue

It seems that saveMetadata on the FICImageTable class is called every time there's an interaction with one of the images in the cache (when images are processed to be added, updated or removed).

This is causing a very noticeable drop in frame-rate when scrolling down a network-populated UICollectionView with many (thousands) of images. It seems that the `saveMetadata' is important as it persists the data that is known about that image table to disk for retrieval later, but must it be done in a synchronous fashion (using a lock)?

Could we instead dispatch_async it on a serial queue and achieve the same safety?

Another option would be to bulk the updates so the disk IO wasn't so expensive (that method alone takes up about 90% of the time when calling _processImage:forEntity:imageTable:completionBlocks:

Perhaps it could be called after X number of changes or after a certain amount of time? From what I see, it shouldn't be more expensive to do it later vs after each entry change (still has to serialize all of the data and write it out).

If I don't call `saveMetadata' every single change when populating my image tables the scrolling performance stays pretty consistent and I don't get nearly as many hiccups. Yes, this is a one-time hit and once it's in the table, things are smooth as butter :) I'm mostly optimizing for the first-load experience.

I'm happy to submit a pull-request if you guys think this is the right move, I just wanted to see if there was a good reason to keep it synchronous!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.