iOS library for quickly displaying images while scrolling

Related tags

Image FastImageCache
Overview

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.
Comments
  • CGContextClearRect() intermittent crash inside of drawingBlockForImage:withFormatName:

    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.

    bug 
    opened by jaredsinclair 54
  • saveMetadata is called too frequently - causes a significant performance issue

    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!

    opened by donholly 19
  • Use NSJSONSerialization instead of NSPropertyListSerialization for better performance

    Use NSJSONSerialization instead of NSPropertyListSerialization for better performance

    Use NSJSONSerialization instead of NSPropertyListSerialization to improve the speed serializing Image Table metadata before writing to disk. This change is backward compatible with previous Image Tables

    opened by donholly 11
  • importing a batch of photos causes large memory allocation then killed by system

    importing a batch of photos causes large memory allocation then killed by system

    Thanks for great fast image cache first. I have done a load test of importing a batch of photos about 1000+ from system album. The app crashed when the process went about 50%. Viewing the crash log, I found the some info like this: My App 7.253 166401 0 - 41854 50 per-process-limit (resume)

    166401* 4=665M(loaded momery)

    and I have launtched the profile, capturing a screenshot like this: image it seems that mmap causes the huge memory allocation then app is killed by system. At the same time, I did not call retrieve image function to show image. Is there a way to write a batch of photos(about 1000-5000) to image table without huge memory allocation? My test environment: iphone6, ios8.1, xcode6.1, arm64 Thanks a lot.

    opened by jerrium 10
  • Problems getting drawingBlockForImage protocol to work in RubyMotion

    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@

    opened by matthewsinclair 8
  • 25 FPS on iPad 3

    25 FPS on iPad 3

    I'm running the Demo App on an iPad 3 and getting 25 FPS when scrolling up and down (20 FPS without #125). The app becomes CPU-bound once each cell is changing every frame. An iPad 3 does not have the CPU to create 100+ UIImages, assign them to UIImageViews and commit the changes to Core Animation.

    screen shot 2016-02-04 at 5 18 59 pm

    A workaround I have on this branch is to wrap every image fetch in a NSBlockOperation. The operation guarantees that the app will get 60 FPS because it avoids returning all 100+ images in a single frame. This is most noticeable on an iPad 3, which can only perform ~7 image updates per frame.

    I feel that the cache should provide async and cancellable requests, by default, so that every use of the cache does not need to implement its own throttling. Regardless of the speed of the device, and no matter how amazing the cache, there is always a limit on the number of images that can be fetched in a single frame because each request takes a non-zero amount of time.

    Thoughts?

    I am happy to work on this, given a bit of guidance on the internal threading and the callback structure of the image cache.

    opened by Axlle 7
  • EXC_BAD_ACCESS drawInRect

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

    opened by Igor-Palaguta 7
  • Carthage support

    Carthage support

    The Carthage doesn't quite work.

    Project "FastImageCacheDemo.xcodeproj" has no shared schemes
    

    Just need to the check the box to share the framework scheme. Thanks!

    opened by soffes 6
  • Is it possible to have multiple FICImageCache instances?

    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

    opened by amarcadet 6
  • FICUUIDBytesFromMD5HashOfString causing crash

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

    opened by robmontesinos 6
  • Cancel image loading

    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.

    opened by shnhrrsn 6
  •  SDWebImage

    SDWebImage

    Hey all,

    I'm aware Fast Image Cache is the absolute fastest way to go from image data on disk to being rendered by Core Animation. Could we in theory use NSURLConnection or one of many popular networking libraries (like AFNetworking) to actually download the image data from our server in batch, then use SDWebImage.

    With that in mind, or option2: As it stands today should we still use Fast Image Cache to have it optimize it for future rendering? This is basically to stop some performance bottlenecks—especially with scrolling—as a result of images need to display quickly while scrolling (obviously.)

    opened by Montana 0
  • EXC_BAD_ACCESS

    EXC_BAD_ACCESS

    Hey ex Pathies,

    When working on the product I remember anything you get from an allocation function (usually the static alloc method, but there are a few others), or a copy method, you own the memory too and must release it when you are done.

    But say, if you get something back from just about anything else including factory methods (e.g. [NSString stringWithFormat]) then you'll have an autorelease reference, which means it could be released at some time in the future by other code - so it is vital that if you need to keep it around beyond the immediate function that you retain it. If you don't, the memory may remain allocated while you are using it, or be released but coincidentally still valid, during your emulator testing, but is more likely to be released and show up as bad access errors when running on the device.

    Is the best way to track these things down, and a good idea anyway (even if there are no apparent problems) is to run the app in the Instruments tool, especially with the Leaks option on?

    opened by Montana 2
  • Would definitely appreciate a PR that fixes this issue. With a small baby at home, I rarely have time to work on fixes myself anymore, but if you write a PR, I'll work with you to get it merged.

    Would definitely appreciate a PR that fixes this issue. With a small baby at home, I rarely have time to work on fixes myself anymore, but if you write a PR, I'll work with you to get it merged.

    Would definitely appreciate a PR that fixes this issue. With a small baby at home, I rarely have time to work on fixes myself anymore, but if you write a PR, I'll work with you to get it merged.

    Originally posted by @mallorypaine in https://github.com/path/FastImageCache/issues/137#issuecomment-231544582

    opened by rrobbyflya330 0
  • Repo is dead -> official successor

    Repo is dead -> official successor

    This is a difficult phase for any OS project. Path has obviously abandoned any work on FastImageCache. @mallorypaine one of/the original developer announced in #152 that he'll continue maintaining FIC even though he's not associated with Path anymore - @mallorypaine please object, if you changed your mind, while I try to push more work your way :-)

    To prevent any further fragmentation we should try to channel as many issues and PRs towards @mallorypaine's fork. Long term we should try to move the repository entirely.

    The Plan

    1. Upvote this issue to gain attention
    2. If you're about to open an issue or PR, head over to @mallorypaine's fork to do so
    3. Get in touch with GitHub and see if there's a way to transfer the repository
    4. Find a way to update the podspec
    opened by sebastianludwig 8
  • Fixes some issues around Xcode 9 yelling about using UIKit on background threads

    Fixes some issues around Xcode 9 yelling about using UIKit on background threads

    In Xcode 9, the new main thread checker kept warning me about FastImageCache using UIApplication's isProtectedDataAvailable on a background thread. This should be innocuous, but I think Xcode 9 is just broadly looking at UIKit on background threads.

    More consequently, this was preventing me from running unit tests locally on my own project.

    The basic change here is to use NSNotificationCenter to watch for notifications when protected data becomes available or unavailable, and maintain that state as a static variable, which the instances can then utilize.

    opened by rhaining 13
Releases(1.5.1)
Owner
Path Mobile Inc Pte. Ltd.
Path Mobile Inc Pte. Ltd.
SwiftUI view that download and display image from URL and displaying Activity Indicator while loading .

ViewWithActivityIndicator ViewWithActivityIndicator is a SwiftUI view that download and display image from URL and displaying Activity Indicator while

Ali Adam 28 Feb 3, 2022
APNGKit is a high performance framework for loading and displaying APNG images in iOS and macOS.

APNGKit is a high performance framework for loading and displaying APNG images in iOS and macOS. It's built on top of a modified version of libpng wit

Wei Wang 2.1k Dec 30, 2022
Advanced framework for loading, caching, processing, displaying and preheating images.

Advanced framework for loading, caching, processing, displaying and preheating images. This framework is no longer maintained. Programming in Swift? C

Alexander Grebenyuk 1.2k Dec 23, 2022
ImageView - Component for loading and displaying different images aka SVG/PNG/JPG/JPEG

ImageView Component that loads and displays images(.svg/.png/.jpg/.jpeg) form as

Sergei 1 Mar 23, 2022
A Swift/SwiftUI utility for caching and displaying images in SwiftUI Views

A Swift/SwiftUI utility for caching and displaying images asynchronously. Built with Swift 5.5 and works with async/await.

王雪铮 Xuezheng Wang 1 May 5, 2022
SwiftUI project to show ActivityIndicator above Image while loading

ImageWithActivityIndicatorDemo SwiftUI project to show ActivityIndicator above Image while loading ImageWithActivityIndicatorDemo is a demo app that s

Ali Adam 4 May 27, 2021
Simple camera application for iOS that uploads pictures to WebDAV server or Dropbox quickly. Available on the AppStore.

Upupu Simple camera application for iOS that uploads pictures to WebDAV server or Dropbox quickly. Also available on the AppStore. Features Easy and f

Xcoo 65 Nov 15, 2022
Scrolling the image as a background in Swift

Scrolling the image as a background!

Aleksandr Bychkovskiy 0 Nov 7, 2021
A custom image view that implements device motion scrolling

YXTMotionView A custom image view that implements device motion scrolling Installation CocoaPods Add the dependency to your Podfile: platform :ios pod

Hanton Yang 79 Dec 17, 2022
Swift image slideshow with circular scrolling, timer and full screen viewer

?? ImageSlideshow Customizable Swift image slideshow with circular scrolling, timer and full screen viewer ?? Example To run the example project, clon

Petr Zvoníček 1.7k Dec 21, 2022
Space! – an iOS widget displaying NASA's Astronomy Picture of the Day

Space! NASA's Astronomy Picture of the Day – now on your Home Screen with widgets! Space! displays the latest APOD photo curated by NASA every day. Se

Jacob Bandes-Storch 72 Dec 17, 2022
A SwiftUI view for displaying image histograms

HistogramView A SwiftUI view for displaying image histograms. How do I use it? It's as simple as: HistogramView(image: myImage) Note: Both UIImage & N

Vasilis Akoinoglou 11 Dec 14, 2022
IBrain - Displaying a Point Cloud Using Scene Depth

Displaying a Point Cloud Using Scene Depth Present a visualization of the physic

YuXuan (Andrew) Liu 2 Oct 24, 2022
LoremPicsum - Simple UIKit based app for displaying grid of pictures

LoremPicsum - Simple UIKit based app for displaying grid of pictures

Paweł Dziennik 0 Jan 20, 2022
FlaneurImagePicker is an iOS image picker that allows users to pick images from different sources (ex: user's library, user's camera, Instagram...). It's highly customizable.

FlaneurImagePicker is a highly customizable iOS image picker that allows users to pick images from different sources (ex: device's library, device's c

FlaneurApp 17 Feb 2, 2020
Kingfisher is a powerful, pure-Swift library for downloading and caching images from the web

Kingfisher is a powerful, pure-Swift library for downloading and caching images from the web. It provides you a chance to use a pure-Swift way to work

Wei Wang 20.9k Dec 30, 2022
A high-performance image library for downloading, caching, and processing images in Swift.

Features Asynchronous image downloader with priority queuing Advanced memory and database caching using YapDatabase (SQLite) Guarantee of only one ima

Yap Studios 72 Sep 19, 2022
A Swift library for parsing and drawing SVG images to CoreGraphics contexts.

SwiftDraw A Swift library for parsing and drawing SVG images to CoreGraphics contexts. SwiftDraw can also convert an SVG into Swift source code. Usage

Simon Whitty 119 Jan 3, 2023
Style Art library process images using COREML with a set of pre trained machine learning models and convert them to Art style.

StyleArt Style Art is a library that process images using COREML with a set of pre trained machine learning models and convert them to Art style. Prev

iLeaf Solutions Pvt. Ltd. 222 Dec 17, 2022