[
  {
    "path": ".gitignore",
    "content": "# Mac OS X Finder\n.DS_Store\n\n# Xcode\nbuild/*\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\n*.xcworkspace\n!default.xcworkspace\nxcuserdata\nprofile\n*.moved-aside\nDerivedData\n\n# Demo Images\nFastImageCache/FastImageCacheDemo/Demo Images/*.jpg\nCarthage\n"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache/FICEntity.h",
    "content": "//\n//  FICEntity.h\n//  FastImageCache\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICImports.h\"\n@class FICImageFormat;\n\nNS_ASSUME_NONNULL_BEGIN\n\ntypedef void (^FICEntityImageDrawingBlock)(CGContextRef context, CGSize contextSize);\n\n/**\n `FICEntity` is a protocol that classes interacting with the image cache must conform to. An entity uniquely identifies entries in image tables, which are instances of `<FICImageTable>`.\n */\n@protocol FICEntity <NSObject>\n\n@required\n\n/**\n A string that uniquely identifies this entity.\n \n @discussion Within each image table, each entry is identified by an entity's UUID. Ideally, this value should never change for an entity. For example, if your entity class is a person\n model, its UUID might be an API-assigned, unchanging, unique user ID. No matter how the properties of the person change, its user ID should never change.\n */\n@property (nonatomic, copy, readonly) NSString *fic_UUID;\n\n/**\n A string that uniquely identifies an entity's source image.\n \n @discussion While `<UUID>` should be unchanging, a source image UUID might change. For example, if your entity class is a person model, its source image UUID might change every time the\n person changes their profile photo. In this case, the source image UUID might be a hash of the profile photo URL (assuming each image is given a unique URL).\n */\n@property (nonatomic, copy, readonly) NSString *fic_sourceImageUUID;\n\n/**\n Returns the source image URL associated with a specific format name.\n \n @param formatName The name of the image format that identifies which image table is requesting the source image.\n \n @return A URL representing the requested source image.\n \n @discussion Fast Image Cache operates on URLs when requesting source images. Typically, these URLs will point to remote image resources that must be downloaded from the Internet. While the\n URL returned by this method must be a valid instance of `NSURL`, it does not need to point to an actual remote resource. The URL might point to a file path on disk or be composed of a custom\n URL scheme of your choosing. The image cache's delegate is prompted to provide a source image for a particular entity and format name when it cannot find the requested image. It only uses the\n URL returned by this method to key image cache requests. No network or file operations are performed by the image cache.\n \n An example of when this method might return different source image URLs for the same entity is if you have defined several image formats for different thumbnail sizes and styles. For very\n large thumbnails, the source image URL might be the original image. For smaller thumbnails, the source image URL might point to a downscaled version of the original image.\n \n @see FICImageFormat\n @see [FICImageCacheDelegate imageCache:wantsSourceImageForEntity:withFormatName:completionBlock:]\n */\n- (nullable NSURL *)fic_sourceImageURLWithFormatName:(NSString *)formatName;\n\n\n/**\n Returns the drawing block for a specific image and format name.\n \n @param image The cached image that represents this entity.\n \n @param formatName The name of the image format that identifies which image table is requesting the source image.\n \n @return The drawing block used to draw the image data to be stored in the image table.\n \n The drawing block's type is defined as follows:\n \n     typedef void (^FICEntityImageDrawingBlock)(CGContextRef context, CGSize contextSize)\n \n @discussion Each entity is responsible for drawing its own source image into the bitmap context provided by the image table that will store the image data. Often it is sufficient to simply\n draw the image into the bitmap context. However, if you wish to apply any additional graphics processing to the source image before it is stored (such as clipping the image to a roundect rect),\n you may use this block to do so.\n \n @note This block will always be called from the serial dispatch queue used by the image cache.\n */\n- (nullable FICEntityImageDrawingBlock)fic_drawingBlockForImage:(UIImage *)image withFormatName:(NSString *)formatName;\n\n@optional\n/**\n Returns the image for a format\n \n @param format The image format that identifies which image table is requesting the source image.\n */\n- (nullable UIImage *)fic_imageForFormat:(FICImageFormat *)format;\n\n@end\n\nNS_ASSUME_NONNULL_END"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache/FICImageCache+FICErrorLogging.h",
    "content": "//\n//  FICImageCache+FICErrorLogging.h\n//  FastImageCache\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICImageCache.h\"\n\n/**\n This category on `<FICImageCache>` simply exposes its private logging mechanism to other classes.\n */\n@interface FICImageCache (FICErrorLogging)\n\n///-----------------------------\n/// @name Logging Error Messages\n///-----------------------------\n\n/**\n Passes an error message to the image cache.\n \n @param message A string representing the error message.\n \n @discussion Rather than logging directly to standard output, Fast Image Cache classes pass all error logging to the shared `<FICImageCache>` instance. `<FICImageCache>` then allows its delegate to handle the\n message.\n \n @see [FICImageCacheDelegate imageCache:errorDidOccurWithMessage:]\n */\n- (void)_logMessage:(NSString *)message;\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache/FICImageCache.h",
    "content": "//\n//  FICImageCache.h\n//  FastImageCache\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICImports.h\"\n#import \"FICImageFormat.h\"\n#import \"FICEntity.h\"\n\n@protocol FICEntity;\n@protocol FICImageCacheDelegate;\n\ntypedef void (^FICImageCacheCompletionBlock)(id <FICEntity> _Nullable entity, NSString * _Nonnull formatName, UIImage * _Nullable image);\ntypedef void (^FICImageRequestCompletionBlock)(UIImage * _Nullable sourceImage);\n\nNS_ASSUME_NONNULL_BEGIN\n\n/**\n `FICImageCache` is the primary class for managing and interacting with the image cache. Applications using the image cache create one or more `<FICImageFormat>`\n objects. These formats effectively act as logical groupings for image data stored in the image cache. An `<FICImageTable>` object is created for each format defined by\n your application to allow for efficient storage and retrieval of image data. Image data is keyed off of objects conforming to the `<FICEntity>` protocol as well as an\n image format name.\n */\n@interface FICImageCache : NSObject\n\n/**\n The namespace of the image cache.\n \n @discussion Namespace is responsible for isolation of dirrerent image cache instances on file system level. Namespace should be unique across application.\n */\n\n@property (readonly, nonatomic) NSString *nameSpace;\n\n///----------------------------\n/// @name Managing the Delegate\n///----------------------------\n\n/**\n The delegate of the image cache.\n \n @discussion The delegate is responsible for asynchronously providing the source image for an entity. Optionally, the delegate can require that all formats in a format\n family for a particular entity be processed. Any errors that occur in the image cache are also communicated back to the delegate.\n */\n@property (nonatomic, weak) id <FICImageCacheDelegate> delegate;\n\n///---------------------------------------\n/// @name Creating Image Cache instances\n///---------------------------------------\n\n/**\n Returns new image cache.\n \n @return A new instance of `FICImageCache`.\n \n @param nameSpace The namespace that uniquely identifies current image cahce entity. If no nameSpace given, default namespace will be used.\n \n @note Fast Image Cache can either be used as a singleton for convenience or can exist as multiple instances. \n However, all instances of `FICImageCache` will make use same dispatch queue. To separate location on disk for storing image tables namespaces are used.\n \n @see [FICImageCache dispatchQueue]\n */\n\n- (instancetype)initWithNameSpace:(NSString *)nameSpace;\n\n///---------------------------------------\n/// @name Accessing the Shared Image Cache\n///---------------------------------------\n\n/**\n Returns the shared image cache.\n \n @return A shared instance of `FICImageCache`.\n \n @note Shared instance always binded to default namespace.\n \n @see [FICImageCache dispatchQueue]\n */\n+ (instancetype)sharedImageCache;\n\n/**\n Returns the shared dispatch queue used by all instances of `FICImageCache`.\n \n @return A generic, shared dispatch queue of type `dispatch_queue_t`.\n \n @note All instances of `FICImageCache` make use a single, shared dispatch queue to do their work.\n */\n+ (dispatch_queue_t)dispatchQueue;\n\n///---------------------------------\n/// @name Working with Image Formats\n///---------------------------------\n\n/**\n Sets the image formats to be used by the image cache.\n \n @param formats An array of `<FICImageFormat>` objects.\n \n @note Once the image formats have been set, subsequent calls to this method will do nothing.\n */\n- (void)setFormats:(NSArray<FICImageFormat*> *)formats;\n\n/**\n Returns an image format previously associated with the image cache.\n \n @param formatName The name of the image format to return.\n \n @return An image format with the name `formatName` or `nil` if no format with that name exists.\n */\n- (nullable FICImageFormat *)formatWithName:(NSString *)formatName;\n\n/**\n Returns all the image formats of the same family previously associated with the image cache.\n \n @param family The name of the family of image formats to return.\n \n @return An array of `<FICImageFormat>` objects whose family is `family` or `nil` if no format belongs to that family.\n */\n- (nullable NSArray<FICImageFormat *> *)formatsWithFamily:(NSString *)family;\n\n///-----------------------------------------------\n/// @name Storing, Retrieving, and Deleting Images\n///-----------------------------------------------\n\n/**\n Manually sets the the image to be used by the image cache for a particular entity and format name.\n \n @discussion Usually the image cache's delegate is responsible for lazily providing the source image for a given entity. This source image is then processed according\n to the drawing block defined by an entity for a given image format. This method allows the sender to explicitly set the image data to be stored in the image cache.\n After the image has been processed by the image cache, the completion block is called asynchronously on the main queue.\n \n @param image The image to store in the image cache.\n \n @param entity The entity that uniquely identifies the source image.\n \n @param formatName The format name that uniquely identifies which image table to look in for the cached image.\n \n @param completionBlock The completion block that is called after the image has been processed or if an error occurs.\n \n The completion block's type is defined as follows:\n     \n     typedef void (^FICImageCacheCompletionBlock)(id <FICEntity> entity, NSString *formatName, UIImage *image)\n */\n- (void)setImage:(UIImage *)image forEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(nullable FICImageCacheCompletionBlock)completionBlock;\n\n/**\n Attempts to synchronously retrieve an image from the image cache.\n \n @param entity The entity that uniquely identifies the source image.\n \n @param formatName The format name that uniquely identifies which image table to look in for the cached image. Must not be nil.\n \n @param completionBlock The completion block that is called when the requested image is available or if an error occurs.\n \n The completion block's type is defined as follows:\n     \n     typedef void (^FICImageCacheCompletionBlock)(id <FICEntity> entity, NSString *formatName, UIImage *image)\n     \n If the requested image already exists in the image cache, then the completion block is immediately called synchronously on the current thread. If the requested image\n does not already exist in the image cache, then the completion block will be called asynchronously on the main thread as soon as the requested image is available.\n     \n @return `YES` if the requested image already exists in the image case, `NO` if the image needs to be provided to the image cache by its delegate.\n \n @discussion Even if you make a synchronous image retrieval request, if the image does not yet exist in the image cache, the delegate will be asked to provide a source\n image, and it will be processed. This always occurs asynchronously. In this case, the return value from this method will be `NO`, and the image will be available in the\n completion block.\n \n @note You can always rely on the completion block being called. If an error occurs for any reason, the `image` parameter of the completion block will be `nil`. See\n <[FICImageCacheDelegate imageCache:errorDidOccurWithMessage:]> for information about being notified when errors occur.\n */\n- (BOOL)retrieveImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(nullable FICImageCacheCompletionBlock)completionBlock;\n\n/**\n Asynchronously retrieves an image from the image cache.\n \n @param entity The entity that uniquely identifies the source image.\n \n @param formatName The format name that uniquely identifies which image table to look in for the cached image. Must not be nil.\n \n @param completionBlock The completion block that is called when the requested image is available or if an error occurs.\n \n The completion block's type is defined as follows:\n \n     typedef void (^FICImageCacheCompletionBlock)(id <FICEntity> entity, NSString *formatName, UIImage *image)\n \n Unlike its synchronous counterpart, this method will always call its completion block asynchronously on the main thread, even if the request image is already in the\n image cache.\n \n @return `YES` if the requested image already exists in the image case, `NO` if the image needs to be provided to the image cache by its delegate.\n \n @note You can always rely on the completion block being called. If an error occurs for any reason, the `image` parameter of the completion block will be `nil`. See\n <[FICImageCacheDelegate imageCache:errorDidOccurWithMessage:]> for information about being notified when errors occur.\n \n @see [FICImageCache retrieveImageForEntity:withFormatName:completionBlock:]\n */\n- (BOOL)asynchronouslyRetrieveImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(nullable FICImageCacheCompletionBlock)completionBlock;\n\n/**\n Deletes an image from the image cache.\n \n @param entity The entity that uniquely identifies the source image.\n \n @param formatName The format name that uniquely identifies which image table to look in for the cached image.\n */\n- (void)deleteImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName;\n\n///-------------------------------\n/// @name Canceling Image Requests\n///-------------------------------\n\n/**\n Cancels an active request for an image from the image cache.\n \n @param entity The entity that uniquely identifies the source image.\n \n @param formatName The format name that uniquely identifies which image table to look in for the cached image.\n \n @discussion After this method is called, the completion block of the <[FICImageCacheDelegate imageCache:wantsSourceImageForEntity:withFormatName:completionBlock:]> delegate\n method for the corresponding entity, if called, does nothing.\n */\n- (void)cancelImageRetrievalForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName;\n    \n///-----------------------------------\n/// @name Checking for Image Existence\n///-----------------------------------\n\n/**\n Returns whether or not an image exists in the image cache.\n \n @param entity The entity that uniquely identifies the source image.\n \n @param formatName The format name that uniquely identifies which image table to look in for the cached image.\n \n @return `YES` if an image exists in the image cache for a given entity and format name. Otherwise, `NO`.\n */\n- (BOOL)imageExistsForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName;\n\n///--------------------------------\n/// @name Resetting the Image Cache\n///--------------------------------\n\n/**\n Resets the image cache by deleting all image tables and their contents.\n \n @note Resetting an image cache does not reset its image formats.\n */\n- (void)reset;\n\n@end\n\n/**\n `FICImageCacheDelegate` defines the required and optional actions that an image cache's delegate can perform.\n */\n@protocol FICImageCacheDelegate <NSObject>\n\n@optional\n\n/**\n This method is called on the delegate when the image cache needs a source image.\n \n @param imageCache The image cache that is requesting the source image.\n \n @param entity The entity that uniquely identifies the source image.\n \n @param formatName The format name that uniquely identifies which image table to look in for the cached image.\n \n @param completionBlock The completion block that the receiver must call when it has a source image ready.\n \n The completion block's type is defined as follows:\n \n     typedef void (^FICImageRequestCompletionBlock)(UIImage *sourceImage)\n     \n The completion block must always be called on the main thread.\n \n @discussion A source image is usually the original, full-size image that represents an entity. This source image is processed for every unique format to create the\n actual image data to be stored in the image cache. This method is an asynchronous data provider, so nothing is actually returned to the sender. Instead, the delegate's\n implementation is expected to call the completion block once an image is available.\n \n Fast Image Cache is architected under the typical design pattern whereby model objects provide a URL to certain image assets and allow the client to actually retrieve\n the images via network requests only when needed. As a result, the implementation of this method will usually involve creating an asynchronous network request using\n the URL returned by <[FICEntity sourceImageURLWithFormatName:]>, deserializing the image data when the request completes, and finally calling this method's completion\n block to provide the image cache with the source image.\n */\n- (void)imageCache:(FICImageCache *)imageCache wantsSourceImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(nullable FICImageRequestCompletionBlock)completionBlock;\n\n/**\n This method is called on the delegate when the image cache has received an image retrieval cancellation request.\n \n @param imageCache The image cache that has received the image retrieval cancellation request.\n \n @param entity The entity that uniquely identifies the source image.\n \n @param formatName The format name that uniquely identifies which image table to look in for the cached image.\n \n @discussion When an image retrieval cancellation request is made to the image cache, it removes all of its internal bookkeeping for requests. However, it is still the\n delegate's responsibility to cancel whatever logic is it performing to provide a source image to the cache (e.g., a network request).\n \n @see [FICImageCache cancelImageRetrievalForEntity:withFormatName:]\n */\n- (void)imageCache:(FICImageCache *)imageCache cancelImageLoadingForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName;\n\n/**\n This method is called on the delegate to determine whether or not all formats in a family should be processed right now.\n \n @note If this method is not implemented by the delegate, the default value is `YES`.\n \n @param imageCache The image cache that is requesting the source image.\n \n @param formatFamily The name of a format family.\n \n @param entity The entity that uniquely identifies the source image.\n \n @return `YES` if all formats in a format family should be processed. Otherwise, `NO`.\n \n @discussion This method is called whenever new image data is stored in the image cache. Because format families are used to group multiple different formats together,\n typically the delegate will want to return `YES` here so that other formats in the same family can be processed.\n \n For example, if your image cache has defined several different thumbnail sizes and styles for a person model, and if a person changes their profile photo, you would\n want every thumbnail size and style is updated with the new source image.\n */\n- (BOOL)imageCache:(FICImageCache *)imageCache shouldProcessAllFormatsInFamily:(NSString *)formatFamily forEntity:(id <FICEntity>)entity;\n\n/**\n This method is called on the delegate whenever the image cache has an error message to log.\n \n @param imageCache The image cache that is requesting the source image.\n \n @param errorMessage The error message generated by the image cache.\n \n @discussion Fast Image Cache will not explicitly log any messages to standard output. Instead, it allows the delegate to handle (or ignore) any error output.\n */\n- (void)imageCache:(FICImageCache *)imageCache errorDidOccurWithMessage:(NSString *)errorMessage;\n\n@end\n\nNS_ASSUME_NONNULL_END"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache/FICImageCache.m",
    "content": "//\n//  FICImageCache.m\n//  FastImageCache\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICImageCache.h\"\n#import \"FICEntity.h\"\n#import \"FICImageTable.h\"\n#import \"FICImageFormat.h\"\n\n#pragma mark Internal Definitions\n\nstatic void _FICAddCompletionBlockForEntity(NSString *formatName, NSMutableDictionary *entityRequestsDictionary, id <FICEntity> entity, FICImageCacheCompletionBlock completionBlock);\n\nstatic NSString *const FICImageCacheFormatKey = @\"FICImageCacheFormatKey\";\nstatic NSString *const FICImageCacheCompletionBlocksKey = @\"FICImageCacheCompletionBlocksKey\";\nstatic NSString *const FICImageCacheEntityKey = @\"FICImageCacheEntityKey\";\n\n#pragma mark - Class Extension\n\n@interface FICImageCache () {\n    NSMutableDictionary *_formats;\n    NSMutableDictionary *_imageTables;\n    NSMutableDictionary *_requests;\n    \n    BOOL _delegateImplementsWantsSourceImageForEntityWithFormatNameCompletionBlock;\n    BOOL _delegateImplementsShouldProcessAllFormatsInFamilyForEntity;\n    BOOL _delegateImplementsErrorDidOccurWithMessage;\n    BOOL _delegateImplementsCancelImageLoadingForEntityWithFormatName;\n}\n\n@end\n\n#pragma mark\n\n@implementation FICImageCache\n\n@synthesize delegate = _delegate;\n\n#pragma mark - Property Accessors\n\n- (void)setDelegate:(id<FICImageCacheDelegate>)delegate {\n    if (delegate != _delegate) {\n        _delegate = delegate;\n        \n        _delegateImplementsWantsSourceImageForEntityWithFormatNameCompletionBlock = [_delegate respondsToSelector:@selector(imageCache:wantsSourceImageForEntity:withFormatName:completionBlock:)];\n        _delegateImplementsShouldProcessAllFormatsInFamilyForEntity = [_delegate respondsToSelector:@selector(imageCache:shouldProcessAllFormatsInFamily:forEntity:)];\n        _delegateImplementsErrorDidOccurWithMessage = [_delegate respondsToSelector:@selector(imageCache:errorDidOccurWithMessage:)];\n        _delegateImplementsCancelImageLoadingForEntityWithFormatName = [_delegate respondsToSelector:@selector(imageCache:cancelImageLoadingForEntity:withFormatName:)];\n    }\n}\n\n#pragma mark - Object Lifecycle\n\n+ (instancetype)sharedImageCache {\n    static dispatch_once_t onceToken;\n    static FICImageCache *__imageCache = nil;\n    dispatch_once(&onceToken, ^{\n        __imageCache = [[[self class] alloc] init];\n    });\n\n    return __imageCache;\n}\n\n+ (dispatch_queue_t)dispatchQueue {\n    static dispatch_queue_t __imageCacheDispatchQueue = NULL;\n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        __imageCacheDispatchQueue = dispatch_queue_create(\"com.path.FastImageCacheQueue\", NULL);\n    });\n    return __imageCacheDispatchQueue;\n}\n\n- (instancetype)init {\n    return [self initWithNameSpace:@\"FICDefaultNamespace\"];\n}\n\n- (instancetype)initWithNameSpace:(NSString *)nameSpace {\n    self = [super init];\n    if (self) {\n        _formats = [[NSMutableDictionary alloc] init];\n        _imageTables = [[NSMutableDictionary alloc] init];\n        _requests = [[NSMutableDictionary alloc] init];\n        _nameSpace = nameSpace;\n    }\n    return self;\n}\n\n#pragma mark - Working with Formats\n\n- (void)setFormats:(NSArray *)formats {\n    if ([_formats count] > 0) {\n        [self _logMessage:[NSString stringWithFormat:@\"*** FIC Error: %s FICImageCache has already been configured with its image formats.\", __PRETTY_FUNCTION__]];\n    } else {\n        NSMutableSet *imageTableFiles = [NSMutableSet set];\n        FICImageFormatDevices currentDevice = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad ? FICImageFormatDevicePad : FICImageFormatDevicePhone;\n        for (FICImageFormat *imageFormat in formats) {\n            NSString *formatName = [imageFormat name];\n            FICImageFormatDevices devices = [imageFormat devices];\n            if (devices & currentDevice) {\n                // Only initialize an image table for this format if it is needed on the current device.\n                FICImageTable *imageTable = [[FICImageTable alloc] initWithFormat:imageFormat imageCache:self];\n                [_imageTables setObject:imageTable forKey:formatName];\n                [_formats setObject:imageFormat forKey:formatName];\n                \n                [imageTableFiles addObject:[[imageTable tableFilePath] lastPathComponent]];\n                [imageTableFiles addObject:[[imageTable metadataFilePath] lastPathComponent]];\n            }\n        }\n        \n        // Remove any extraneous files in the image tables directory\n        NSFileManager *fileManager = [NSFileManager defaultManager];\n        NSString *directoryPath = [FICImageTable directoryPath];\n        if (self.nameSpace) {\n            directoryPath = [directoryPath stringByAppendingPathComponent:self.nameSpace];\n        }\n        NSArray *fileNames = [fileManager contentsOfDirectoryAtPath:directoryPath error:nil];\n        for (NSString *fileName in fileNames) {\n            if ([imageTableFiles containsObject:fileName] == NO) {\n                // This is an extraneous file, which is no longer needed.\n                NSString* filePath = [directoryPath stringByAppendingPathComponent:fileName];\n                [fileManager removeItemAtPath:filePath error:nil];\n            }\n        }\n    }\n}\n\n- (FICImageFormat *)formatWithName:(NSString *)formatName {\n    return [_formats objectForKey:formatName];\n}\n\n- (NSArray *)formatsWithFamily:(NSString *)family {\n    NSMutableArray *formats = nil;\n    for (FICImageFormat *format in [_formats allValues]) {\n        if ([[format family] isEqualToString:family]) {\n            if (formats == nil) {\n                formats = [NSMutableArray array];\n            }\n            \n            [formats addObject:format];\n        }\n    }\n    \n    return [formats copy];\n}\n\n#pragma mark - Retrieving Images\n\n- (BOOL)retrieveImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(FICImageCacheCompletionBlock)completionBlock {\n    return [self _retrieveImageForEntity:entity withFormatName:formatName loadSynchronously:YES completionBlock:completionBlock];\n}\n\n- (BOOL)asynchronouslyRetrieveImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(FICImageCacheCompletionBlock)completionBlock {\n    return [self _retrieveImageForEntity:entity withFormatName:formatName loadSynchronously:NO completionBlock:completionBlock];\n}\n\n- (BOOL)_retrieveImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName loadSynchronously:(BOOL)loadSynchronously completionBlock:(FICImageCacheCompletionBlock)completionBlock {\n    NSParameterAssert(formatName);\n\t\n    BOOL imageExists = NO;\n    \n    FICImageTable *imageTable = [_imageTables objectForKey:formatName];\n    NSString *entityUUID = [entity fic_UUID];\n    NSString *sourceImageUUID = [entity fic_sourceImageUUID];\n    \n    if (loadSynchronously == NO && [imageTable entryExistsForEntityUUID:entityUUID sourceImageUUID:sourceImageUUID]) {\n        imageExists = YES;\n        \n        dispatch_async([FICImageCache dispatchQueue], ^{\n            UIImage *image = [imageTable newImageForEntityUUID:entityUUID sourceImageUUID:sourceImageUUID preheatData:YES];\n            \n            if (completionBlock != nil) {\n                dispatch_async(dispatch_get_main_queue(), ^{\n                    completionBlock(entity, formatName, image);\n                });\n            }\n        });\n    } else {\n        UIImage *image = [imageTable newImageForEntityUUID:entityUUID sourceImageUUID:sourceImageUUID preheatData:NO];\n        imageExists = image != nil;\n        \n        dispatch_block_t completionBlockCallingBlock = ^{\n            if (completionBlock != nil) {\n                if (loadSynchronously) {\n                    completionBlock(entity, formatName, image);\n                } else {\n                    dispatch_async(dispatch_get_main_queue(), ^{\n                        completionBlock(entity, formatName, image);\n                    });\n                }\n            }\n        };\n        \n        if (image == nil) {\n            // No image for this UUID exists in the image table. We'll need to ask the delegate to retrieve the source asset.\n            NSURL *sourceImageURL = [entity fic_sourceImageURLWithFormatName:formatName];\n            \n            if (sourceImageURL != nil) {\n                // We check to see if this image is already being fetched.\n                BOOL needsToFetch = NO;\n                @synchronized (_requests) {\n                    NSMutableDictionary *requestDictionary = [_requests objectForKey:sourceImageURL];\n                    if (requestDictionary == nil) {\n                        // If we're here, then we aren't currently fetching this image.\n                        requestDictionary = [NSMutableDictionary dictionary];\n                        [_requests setObject:requestDictionary forKey:sourceImageURL];\n                        needsToFetch = YES;\n                    }\n                    \n                    _FICAddCompletionBlockForEntity(formatName, requestDictionary, entity, completionBlock);\n                }\n\n                if (needsToFetch) {\n                    @autoreleasepool {\n                        UIImage *image;\n                        if ([entity respondsToSelector:@selector(fic_imageForFormat:)]){\n                            FICImageFormat *format = [self formatWithName:formatName];\n                            image = [entity fic_imageForFormat:format];\n                        }\n                        \n                        if (image){\n                            [self _imageDidLoad:image forURL:sourceImageURL];\n                        } else if (_delegateImplementsWantsSourceImageForEntityWithFormatNameCompletionBlock){\n                            [_delegate imageCache:self wantsSourceImageForEntity:entity withFormatName:formatName completionBlock:^(UIImage *sourceImage) {\n                                [self _imageDidLoad:sourceImage forURL:sourceImageURL];\n                            }];\n                        }\n                    }\n                }\n            } else {\n                NSString *message = [NSString stringWithFormat:@\"*** FIC Error: %s entity %@ returned a nil source image URL for image format %@.\", __PRETTY_FUNCTION__, entity, formatName];\n                [self _logMessage:message];\n                \n                completionBlockCallingBlock();\n            }\n        } else {\n            completionBlockCallingBlock();\n        }\n    }\n    \n    return imageExists;\n}\n\n- (void)_imageDidLoad:(UIImage *)image forURL:(NSURL *)URL {\n    NSDictionary *requestDictionary;\n    @synchronized (_requests) {\n        requestDictionary = [_requests objectForKey:URL];\n        [_requests removeObjectForKey:URL];\n        // Now safe to use requestsDictionary outside the lock, because we've taken ownership from _requests\n    }\n\n    if (requestDictionary != nil) {\n        for (NSMutableDictionary *entityDictionary in [requestDictionary allValues]) {\n            id <FICEntity> entity = [entityDictionary objectForKey:FICImageCacheEntityKey];\n            NSString *formatName = [entityDictionary objectForKey:FICImageCacheFormatKey];\n            NSDictionary *completionBlocksDictionary = [entityDictionary objectForKey:FICImageCacheCompletionBlocksKey];\n            if (image != nil){\n                [self _processImage:image forEntity:entity completionBlocksDictionary:completionBlocksDictionary];\n            } else {\n                NSArray *completionBlocks = [completionBlocksDictionary objectForKey:formatName];\n                if (completionBlocks != nil) {\n                    dispatch_async(dispatch_get_main_queue(), ^{\n                        for (FICImageCacheCompletionBlock completionBlock in completionBlocks) {\n                            completionBlock(entity, formatName, nil);\n                        }\n                    });\n                }\n            }\n        }\n    }\n}\n\nstatic void _FICAddCompletionBlockForEntity(NSString *formatName, NSMutableDictionary *entityRequestsDictionary, id <FICEntity> entity, FICImageCacheCompletionBlock completionBlock) {\n    NSString *entityUUID = [entity fic_UUID];\n    NSMutableDictionary *requestDictionary = [entityRequestsDictionary objectForKey:entityUUID];\n    NSMutableDictionary *completionBlocks = nil;\n    \n    if (requestDictionary == nil) {\n        // This is the first time we're dealing with this particular entity for this URL request.\n        requestDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:entity, FICImageCacheEntityKey, nil];\n        [entityRequestsDictionary setObject:requestDictionary forKey:entityUUID];\n        [requestDictionary setObject:formatName forKey:FICImageCacheFormatKey];\n        \n        // Dictionary where keys are imageFormats, and each value is an array of the completion blocks for the requests for this\n        // URL at the specified format.\n        completionBlocks = [NSMutableDictionary dictionary];\n        [requestDictionary setObject:completionBlocks forKey:FICImageCacheCompletionBlocksKey];\n    } else {\n        // We already have a request dictionary for this entity, so we just need to append a completion block.\n        completionBlocks = [requestDictionary objectForKey:FICImageCacheCompletionBlocksKey];\n    }\n    \n    if (completionBlock != nil) {\n        NSMutableArray *blocksArray = [completionBlocks objectForKey:formatName];\n        if (blocksArray == nil) {\n            blocksArray = [NSMutableArray array];\n            [completionBlocks setObject:blocksArray forKey:formatName];\n        }\n        \n        FICImageCacheCompletionBlock completionBlockCopy = [completionBlock copy];\n        [blocksArray addObject:completionBlockCopy];\n    }\n}\n\n#pragma mark - Storing Images\n\n- (void)setImage:(UIImage *)image forEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(FICImageCacheCompletionBlock)completionBlock {\n    if (image != nil && entity != nil) {\n        NSDictionary *completionBlocksDictionary = nil;\n        \n        if (completionBlock != nil) {\n            completionBlocksDictionary = [NSDictionary dictionaryWithObject:[NSArray arrayWithObject:[completionBlock copy]] forKey:formatName];\n        }\n        \n        NSString *entityUUID = [entity fic_UUID];\n        FICImageTable *imageTable = [_imageTables objectForKey:formatName];\n        if (imageTable) {\n            [imageTable deleteEntryForEntityUUID:entityUUID];\n        \n            [self _processImage:image forEntity:entity completionBlocksDictionary:completionBlocksDictionary];\n        } else {\n            [self _logMessage:[NSString stringWithFormat:@\"*** FIC Error: %s Couldn't find image table with format name %@\", __PRETTY_FUNCTION__, formatName]];\n        }\n    }\n}\n\n- (void)_processImage:(UIImage *)image forEntity:(id <FICEntity>)entity completionBlocksDictionary:(NSDictionary *)completionBlocksDictionary {\n    for (NSString *formatToProcess in [self formatsToProcessForCompletionBlocks:completionBlocksDictionary\n                                                                         entity:entity]) {\n        FICImageTable *imageTable = [_imageTables objectForKey:formatToProcess];\n        NSArray *completionBlocks = [completionBlocksDictionary objectForKey:formatToProcess];\n        [self _processImage:image forEntity:entity imageTable:imageTable completionBlocks:completionBlocks];\n    }\n}\n\n- (void)_processImage:(UIImage *)image forEntity:(id <FICEntity>)entity imageTable:(FICImageTable *)imageTable completionBlocks:(NSArray *)completionBlocks {\n    if (imageTable != nil) {\n        if ([entity fic_UUID] == nil) {\n            [self _logMessage:[NSString stringWithFormat:@\"*** FIC Error: %s entity %@ is missing its UUID.\", __PRETTY_FUNCTION__, entity]];\n            return;\n        }\n        \n        if ([entity fic_sourceImageUUID] == nil) {\n            [self _logMessage:[NSString stringWithFormat:@\"*** FIC Error: %s entity %@ is missing its source image UUID.\", __PRETTY_FUNCTION__, entity]];\n            return;\n        }\n        \n        NSString *entityUUID = [entity fic_UUID];\n        NSString *sourceImageUUID = [entity fic_sourceImageUUID];\n        FICImageFormat *imageFormat = [imageTable imageFormat];\n        NSString *imageFormatName = [imageFormat name];\n        FICEntityImageDrawingBlock imageDrawingBlock = [entity fic_drawingBlockForImage:image withFormatName:imageFormatName];\n        \n        dispatch_async([FICImageCache dispatchQueue], ^{\n            [imageTable setEntryForEntityUUID:entityUUID sourceImageUUID:sourceImageUUID imageDrawingBlock:imageDrawingBlock];\n\n            UIImage *resultImage = [imageTable newImageForEntityUUID:entityUUID sourceImageUUID:sourceImageUUID preheatData:NO];\n            \n            if (completionBlocks != nil) {\n                dispatch_async(dispatch_get_main_queue(), ^{\n                    NSString *formatName = [[imageTable imageFormat] name];\n                    for (FICImageCacheCompletionBlock completionBlock in completionBlocks) {\n                        completionBlock(entity, formatName, resultImage);\n                    }\n                });\n            }\n        });\n    }\n}\n\n- (NSSet *)formatsToProcessForCompletionBlocks:(NSDictionary *)completionBlocksDictionary entity:(id <FICEntity>)entity {\n    // At the very least, we must process all formats with pending completion blocks\n    NSMutableSet *formatsToProcess = [NSMutableSet setWithArray:completionBlocksDictionary.allKeys];\n\n    // Get the list of format families included by the formats we have to process\n    NSMutableSet *families;\n    for (NSString *formatToProcess in formatsToProcess) {\n        FICImageTable *imageTable = _imageTables[formatToProcess];\n        FICImageFormat *imageFormat = imageTable.imageFormat;\n        NSString *tableFormatFamily = imageFormat.family;\n        if (tableFormatFamily) {\n            if (!families) {\n                families = [NSMutableSet set];\n            }\n            [families addObject:tableFormatFamily];\n        }\n    }\n\n    // The delegate can override the list of families to process\n    if (_delegateImplementsShouldProcessAllFormatsInFamilyForEntity) {\n        [families minusSet:[families objectsPassingTest:^BOOL(NSString *familyName, BOOL *stop) {\n            return ![_delegate imageCache:self shouldProcessAllFormatsInFamily:familyName forEntity:entity];\n        }]];\n    }\n\n    // Ensure that all formats from all of those families are included in the list\n    if (families.count) {\n        for (FICImageTable *table in _imageTables.allValues) {\n            FICImageFormat *imageFormat = table.imageFormat;\n            NSString *imageFormatName = imageFormat.name;\n            // If we're already processing this format, keep looking\n            if ([formatsToProcess containsObject:imageFormatName]) {\n                continue;\n            }\n\n            // If this format isn't included in any referenced family, keep looking\n            if (![families containsObject:imageFormat.family]) {\n                continue;\n            }\n\n            // If the image already exists, keep going\n            if ([table entryExistsForEntityUUID:entity.fic_UUID sourceImageUUID:entity.fic_sourceImageUUID]) {\n                continue;\n            }\n\n            [formatsToProcess addObject:imageFormatName];\n        }\n    }\n\n    return formatsToProcess;\n}\n\n#pragma mark - Checking for Image Existence\n\n- (BOOL)imageExistsForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName {\n    FICImageTable *imageTable = [_imageTables objectForKey:formatName];\n    NSString *entityUUID = [entity fic_UUID];\n    NSString *sourceImageUUID = [entity fic_sourceImageUUID];\n    \n    BOOL imageExists = [imageTable entryExistsForEntityUUID:entityUUID sourceImageUUID:sourceImageUUID];\n\n    return imageExists;\n}\n\n#pragma mark - Invalidating Image Data\n\n- (void)deleteImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName {\n    FICImageTable *imageTable = [_imageTables objectForKey:formatName];\n    NSString *entityUUID = [entity fic_UUID];\n    [imageTable deleteEntryForEntityUUID:entityUUID];\n}\n\n- (void)cancelImageRetrievalForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName {\n    NSURL *sourceImageURL = [entity fic_sourceImageURLWithFormatName:formatName];\n    NSString *entityUUID = [entity fic_UUID];\n\n    BOOL cancelImageLoadingForEntity = NO;\n    @synchronized (_requests) {\n        NSMutableDictionary *requestDictionary = [_requests objectForKey:sourceImageURL];\n        if (requestDictionary) {\n            NSMutableDictionary *entityRequestsDictionary = [requestDictionary objectForKey:entityUUID];\n            if (entityRequestsDictionary) {\n                NSMutableDictionary *completionBlocksDictionary = [entityRequestsDictionary objectForKey:FICImageCacheCompletionBlocksKey];\n                [completionBlocksDictionary removeObjectForKey:formatName];\n\n                if ([completionBlocksDictionary count] == 0) {\n                    [requestDictionary removeObjectForKey:entityUUID];\n                }\n\n                if ([requestDictionary count] == 0) {\n                    [_requests removeObjectForKey:sourceImageURL];\n                    cancelImageLoadingForEntity = YES;\n                }\n            }\n        }\n    }\n\n    if (cancelImageLoadingForEntity && _delegateImplementsCancelImageLoadingForEntityWithFormatName) {\n        [_delegate imageCache:self cancelImageLoadingForEntity:entity withFormatName:formatName];\n    }\n}\n\n- (void)reset {\n    for (FICImageTable *imageTable in [_imageTables allValues]) {\n        dispatch_async([[self class] dispatchQueue], ^{\n            [imageTable reset];\n        });\n    }\n}\n\n#pragma mark - Logging Errors\n\n- (void)_logMessage:(NSString *)message {\n    if (_delegateImplementsErrorDidOccurWithMessage) {\n        [_delegate imageCache:self errorDidOccurWithMessage:message];\n    }\n}\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache/FICImageFormat.h",
    "content": "//\n//  FICImageFormat.h\n//  FastImageCache\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICImports.h\"\n\n@class FICImageTable;\n\ntypedef NS_OPTIONS(NSUInteger, FICImageFormatDevices) {\n    FICImageFormatDevicePhone = 1 << UIUserInterfaceIdiomPhone,\n    FICImageFormatDevicePad = 1 << UIUserInterfaceIdiomPad,\n};\n\ntypedef NS_ENUM(NSUInteger, FICImageFormatStyle) {\n    FICImageFormatStyle32BitBGRA,\n    FICImageFormatStyle32BitBGR,\n    FICImageFormatStyle16BitBGR,\n    FICImageFormatStyle8BitGrayscale,\n};\n\ntypedef NS_ENUM(NSUInteger, FICImageFormatProtectionMode) {\n    FICImageFormatProtectionModeNone,\n    FICImageFormatProtectionModeComplete,\n    FICImageFormatProtectionModeCompleteUntilFirstUserAuthentication,\n};\n\n/**\n `FICImageFormat` acts as a definition for the types of images that are stored in the image cache. Each image format must have a unique name, but multiple formats can belong to the same family.\n All images associated with a particular format must have the same image dimentions and opacity preference. You can define the maximum number of entries that an image format can accommodate to\n prevent the image cache from consuming too much disk space. Each `<FICImageTable>` managed by the image cache is associated with a single image format.\n */\n\nNS_ASSUME_NONNULL_BEGIN\n@interface FICImageFormat : NSObject <NSCopying>\n\n///------------------------------\n/// @name Image Format Properties\n///------------------------------\n\n/**\n The name of the image format. Each image format must have a unique name.\n \n @note Since multiple instances of Fast Image Cache can exist in the same application, it is important that image format name's be unique across all instances of `<FICImageCache>`. Reverse DNS naming\n is recommended (e.g., com.path.PTUserProfilePhotoLargeImageFormat).\n */\n@property (nonatomic, copy) NSString *name;\n\n/**\n The optional family that the image format belongs to. Families group together related image formats.\n \n @discussion If you are using the image cache to create several different cached variants of the same source image, all of those variants would be unique image formats that share the same family.\n \n For example, you might define a `userPhoto` family that groups together image formats with the following names: `userPhotoSmallThumbnail`, `userPhotoLargeThumbnail`, `userPhotoLargeThumbnailBorder`.\n Ideally, the same source image can be processed to create cached image data for every image format belonging to the same family.\n \n `<FICImageCache>` provides its delegate a chance to process all image formats in a given family at the same time when a particular entity-image format pair is being processed. This allows you to process\n a source image once instead of having to download and process the same source image multiple times for different formats in the same family.\n \n @see [FICImageCacheDelegate imageCache:shouldProcessAllFormatsInFamily:forEntity:]\n */\n@property (nonatomic, copy) NSString *family;\n\n/**\n The size, in points, of the images stored in the image table created by this format.\n */\n@property (nonatomic, assign) CGSize imageSize;\n\n/**\n A bitmask of type `<FICImageFormatStyle>` that defines the style of the image format.\n \n `FICImageFormatStyle` has the following values:\n \n - `FICImageFormatStyle32BitBGRA`: Full-color image format with alpha channel. 8 bits per color component, and 8 bits for the alpha channel.\n - `FICImageFormatStyle32BitBGR`: Full-color image format with no alpha channel. 8 bits per color component. The remaining 8 bits are unused.\n - `FICImageFormatStyle16BitBGR`: Reduced-color image format with no alpha channel. 5 bits per color component. The remaining bit is unused.\n - `FICImageFormatStyle8BitGrayscale`: Grayscale-only image format with no alpha channel.\n \n If you are storing images without an alpha component (e.g., JPEG images), then you should use the `FICImageFormatStyle32BitBGR` style for performance reasons. If you are storing very small images or images\n without a great deal of color complexity, the `FICImageFormatStyle16BitBGR` style may be sufficient and uses less disk space than the 32-bit styles use. For grayscale-only image formats, the\n `FICImageFormatStyle8BitGrayscale` style is sufficient and further reduces disk space usage.\n */\n@property (nonatomic, assign)  FICImageFormatStyle style;\n\n/**\n The maximum number of entries that an image table can contain for this image format.\n \n @discussion Images inserted into the image table defined by this image format after the maximum number of entries has been exceeded will replace the least-recently accessed entry.\n */\n@property (nonatomic, assign) NSInteger maximumCount;\n\n/**\n A bitmask of type `<FICImageFormatDevices>` that defines which devices are managed by an image table.\n \n @discussion If the current device is not included in a particular image format, the image cache will not store image data for that device.\n */\n@property (nonatomic, assign) FICImageFormatDevices devices;\n\n/**\n The size, in pixels, of the images stored in the image table created by this format. This takes into account the screen scale.\n */\n@property (nonatomic, assign, readonly) CGSize pixelSize;\n\n/**\n The bitmap info associated with the images created with this image format.\n */\n@property (nonatomic, assign, readonly) CGBitmapInfo bitmapInfo;\n\n/**\n The number of bytes each pixel of an image created with this image format occupies.\n */\n@property (nonatomic, assign, readonly) NSInteger bytesPerPixel;\n\n/**\n The number of bits each pixel component (e.g., blue, green, red color channels) uses for images created with this image format.\n */\n@property (nonatomic, assign, readonly) NSInteger bitsPerComponent;\n\n/**\n Whether or not the the images represented by this image format are grayscale.\n */\n@property (nonatomic, assign, readonly) BOOL isGrayscale;\n\n/**\n The data protection mode that image table files will be created with.\n \n `FICImageFormatProtectionMode` has the following values:\n \n - `FICImageFormatProtectionModeNone`: No data protection is used. The image table file backing this image format will always be available for reading and writing.\n - `FICImageFormatProtectionModeComplete`: Complete data protection is used. As soon as the system enables data protection (i.e., when the device is locked), the image table file backing this image\n format will not be available for reading and writing. As a result, images of this format should not be requested by Fast Image Cache when executing backgrounded code.\n - `FICImageFormatProtectionModeCompleteUntilFirstUserAuthentication`: Partial data protection is used. After a device restart, until the user unlocks the device for the first time, complete data\n protection is in effect. However, after the device has been unlocked for the first time, the image table file backing this image format will remain available for readin and writing. This mode may be\n a good compromise between encrypting image table files after the device powers down and allowing the files to be accessed successfully by Fast Image Cache, whether or not the device is subsequently\n locked.\n \n @note Data protection can prevent Fast Image Cache from accessing its image table files to read and write image data. If the image data being stored in Fast Image Cache is not sensitive in nature,\n consider using `FICImageFormatProtectionModeNone` to prevent any issues accessing image table files when the disk is encrypted.\n */\n@property (nonatomic, assign) FICImageFormatProtectionMode protectionMode;\n\n/**\n The string representation of `<protectionMode>`.\n */\n@property (nonatomic, assign, readonly) NSString *protectionModeString;\n\n/**\n The dictionary representation of this image format.\n \n @discussion Fast Image Cache automatically serializes the image formats that it uses to disk. If an image format ever changes, Fast Image Cache automatically detects the change and invalidates the\n image table associated with that image format. The image table is then recreated from the updated image format.\n */\n@property (nonatomic, copy, readonly) NSDictionary<NSString*, id> *dictionaryRepresentation;\n\n///-----------------------------------\n/// @name Initializing an Image Format\n///-----------------------------------\n\n/**\n Convenience initializer to create a new image format.\n \n @param name The name of the image format. Each image format must have a unique name.\n \n @param family The optional family that the image format belongs to. See the `<family>` property description for more information.\n \n @param imageSize The size, in points, of the images stored in the image table created by this format.\n \n @param style The style of the image format. See the `<style>` property description for more information.\n \n @param maximumCount The maximum number of entries that an image table can contain for this image format.\n \n @param devices A bitmask of type `<FICImageFormatDevices>` that defines which devices are managed by an image table.\n \n @param protectionMode The data protection mode to use when creating the backing image table file for this image format. See the `<protectionMode>` property description for more information.\n \n @return An autoreleased instance of `FICImageFormat` or one of its subclasses, if any exist.\n */\n+ (instancetype)formatWithName:(NSString *)name family:(NSString *)family imageSize:(CGSize)imageSize style:(FICImageFormatStyle)style maximumCount:(NSInteger)maximumCount devices:(FICImageFormatDevices)devices protectionMode:(FICImageFormatProtectionMode)protectionMode;\n\n@end\nNS_ASSUME_NONNULL_END"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache/FICImageFormat.m",
    "content": "//\n//  FICImageFormat.m\n//  FastImageCache\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICImageFormat.h\"\n#import \"FICImageTable.h\"\n#import \"FICImageTableEntry.h\"\n\n#pragma mark Internal Definitions\n\nstatic NSString *const FICImageFormatNameKey = @\"name\";\nstatic NSString *const FICImageFormatFamilyKey = @\"family\";\nstatic NSString *const FICImageFormatWidthKey = @\"width\";\nstatic NSString *const FICImageFormatHeightKey = @\"height\";\nstatic NSString *const FICImageFormatStyleKey = @\"style\";\nstatic NSString *const FICImageFormatMaximumCountKey = @\"maximumCount\";\nstatic NSString *const FICImageFormatDevicesKey = @\"devices\";\nstatic NSString *const FICImageFormatProtectionModeKey = @\"protectionMode\";\n\n#pragma mark - Class Extension\n\n@interface FICImageFormat () {\n    NSString *_name;\n    NSString *_family;\n    CGSize _imageSize;\n    CGSize _pixelSize;\n    FICImageFormatStyle _style;\n    NSInteger _maximumCount;\n    FICImageFormatDevices _devices;\n    FICImageFormatProtectionMode _protectionMode;\n}\n\n@end\n\n#pragma mark\n\n@implementation FICImageFormat\n\n@synthesize name = _name;\n@synthesize family = _family;\n@synthesize imageSize = _imageSize;\n@synthesize pixelSize = _pixelSize;\n@synthesize style = _style;\n@synthesize maximumCount = _maximumCount;\n@synthesize devices = _devices;\n@synthesize protectionMode = _protectionMode;\n\n#pragma mark - Property Accessors\n\n- (void)setImageSize:(CGSize)imageSize {\n    BOOL currentSizeEqualToNewSize = CGSizeEqualToSize(imageSize, _imageSize);\n    if (currentSizeEqualToNewSize == NO) {\n        _imageSize = imageSize;\n        \n        CGFloat screenScale = [[UIScreen mainScreen] scale];\n        _pixelSize = CGSizeMake(screenScale * _imageSize.width, screenScale * _imageSize.height);\n    }\n}\n\n- (CGBitmapInfo)bitmapInfo {\n    CGBitmapInfo info;\n    switch (_style) {\n        case FICImageFormatStyle32BitBGRA:\n            info = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;\n            break;\n        case FICImageFormatStyle32BitBGR:\n            info = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;\n            break;\n        case FICImageFormatStyle16BitBGR:\n            info = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder16Host;\n            break;\n        case FICImageFormatStyle8BitGrayscale:\n            info = (CGBitmapInfo)kCGImageAlphaNone;\n            break;\n    }\n    return info;\n}\n\n- (NSInteger)bytesPerPixel {\n    NSInteger bytesPerPixel;\n    switch (_style) {\n        case FICImageFormatStyle32BitBGRA:\n        case FICImageFormatStyle32BitBGR:\n            bytesPerPixel = 4;\n            break;\n        case FICImageFormatStyle16BitBGR:\n            bytesPerPixel = 2;\n            break;\n        case FICImageFormatStyle8BitGrayscale:\n            bytesPerPixel = 1;\n            break;\n    }\n    return bytesPerPixel;\n}\n\n- (NSInteger)bitsPerComponent {\n    NSInteger bitsPerComponent;\n    switch (_style) {\n        case FICImageFormatStyle32BitBGRA:\n        case FICImageFormatStyle32BitBGR:\n        case FICImageFormatStyle8BitGrayscale:\n            bitsPerComponent = 8;\n            break;\n        case FICImageFormatStyle16BitBGR:\n            bitsPerComponent = 5;\n            break;\n    }\n    return bitsPerComponent;\n}\n\n- (BOOL)isGrayscale {\n    BOOL isGrayscale;\n    switch (_style) {\n        case FICImageFormatStyle32BitBGRA:\n        case FICImageFormatStyle32BitBGR:\n        case FICImageFormatStyle16BitBGR:\n            isGrayscale = NO;\n            break;\n        case FICImageFormatStyle8BitGrayscale:\n            isGrayscale = YES;\n            break;\n    }\n    return isGrayscale;\n}\n\n- (NSString *)protectionModeString {\n    NSString *protectionModeString = nil;\n    switch (_protectionMode) {\n        case FICImageFormatProtectionModeNone:\n            protectionModeString = NSFileProtectionNone;\n            break;\n        case FICImageFormatProtectionModeComplete:\n            protectionModeString = NSFileProtectionComplete;\n            break;\n        case FICImageFormatProtectionModeCompleteUntilFirstUserAuthentication:\n            protectionModeString = NSFileProtectionCompleteUntilFirstUserAuthentication;\n            break;\n    }\n    return protectionModeString;\n}\n\n#pragma mark - Object Lifecycle\n\n+ (instancetype)formatWithName:(NSString *)name family:(NSString *)family imageSize:(CGSize)imageSize style:(FICImageFormatStyle)style maximumCount:(NSInteger)maximumCount devices:(FICImageFormatDevices)devices protectionMode:(FICImageFormatProtectionMode)protectionMode {\n    FICImageFormat *imageFormat = [[FICImageFormat alloc] init];\n    \n    [imageFormat setName:name];\n    [imageFormat setFamily:family];\n    [imageFormat setImageSize:imageSize];\n    [imageFormat setStyle:style];\n    [imageFormat setMaximumCount:maximumCount];\n    [imageFormat setDevices:devices];\n    [imageFormat setProtectionMode:protectionMode];\n    \n    return imageFormat;\n}\n\n#pragma mark - Working with Dictionary Representations\n\n- (NSDictionary *)dictionaryRepresentation {\n    NSMutableDictionary *dictionaryRepresentation = [NSMutableDictionary dictionary];\n    \n    [dictionaryRepresentation setValue:_name forKey:FICImageFormatNameKey];\n    [dictionaryRepresentation setValue:_family forKey:FICImageFormatFamilyKey];\n    [dictionaryRepresentation setValue:[NSNumber numberWithUnsignedInteger:_imageSize.width] forKey:FICImageFormatWidthKey];\n    [dictionaryRepresentation setValue:[NSNumber numberWithUnsignedInteger:_imageSize.height] forKey:FICImageFormatHeightKey];\n    [dictionaryRepresentation setValue:[NSNumber numberWithInt:_style] forKey:FICImageFormatStyleKey];\n    [dictionaryRepresentation setValue:[NSNumber numberWithUnsignedInteger:_maximumCount] forKey:FICImageFormatMaximumCountKey];\n    [dictionaryRepresentation setValue:[NSNumber numberWithInt:_devices] forKey:FICImageFormatDevicesKey];\n    [dictionaryRepresentation setValue:[NSNumber numberWithUnsignedInteger:_protectionMode] forKey:FICImageFormatProtectionModeKey];\n\n    [dictionaryRepresentation setValue:[NSNumber numberWithFloat:[[UIScreen mainScreen] scale]] forKey:FICImageTableScreenScaleKey];\n    [dictionaryRepresentation setValue:[NSNumber numberWithUnsignedInteger:[FICImageTableEntry metadataVersion]] forKey:FICImageTableEntryDataVersionKey];\n    \n    return dictionaryRepresentation;\n}\n\n#pragma mark - Protocol Implementations\n\n#pragma mark - NSObject (NSCopying)\n\n- (id)copyWithZone:(NSZone *)zone {\n    FICImageFormat *imageFormatCopy = [[FICImageFormat alloc] init];\n    \n    [imageFormatCopy setName:[self name]];\n    [imageFormatCopy setFamily:[self family]];\n    [imageFormatCopy setImageSize:[self imageSize]];\n    [imageFormatCopy setStyle:[self style]];\n    [imageFormatCopy setMaximumCount:[self maximumCount]];\n    [imageFormatCopy setDevices:[self devices]];\n    [imageFormatCopy setProtectionMode:[self protectionMode]];\n    \n    return imageFormatCopy;\n}\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache/FICImageTable.h",
    "content": "//\n//  FICImageTable.h\n//  FastImageCache\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICImports.h\"\n#import \"FICImageCache.h\"\n#import \"FICEntity.h\"\n\n@class FICImageFormat;\n@class FICImageTableChunk;\n@class FICImageTableEntry;\n@class FICImage;\n\nNS_ASSUME_NONNULL_BEGIN\n\nextern NSString *const FICImageTableEntryDataVersionKey;\nextern NSString *const FICImageTableScreenScaleKey;\n\n/**\n `FICImageTable` is the primary class that efficiently stores and retrieves cached image data. Image tables are defined by instances of `<FICImageFormat>`. Each image table is backed by a single\n file on disk that sequentially stores image entry data. All images in an image table are either opaque or not and have the same dimensions. Therefore, when defining your image formats, keep in\n mind that you cannot mix image dimensions or whether or not an image is opaque.\n */\n@interface FICImageTable : NSObject\n\n///-----------------------------\n/// @name Image Table Properties\n///-----------------------------\n\n/**\n The file system path where the image table's data file is located.\n */\n@property (nonatomic, copy, readonly) NSString *tableFilePath;\n\n/**\n The file system path where the image table's metadata file is located.\n */\n@property (nonatomic, copy, readonly) NSString *metadataFilePath;\n\n/**\n The image format that describes the image table.\n */\n@property (nonatomic, strong, readonly) FICImageFormat *imageFormat;\n\n///-----------------------------------------------\n/// @name Accessing Information about Image Tables\n///-----------------------------------------------\n\n/**\n Returns the page size for the current device.\n \n @return The number of bytes in a page of memory.\n \n @discussion This class method calls the UNIX function `getpagesize()` exactly once, storing the result in a static local variable.\n */\n+ (int)pageSize;\n\n/**\n Returns the file system path for the directory that stores image table files.\n \n @return The string representing the file system directory path where image table files are stored.\n \n @warning Image table files are stored in the user's caches directory, so you should be prepared for the image tables to be deleted from the file system at any time.\n */\n+ (NSString *)directoryPath;\n\n///----------------------------------\n/// @name Initializing an Image Table\n///----------------------------------\n\n/**\n Initializes a new image table described by the provided image format.\n \n @param imageFormat The image format that describes the image table.\n \n @param imageCache The instance of `<FICImageCache>` that owns this image table.\n \n @return A new image table.\n \n @warning `FICImageTable` raises an exception if `imageFormat` is `nil`. `FICImageTable`'s implementation of `-init` simply calls through to this initializer, passing `nil` for `imageFormat`.\n */\n- (nullable instancetype)initWithFormat:(FICImageFormat *)imageFormat imageCache:(FICImageCache *)imageCache NS_DESIGNATED_INITIALIZER;\n-(instancetype) init __attribute__((unavailable(\"Invoke the designated initializer initWithFormat:imageCache: instead\")));\n+(instancetype) new __attribute__((unavailable(\"Invoke the designated initializer initWithFormat:imageCache: instead\")));\n\n///------------------------------------------------\n/// @name Storing, Retrieving, and Deleting Entries\n///------------------------------------------------\n\n/**\n Stores new image entry data in the image table.\n \n @param entityUUID The UUID of the entity that uniquely identifies an image table entry. Must not be `nil`.\n \n @param sourceImageUUID The UUID of the source image that represents the actual image data stored in an image table entry. Must not be `nil`.\n \n @param imageDrawingBlock The drawing block provided by the entity that actually draws the source image into a bitmap context. Must not be `nil`.\n \n @discussion Objects conforming to `<FICEntity>` are responsible for providing an image drawing block that does the actual drawing of their source images to a bitmap context provided\n by the image table. Drawing in the provided bitmap context writes the uncompressed image data directly to the image table file on disk.\n \n @note If any of the parameters to this method are `nil`, this method does nothing.\n \n @see [FICEntity drawingBlockForImage:withFormatName:]\n */\n- (void)setEntryForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID imageDrawingBlock:(FICEntityImageDrawingBlock)imageDrawingBlock;\n\n/**\n Returns a new image from the image entry data in the image table.\n \n @param entityUUID The UUID of the entity that uniquely identifies an image table entry. Must not be `nil`.\n \n @param sourceImageUUID The UUID of the source image that represents the actual image data stored in an image table entry. Must not be `nil`.\n \n @param preheatData A `BOOL` indicating whether or not the entry's image data should be preheated. See `<[FICImageTableEntry preheat]>` for more information.\n \n @return A new image created from the entry data stored in the image table or `nil` if something went wrong.\n \n @discussion The `UIImage` returned by this method is initialized by a `CGImageRef` backed directly by mapped file data, so no memory copy occurs.\n \n @note If either of the first two parameters to this method are `nil`, the return value is `nil`.\n \n @note If either the entity UUID or the source image UUID doesn't match the corresponding UUIDs in the entry data, then something has changed. The entry data is deleted for the\n provided entity UUID, and `nil` is returned.\n */\n- (nullable UIImage *)newImageForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID preheatData:(BOOL)preheatData;\n\n/**\n Deletes image entry data in the image table.\n \n @param entityUUID The UUID of the entity that uniquely identifies an image table entry. Must not be `nil`.\n \n @note If `entityUUID` is `nil`, this method does nothing.\n */\n- (void)deleteEntryForEntityUUID:(NSString *)entityUUID;\n\n///-----------------------------------\n/// @name Checking for Entry Existence\n///-----------------------------------\n\n/**\n Returns whether or not an entry exists in the image table.\n \n @param entityUUID The UUID of the entity that uniquely identifies an image table entry. Must not be `nil`.\n \n @param sourceImageUUID The UUID of the source image that represents the actual image data stored in an image table entry. Must not be `nil`.\n \n @return `YES` if an entry exists in the image table for the provided entity UUID and source image UUID. Otherwise, `NO`.\n \n @note If either of the parameters to this method are `nil`, the return value is `NO`.\n \n @note If either the entity UUID or the source image UUID doesn't match the corresponding UUIDs in the entry data, then something has changed. The entry data is deleted for the\n provided entity UUID, and `NO` is returned.\n */\n- (BOOL)entryExistsForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID;\n\n///--------------------------------\n/// @name Resetting the Image Table\n///--------------------------------\n\n/**\n Resets the image table by deleting all its data and metadata.\n */\n- (void)reset;\n\n@end\n\nNS_ASSUME_NONNULL_END"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache/FICImageTable.m",
    "content": "//\n//  FICImageTable.m\n//  FastImageCache\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICImageTable.h\"\n#import \"FICImageFormat.h\"\n#import \"FICImageCache.h\"\n#import \"FICImageTableChunk.h\"\n#import \"FICImageTableEntry.h\"\n#import \"FICUtilities.h\"\n#import <libkern/OSAtomic.h>\n\n#import \"FICImageCache+FICErrorLogging.h\"\n\n#pragma mark External Definitions\n\nNSString *const FICImageTableEntryDataVersionKey = @\"FICImageTableEntryDataVersionKey\";\nNSString *const FICImageTableScreenScaleKey = @\"FICImageTableScreenScaleKey\";\n\n#pragma mark - Internal Definitions\n\nstatic NSString *const FICImageTableMetadataFileExtension = @\"metadata\";\nstatic NSString *const FICImageTableFileExtension = @\"imageTable\";\n\nstatic NSString *const FICImageTableIndexMapKey = @\"indexMap\";\nstatic NSString *const FICImageTableContextMapKey = @\"contextMap\";\nstatic NSString *const FICImageTableMRUArrayKey = @\"mruArray\";\nstatic NSString *const FICImageTableFormatKey = @\"format\";\n\n#pragma mark - Class Extension\n\n@interface FICImageTable () {\n    FICImageFormat *_imageFormat;\n    CGFloat _screenScale;\n    NSInteger _imageRowLength;\n    \n    NSString *_filePath;\n    int _fileDescriptor;\n    off_t _fileLength;\n    \n    NSUInteger _entryCount;\n    NSInteger _entryLength;\n    NSUInteger _entriesPerChunk;\n    NSInteger _imageLength;\n    \n    size_t _chunkLength;\n    NSInteger _chunkCount;\n    \n    NSMutableDictionary *_chunkDictionary;\n    NSCountedSet *_chunkSet;\n    \n    NSRecursiveLock *_lock;\n    CFMutableDictionaryRef _indexNumbers;\n    \n    // Image table metadata\n    NSMutableDictionary *_indexMap;         // Key: entity UUID, value: integer index into the table file\n    NSMutableDictionary *_sourceImageMap;   // Key: entity UUID, value: source image UUID\n    NSMutableIndexSet *_occupiedIndexes;\n    NSMutableOrderedSet *_MRUEntries;\n    NSCountedSet *_inUseEntries;\n    NSDictionary *_imageFormatDictionary;\n    int32_t _metadataVersion;\n\n    NSString *_fileDataProtectionMode;\n    BOOL _canAccessData;\n}\n\n@property (nonatomic, weak) FICImageCache *imageCache;\n\n@end\n\n#pragma mark\n\n@implementation FICImageTable\n\n@synthesize imageFormat =_imageFormat;\n\n#pragma mark - Property Accessors (Public)\n\n- (NSString *)tableFilePath {\n    NSString *tableFilePath = [[_imageFormat name] stringByAppendingPathExtension:FICImageTableFileExtension];\n    tableFilePath = [[self directoryPath] stringByAppendingPathComponent:tableFilePath];\n    \n    return tableFilePath;\n}\n\n- (NSString *)metadataFilePath {\n    NSString *metadataFilePath = [[_imageFormat name] stringByAppendingPathExtension:FICImageTableMetadataFileExtension];\n    metadataFilePath = [[self directoryPath] stringByAppendingPathComponent:metadataFilePath];\n    \n    return metadataFilePath;\n}\n\n- (NSString *) directoryPath {\n    NSString *directoryPath = [FICImageTable directoryPath];\n    if (self.imageCache.nameSpace) {\n        directoryPath = [directoryPath stringByAppendingPathComponent:self.imageCache.nameSpace];\n    }\n    return directoryPath;\n}\n\n#pragma mark - Class-Level Definitions\n\n+ (int)pageSize {\n    static int __pageSize = 0;\n    \n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        __pageSize = getpagesize();\n    });\n\n    return __pageSize;\n}\n\n+ (NSString *)directoryPath {\n    static NSString *__directoryPath = nil;\n    \n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);\n        __directoryPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@\"ImageTables\"];\n        \n        NSFileManager *fileManager = [[NSFileManager alloc] init];\n        BOOL directoryExists = [fileManager fileExistsAtPath:__directoryPath];\n        if (directoryExists == NO) {\n            [fileManager createDirectoryAtPath:__directoryPath withIntermediateDirectories:YES attributes:nil error:nil];\n        }\n    });\n    \n    return __directoryPath;\n}\n\n#pragma mark - Object Lifecycle\n\n- (instancetype)initWithFormat:(FICImageFormat *)imageFormat imageCache:(FICImageCache *)imageCache {\n    self = [super init];\n    \n    if (self != nil) {\n        if (imageFormat == nil) {\n            [NSException raise:NSInvalidArgumentException format:@\"*** FIC Exception: %s must pass in an image format.\", __PRETTY_FUNCTION__];\n        }\n        if (imageCache == nil) {\n            [NSException raise:NSInvalidArgumentException format:@\"*** FIC Exception: %s must pass in an image cache.\", __PRETTY_FUNCTION__];\n        }\n        \n        self.imageCache = imageCache;\n        \n        _lock = [[NSRecursiveLock alloc] init];\n        _indexNumbers = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);\n        \n        _imageFormat = [imageFormat copy];\n        _imageFormatDictionary = [imageFormat dictionaryRepresentation];\n        \n        _screenScale = [[UIScreen mainScreen] scale];\n        \n        CGSize pixelSize = [_imageFormat pixelSize];\n        NSInteger bytesPerPixel = [_imageFormat bytesPerPixel];\n        _imageRowLength = (NSInteger)FICByteAlignForCoreAnimation(pixelSize.width * bytesPerPixel);\n        _imageLength = _imageRowLength * (NSInteger)pixelSize.height;\n        \n        _chunkDictionary = [[NSMutableDictionary alloc] init];\n        _chunkSet = [[NSCountedSet alloc] init];\n        \n        _indexMap = [[NSMutableDictionary alloc] init];\n        _occupiedIndexes = [[NSMutableIndexSet alloc] init];\n        \n        _MRUEntries = [[NSMutableOrderedSet alloc] init];\n        _inUseEntries = [NSCountedSet set];\n\n        _sourceImageMap = [[NSMutableDictionary alloc] init];\n        \n        _filePath = [[self tableFilePath] copy];\n        \n        [self _loadMetadata];\n        \n        NSString *directoryPath = [self directoryPath];\n        \n        NSFileManager *fileManager = [[NSFileManager alloc] init];\n        \n        BOOL isDirectory;\n        if (self.imageCache.nameSpace && ![fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory]) {\n            [fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:nil];\n        }\n        \n        if ([fileManager fileExistsAtPath:_filePath] == NO) {\n            NSMutableDictionary *attributes = [NSMutableDictionary dictionary];\n            [attributes setValue:[_imageFormat protectionModeString] forKeyPath:NSFileProtectionKey];\n            [fileManager createFileAtPath:_filePath contents:nil attributes:attributes];\n        }\n       \n        NSDictionary *attributes = [fileManager attributesOfItemAtPath:_filePath error:NULL];\n        _fileDataProtectionMode = [attributes objectForKey:NSFileProtectionKey];\n        \n        _fileDescriptor = open([_filePath fileSystemRepresentation], O_RDWR | O_CREAT, 0666);\n        \n        if (_fileDescriptor >= 0) {\n            // The size of each entry in the table needs to be page-aligned. This will cause each entry to have a page-aligned base\n            // address, which will help us avoid Core Animation having to copy our images when we eventually set them on layers.\n            _entryLength = (NSInteger)FICByteAlign(_imageLength + sizeof(FICImageTableEntryMetadata), [FICImageTable pageSize]);\n            \n            // Each chunk will map in n entries. Try to keep the chunkLength around 2MB.\n            NSInteger goalChunkLength = 2 * (1024 * 1024);\n            NSInteger goalEntriesPerChunk = goalChunkLength / _entryLength;\n            _entriesPerChunk = MAX(4, goalEntriesPerChunk);\n            if ([self _maximumCount] > [_imageFormat maximumCount]) {\n                NSString *message = [NSString stringWithFormat:@\"*** FIC Warning: growing desired maximumCount (%ld) for format %@ to fill a chunk (%ld)\", (long)[_imageFormat maximumCount], [_imageFormat name], (long)[self _maximumCount]];\n                [self.imageCache _logMessage:message];\n            }\n            _chunkLength = (size_t)(_entryLength * _entriesPerChunk);\n            \n            _fileLength = lseek(_fileDescriptor, 0, SEEK_END);\n            _entryCount = (NSInteger)(_fileLength / _entryLength);\n            _chunkCount = (_entryCount + _entriesPerChunk - 1) / _entriesPerChunk;\n            \n            if ([_indexMap count] > _entryCount) {\n                // It's possible that someone deleted the image table file but left behind the metadata file. If this happens, the metadata\n                // will obviously become out of sync with the image table file, so we need to reset the image table.\n                [self reset];\n            }\n        } else {\n            // If something goes wrong and we can't open the image table file, then we have no choice but to release and nil self.\n            NSString *message = [NSString stringWithFormat:@\"*** FIC Error: %s could not open the image table file at path %@. The image table was not created.\", __PRETTY_FUNCTION__, _filePath];\n            [self.imageCache _logMessage:message];\n\n            self = nil;\n        }    \n    }\n    \n    return self;\n}\n\n- (void)dealloc {\n    if (_fileDescriptor >= 0) {\n        close(_fileDescriptor);\n    }\n}\n\n#pragma mark - Working with Chunks\n\n- (FICImageTableChunk *)_cachedChunkAtIndex:(NSInteger)index {\n    return [_chunkDictionary objectForKey:@(index)];\n}\n\n- (void)_setChunk:(FICImageTableChunk *)chunk index:(NSInteger)index {\n    NSNumber *indexNumber = @(index);\n    if (chunk != nil) {\n        [_chunkDictionary setObject:chunk forKey:indexNumber];\n    } else {\n        [_chunkDictionary removeObjectForKey:indexNumber];\n    }\n}\n\n- (FICImageTableChunk *)_chunkAtIndex:(NSInteger)index {\n    FICImageTableChunk *chunk = nil;\n    \n    if (index < _chunkCount) {\n        chunk = [self _cachedChunkAtIndex:index];\n        \n        if (chunk == nil) {\n            size_t chunkLength = _chunkLength;\n            off_t chunkOffset = index * (off_t)_chunkLength;\n            if (chunkOffset + chunkLength > _fileLength) {\n                chunkLength = (size_t)(_fileLength - chunkOffset);\n            }\n                    \n            chunk = [[FICImageTableChunk alloc] initWithFileDescriptor:_fileDescriptor index:index length:chunkLength];\n            [self _setChunk:chunk index:index];\n        }\n    }\n    \n    if (!chunk) {\n        NSString *message = [NSString stringWithFormat:@\"*** FIC Error: %s failed to get chunk for index %ld.\", __PRETTY_FUNCTION__, (long)index];\n        [self.imageCache _logMessage:message];\n    }\n    \n    return chunk;\n}\n\n#pragma mark - Storing, Retrieving, and Deleting Entries\n\n- (void)setEntryForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID imageDrawingBlock:(FICEntityImageDrawingBlock)imageDrawingBlock {\n    if (entityUUID != nil && sourceImageUUID != nil && imageDrawingBlock != NULL) {\n        [_lock lock];\n        \n        NSInteger newEntryIndex = [self _indexOfEntryForEntityUUID:entityUUID];\n        if (newEntryIndex == NSNotFound) {\n            newEntryIndex = [self _nextEntryIndex];\n            \n            if (newEntryIndex >= _entryCount) {\n                // Determine how many chunks we need to support new entry index.\n                // Number of entries should always be a multiple of _entriesPerChunk\n                NSInteger numberOfEntriesRequired = newEntryIndex + 1;\n                NSInteger newChunkCount = _entriesPerChunk > 0 ? ((numberOfEntriesRequired + _entriesPerChunk - 1) / _entriesPerChunk) : 0;\n                NSInteger newEntryCount = newChunkCount * _entriesPerChunk;\n                [self _setEntryCount:newEntryCount];\n            }\n        }\n        \n        if (newEntryIndex < _entryCount) {\n            CGSize pixelSize = [_imageFormat pixelSize];\n            CGBitmapInfo bitmapInfo = [_imageFormat bitmapInfo];\n            CGColorSpaceRef colorSpace = [_imageFormat isGrayscale] ? CGColorSpaceCreateDeviceGray() : CGColorSpaceCreateDeviceRGB();\n            NSInteger bitsPerComponent = [_imageFormat bitsPerComponent];\n            \n            // Create context whose backing store *is* the mapped file data\n            FICImageTableEntry *entryData = [self _entryDataAtIndex:newEntryIndex];\n            if (entryData != nil) {\n                [entryData setEntityUUIDBytes:FICUUIDBytesWithString(entityUUID)];\n                [entryData setSourceImageUUIDBytes:FICUUIDBytesWithString(sourceImageUUID)];\n                \n                // Update our book-keeping\n                [_indexMap setObject:[NSNumber numberWithUnsignedInteger:newEntryIndex] forKey:entityUUID];\n                [_occupiedIndexes addIndex:newEntryIndex];\n                [_sourceImageMap setObject:sourceImageUUID forKey:entityUUID];\n                \n                // Update MRU array\n                [self _entryWasAccessedWithEntityUUID:entityUUID];\n                [self saveMetadata];\n                \n                // Unique, unchanging pointer for this entry's index\n                NSNumber *indexNumber = [self _numberForEntryAtIndex:newEntryIndex];\n                \n                // Relinquish the image table lock before calling potentially slow imageDrawingBlock to unblock other FIC operations\n                [_lock unlock];\n                \n                CGContextRef context = CGBitmapContextCreate([entryData bytes], pixelSize.width, pixelSize.height, bitsPerComponent, _imageRowLength, colorSpace, bitmapInfo);\n                \n                CGContextTranslateCTM(context, 0, pixelSize.height);\n                CGContextScaleCTM(context, _screenScale, -_screenScale);\n                \n                @synchronized(indexNumber) {\n                    // Call drawing block to allow client to draw into the context\n                    imageDrawingBlock(context, [_imageFormat imageSize]);\n                    CGContextRelease(context);\n                \n                    // Write the data back to the filesystem\n                    [entryData flush];\n                }\n            } else {\n                [_lock unlock];\n            }\n            \n            CGColorSpaceRelease(colorSpace);\n        } else {\n            [_lock unlock];\n        }\n    }\n}\n\n- (UIImage *)newImageForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID preheatData:(BOOL)preheatData {\n    UIImage *image = nil;\n    \n    if (entityUUID != nil && sourceImageUUID != nil) {\n        [_lock lock];\n\n        FICImageTableEntry *entryData = [self _entryDataForEntityUUID:entityUUID];\n        if (entryData != nil) {\n            NSString *entryEntityUUID = FICStringWithUUIDBytes([entryData entityUUIDBytes]);\n            NSString *entrySourceImageUUID = FICStringWithUUIDBytes([entryData sourceImageUUIDBytes]);\n            BOOL entityUUIDIsCorrect = entityUUID == nil || [entityUUID caseInsensitiveCompare:entryEntityUUID] == NSOrderedSame;\n            BOOL sourceImageUUIDIsCorrect = sourceImageUUID == nil || [sourceImageUUID caseInsensitiveCompare:entrySourceImageUUID] == NSOrderedSame;\n            \n            NSNumber *indexNumber = [self _numberForEntryAtIndex:[entryData index]];\n            @synchronized(indexNumber) {\n                if (entityUUIDIsCorrect == NO || sourceImageUUIDIsCorrect == NO) {\n                    // The UUIDs don't match, so we need to invalidate the entry.\n                    [self deleteEntryForEntityUUID:entityUUID];\n                } else {\n                    [self _entryWasAccessedWithEntityUUID:entityUUID];\n                    \n                    // Create CGImageRef whose backing store *is* the mapped image table entry. We avoid a memcpy this way.\n                    CGDataProviderRef dataProvider = CGDataProviderCreateWithData((__bridge_retained void *)entryData, [entryData bytes], [entryData imageLength], _FICReleaseImageData);\n                    \n                    [_inUseEntries addObject:entityUUID];\n                    __weak FICImageTable *weakSelf = self;\n                    [entryData executeBlockOnDealloc:^{\n                        [weakSelf removeInUseForEntityUUID:entityUUID];\n                    }];\n                    \n                    CGSize pixelSize = [_imageFormat pixelSize];\n                    CGBitmapInfo bitmapInfo = [_imageFormat bitmapInfo];\n                    NSInteger bitsPerComponent = [_imageFormat bitsPerComponent];\n                    NSInteger bitsPerPixel = [_imageFormat bytesPerPixel] * 8;\n                    CGColorSpaceRef colorSpace = [_imageFormat isGrayscale] ? CGColorSpaceCreateDeviceGray() : CGColorSpaceCreateDeviceRGB();\n                    \n                    CGImageRef imageRef = CGImageCreate(pixelSize.width, pixelSize.height, bitsPerComponent, bitsPerPixel, _imageRowLength, colorSpace, bitmapInfo, dataProvider, NULL, false, (CGColorRenderingIntent)0);\n                    CGDataProviderRelease(dataProvider);\n                    CGColorSpaceRelease(colorSpace);\n                    \n                    if (imageRef != NULL) {\n                        image = [[UIImage alloc] initWithCGImage:imageRef scale:_screenScale orientation:UIImageOrientationUp];\n                        CGImageRelease(imageRef);\n                    } else {\n                        NSString *message = [NSString stringWithFormat:@\"*** FIC Error: %s could not create a new CGImageRef for entity UUID %@.\", __PRETTY_FUNCTION__, entityUUID];\n                        [self.imageCache _logMessage:message];\n                    }\n                    \n                    if (image != nil && preheatData) {\n                        [entryData preheat];\n                    }\n                }\n            }\n        }\n        \n        [_lock unlock];\n    }\n    \n    return image;\n}\n\nstatic void _FICReleaseImageData(void *info, const void *data, size_t size) {\n    if (info) {\n        CFRelease(info);\n    }\n}\n\n- (void)removeInUseForEntityUUID:(NSString *)entityUUID {\n    [_lock lock];\n    [_inUseEntries removeObject:entityUUID];\n    [_lock unlock];\n}\n\n- (void)deleteEntryForEntityUUID:(NSString *)entityUUID {\n    if (entityUUID != nil) {\n        [_lock lock];\n        \n        NSInteger MRUIndex = [_MRUEntries indexOfObject:entityUUID];\n        if (MRUIndex != NSNotFound) {\n            [_MRUEntries removeObjectAtIndex:MRUIndex];\n        }\n        \n        NSInteger index = [self _indexOfEntryForEntityUUID:entityUUID];\n        if (index != NSNotFound) {\n            [_sourceImageMap removeObjectForKey:entityUUID];\n            [_indexMap removeObjectForKey:entityUUID];\n            [_occupiedIndexes removeIndex:index];\n            [self saveMetadata];\n        }\n        \n        [_lock unlock];\n    }\n}\n\n#pragma mark - Checking for Entry Existence\n\n- (BOOL)entryExistsForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID {\n    BOOL imageExists = NO;\n\n    [_lock lock];\n    \n    FICImageTableEntry *entryData = [self _entryDataForEntityUUID:entityUUID];\n    if (entryData != nil && sourceImageUUID != nil) {\n        NSString *existingEntityUUID = FICStringWithUUIDBytes([entryData entityUUIDBytes]);\n        BOOL entityUUIDIsCorrect = [entityUUID isEqualToString:existingEntityUUID];\n        \n        NSString *existingSourceImageUUID = FICStringWithUUIDBytes([entryData sourceImageUUIDBytes]);\n        BOOL sourceImageUUIDIsCorrect = [sourceImageUUID isEqualToString:existingSourceImageUUID];\n        \n        if (entityUUIDIsCorrect == NO || sourceImageUUIDIsCorrect == NO) {\n            // The source image UUIDs don't match, so the image data should be deleted for this entity.\n            [self deleteEntryForEntityUUID:entityUUID];\n            entryData = nil;\n        }\n    }\n    \n    [_lock unlock];\n    \n    imageExists = entryData != nil;\n    \n    return imageExists;\n}\n\n#pragma mark - Working with Entries\n\n- (NSInteger)_maximumCount {\n    return MAX([_imageFormat maximumCount], _entriesPerChunk);\n}\n\n- (void)_setEntryCount:(NSInteger)entryCount {\n    if (entryCount != _entryCount) {        \n        off_t fileLength = entryCount * _entryLength;\n        int result = ftruncate(_fileDescriptor, fileLength);\n        \n        if (result != 0) {\n            NSString *message = [NSString stringWithFormat:@\"*** FIC Error: %s ftruncate returned %d, error = %d, fd = %d, filePath = %@, length = %lld\", __PRETTY_FUNCTION__, result, errno, _fileDescriptor, _filePath, fileLength];\n            [self.imageCache _logMessage:message];\n        } else {\n            _fileLength = fileLength;\n            _entryCount = entryCount;\n            _chunkCount = _entriesPerChunk > 0 ? ((_entryCount + _entriesPerChunk - 1) / _entriesPerChunk) : 0;\n            \n            NSDictionary *chunkDictionary = [_chunkDictionary copy];\n            for (FICImageTableChunk *chunk in [chunkDictionary allValues]) {\n                if ([chunk length] != _chunkLength) {\n                    // Issue 31: https://github.com/path/FastImageCache/issues/31\n                    // Somehow, we have a partial chunk whose length needs to be adjusted\n                    // since we changed our file length.\n                    [self _setChunk:nil index:[chunk index]];\n                }\n            }\n        }\n    }\n}\n\n// There's inherently a race condition between when you ask whether the data is\n// accessible and when you try to use that data. Sidestep this issue altogether\n// by using NSFileProtectionNone\n- (BOOL)canAccessEntryData {\n    if ([_fileDataProtectionMode isEqualToString:NSFileProtectionNone])\n        return YES;\n    \n    if ([_fileDataProtectionMode isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication] && _canAccessData)\n        return YES;\n    \n    // -[UIApplication isProtectedDataAvailable] checks whether the keybag is locked or not\n    UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];\n    if (application) {\n        _canAccessData = [application isProtectedDataAvailable];\n    }\n    \n    // We have to fallback to a direct check on the file if either:\n    // - The application doesn't exist (happens in some extensions)\n    // - The keybag is locked, but the file might still be accessible because the mode is \"until first user authentication\"\n    if (!application || (!_canAccessData && [_fileDataProtectionMode isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication])) {\n        int fd;\n        _canAccessData = ((fd = open([_filePath fileSystemRepresentation], O_RDONLY)) != -1);\n        if (_canAccessData)\n            close(fd);\n    }\n    \n    return _canAccessData;\n}\n\n- (FICImageTableEntry *)_entryDataAtIndex:(NSInteger)index {\n    FICImageTableEntry *entryData = nil;\n    \n    [_lock lock];\n\n    BOOL canAccessData = [self canAccessEntryData];\n    if (index < _entryCount && canAccessData) {\n        off_t entryOffset = index * _entryLength;\n        size_t chunkIndex = (size_t)(entryOffset / _chunkLength);\n        \n        FICImageTableChunk *chunk = [self _chunkAtIndex:chunkIndex];\n        if (chunk != nil) {\n            off_t chunkOffset = chunkIndex * _chunkLength;\n            off_t entryOffsetInChunk = entryOffset - chunkOffset;\n            void *mappedChunkAddress = [chunk bytes];\n            void *mappedEntryAddress = mappedChunkAddress + entryOffsetInChunk;\n            entryData = [[FICImageTableEntry alloc] initWithImageTableChunk:chunk bytes:mappedEntryAddress length:_entryLength];\n            \n            if (entryData) {\n                [entryData setImageCache:self.imageCache];\n                [entryData setIndex:index];\n                [_chunkSet addObject:chunk];\n            \n                __weak FICImageTable *weakSelf = self;\n                [entryData executeBlockOnDealloc:^{\n                    [weakSelf _entryWasDeallocatedFromChunk:chunk];\n                }];\n            }\n        }\n    }\n    \n    [_lock unlock];\n    \n    if (!entryData) {\n        NSString *message = nil;\n        if (canAccessData) {\n            message = [NSString stringWithFormat:@\"*** FIC Error: %s failed to get entry for index %ld.\", __PRETTY_FUNCTION__, (long)index];\n        } else {\n            message = [NSString stringWithFormat:@\"*** FIC Error: %s. Cannot get entry data because imageTable's file has data protection enabled and that data is not currently accessible.\", __PRETTY_FUNCTION__];\n        }\n        [self.imageCache _logMessage:message];\n    }\n    \n    return entryData;\n}\n\n- (void)_entryWasDeallocatedFromChunk:(FICImageTableChunk *)chunk {\n    [_lock lock];\n    [_chunkSet removeObject:chunk];\n    if ([_chunkSet countForObject:chunk] == 0) {\n        [self _setChunk:nil index:[chunk index]];\n    }\n    [_lock unlock];\n}\n\n- (NSInteger)_nextEntryIndex {\n    NSMutableIndexSet *unoccupiedIndexes = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, _entryCount)];\n    [unoccupiedIndexes removeIndexes:_occupiedIndexes];\n    \n    NSInteger index = [unoccupiedIndexes firstIndex];\n    if (index == NSNotFound) {\n        index = _entryCount;\n    }\n    \n    if (index >= [self _maximumCount] && [_MRUEntries count]) {\n        // Evict the oldest/least-recently accessed entry here\n\n        NSString *oldestEvictableEntityUUID = [self oldestEvictableEntityUUID];\n        if (oldestEvictableEntityUUID) {\n            [self deleteEntryForEntityUUID:oldestEvictableEntityUUID];\n            index = [self _nextEntryIndex];\n        }\n    }\n\n    if (index >= [self _maximumCount]) {\n        NSString *message = [NSString stringWithFormat:@\"FICImageTable - unable to evict entry from table '%@' to make room. New index %ld, desired max %ld\", [_imageFormat name], (long)index, (long)[self _maximumCount]];\n        [self.imageCache _logMessage:message];\n    }\n    \n    return index;\n}\n\n- (NSString *)oldestEvictableEntityUUID {\n    NSString *uuid = nil;\n    for (NSInteger i = [_MRUEntries count] - 1; i >= 0; i--) {\n        NSString *candidateUUID = [_MRUEntries objectAtIndex:i];\n        if (![_inUseEntries containsObject:candidateUUID]) {\n            uuid = candidateUUID;\n            break;\n        }\n    }\n\n    return uuid;\n}\n\n- (NSInteger)_indexOfEntryForEntityUUID:(NSString *)entityUUID {\n    NSInteger index = NSNotFound;\n    if (_indexMap != nil && entityUUID != nil) {\n        NSNumber *indexNumber = [_indexMap objectForKey:entityUUID];\n        index = indexNumber ? [indexNumber integerValue] : NSNotFound;\n        \n        if (index != NSNotFound && index >= _entryCount) {\n            [_indexMap removeObjectForKey:entityUUID];\n            [_occupiedIndexes removeIndex:index];\n            [_sourceImageMap removeObjectForKey:entityUUID];\n            index = NSNotFound;\n        }\n    }\n    \n    return index;\n}\n\n- (FICImageTableEntry *)_entryDataForEntityUUID:(NSString *)entityUUID {\n    FICImageTableEntry *entryData = nil;\n    NSInteger index = [self _indexOfEntryForEntityUUID:entityUUID];\n    if (index != NSNotFound) {\n        entryData = [self _entryDataAtIndex:index];\n    }\n    \n    return entryData;\n}\n\n- (void)_entryWasAccessedWithEntityUUID:(NSString *)entityUUID {\n    // Update MRU array\n    NSInteger index = [_MRUEntries indexOfObject:entityUUID];\n    if (index == NSNotFound) {\n        [_MRUEntries insertObject:entityUUID atIndex:0];\n    } else if (index != 0) {\n        [_MRUEntries removeObjectAtIndex:index];\n        [_MRUEntries insertObject:entityUUID atIndex:0];\n    }\n}\n\n// Unchanging pointer value for a given entry index to synchronize on\n- (NSNumber *)_numberForEntryAtIndex:(NSInteger)index {\n    NSNumber *resultNumber = (__bridge id)CFDictionaryGetValue(_indexNumbers, (const void *)index);\n    if (!resultNumber) {\n        resultNumber = [NSNumber numberWithInteger:index];\n        CFDictionarySetValue(_indexNumbers, (const void *)index, (__bridge void *)resultNumber);\n    }\n    return resultNumber;\n}\n\n#pragma mark - Working with Metadata\n\n- (void)saveMetadata {\n    @autoreleasepool {\n        [_lock lock];\n        \n        NSDictionary *metadataDictionary = [NSDictionary dictionaryWithObjectsAndKeys:\n                                            [_indexMap copy], FICImageTableIndexMapKey,\n                                            [_sourceImageMap copy], FICImageTableContextMapKey,\n                                            [[_MRUEntries array] copy], FICImageTableMRUArrayKey,\n                                            [_imageFormatDictionary copy], FICImageTableFormatKey, nil];\n\n        __block int32_t metadataVersion = OSAtomicIncrement32(&_metadataVersion);\n\n        [_lock unlock];\n        \n        static dispatch_queue_t __metadataQueue = nil;\n        static dispatch_once_t onceToken;\n        dispatch_once(&onceToken, ^{\n            __metadataQueue = dispatch_queue_create(\"com.path.FastImageCache.ImageTableMetadataQueue\", NULL);\n        });\n        \n        dispatch_async(__metadataQueue, ^{\n            // Cancel serialization if a new metadata version is queued to be saved\n            if (metadataVersion != _metadataVersion) {\n                return;\n            }\n\n            @autoreleasepool {\n                NSData *data = [NSJSONSerialization dataWithJSONObject:metadataDictionary options:kNilOptions error:NULL];\n\n                // Cancel disk writing if a new metadata version is queued to be saved\n                if (metadataVersion != _metadataVersion) {\n                    return;\n                }\n\n                BOOL fileWriteResult = [data writeToFile:[self metadataFilePath] atomically:NO];\n                if (fileWriteResult == NO) {\n                    NSString *message = [NSString stringWithFormat:@\"*** FIC Error: %s couldn't write metadata for format %@\", __PRETTY_FUNCTION__, [_imageFormat name]];\n                    [self.imageCache _logMessage:message];\n                }\n            }\n        });\n    }\n}\n\n- (void)_loadMetadata {\n    NSString *metadataFilePath = [self metadataFilePath];\n    NSData *metadataData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:metadataFilePath] options:NSDataReadingMappedAlways error:NULL];\n    if (metadataData != nil) {\n        NSDictionary *metadataDictionary = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:metadataData options:kNilOptions error:NULL];\n        \n        if (!metadataDictionary) {\n            // The image table was likely previously stored as a .plist\n            // We'll read it into memory as a .plist and later store it (during -saveMetadata) using NSJSONSerialization for performance reasons\n            metadataDictionary = (NSDictionary *)[NSPropertyListSerialization propertyListWithData:metadataData options:0 format:NULL error:NULL];\n        }\n        \n        NSDictionary *formatDictionary = [metadataDictionary objectForKey:FICImageTableFormatKey];\n        if ([formatDictionary isEqualToDictionary:_imageFormatDictionary] == NO) {\n            // Something about this image format has changed, so the existing metadata is no longer valid. The image table file\n            // must be deleted and recreated.\n            [[NSFileManager defaultManager] removeItemAtPath:_filePath error:NULL];\n            [[NSFileManager defaultManager] removeItemAtPath:metadataFilePath error:NULL];\n            metadataDictionary = nil;\n            \n            NSString *message = [NSString stringWithFormat:@\"*** FIC Notice: Image format %@ has changed; deleting data and starting over.\", [_imageFormat name]];\n            [self.imageCache _logMessage:message];\n        }\n        \n        [_indexMap setDictionary:[metadataDictionary objectForKey:FICImageTableIndexMapKey]];\n        \n        for (NSNumber *index in [_indexMap allValues]) {\n            [_occupiedIndexes addIndex:[index intValue]];\n        }\n        \n        [_sourceImageMap setDictionary:[metadataDictionary objectForKey:FICImageTableContextMapKey]];\n        \n        [_MRUEntries removeAllObjects];\n        \n        NSArray *mruArray = [metadataDictionary objectForKey:FICImageTableMRUArrayKey];\n        if (mruArray) {\n            [_MRUEntries addObjectsFromArray:mruArray];\n        }\n    }\n}\n\n#pragma mark - Resetting the Image Table\n\n- (void)reset {\n    [_lock lock];\n    \n    [_indexMap removeAllObjects];\n    [_occupiedIndexes removeAllIndexes];\n    [_inUseEntries removeAllObjects];\n    [_MRUEntries removeAllObjects];\n    [_sourceImageMap removeAllObjects];\n    [_chunkDictionary removeAllObjects];\n    [_chunkSet removeAllObjects];\n    \n    [self _setEntryCount:0];\n    [self saveMetadata];\n    \n    [_lock unlock];\n}\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache/FICImageTableChunk.h",
    "content": "//\n//  FICImageTableChunk.h\n//  FastImageCache\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICImports.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@class FICImageTable;\n\n/**\n `FICImageTableChunk` represents a contiguous portion of image table file data.\n */\n@interface FICImageTableChunk : NSObject\n\n///-----------------------------------\n/// @name Image Table Chunk Properties\n///-----------------------------------\n\n/**\n The bytes of file data contained in the chunk.\n \n @discussion `FICImageTableChunk` maps file data directly to `bytes`, so no memory copy occurs.\n */\n@property (nonatomic, assign, readonly) void *bytes;\n\n/**\n The index of the chunk in the image table file.\n */\n@property (nonatomic, assign, readonly) NSInteger index;\n\n/**\n The offset in the image table file where the chunk begins.\n */\n@property (nonatomic, assign, readonly) off_t fileOffset;\n\n/**\n The length, in bytes, of the chunk.\n */\n@property (nonatomic, assign, readonly) size_t length;\n\n\n///----------------------------------------\n/// @name Initializing an Image Table Chunk\n///----------------------------------------\n\n/**\n Initializes a new image table chunk.\n\n @param fileDescriptor The image table's file descriptor to map from.\n \n @param index The index of the chunk.\n \n @param length The length, in bytes, of the chunk.\n \n @return A new image table chunk.\n */\n- (nullable instancetype)initWithFileDescriptor:(int)fileDescriptor index:(NSInteger)index length:(size_t)length;\n\n@end\n\nNS_ASSUME_NONNULL_END"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache/FICImageTableChunk.m",
    "content": "//\n//  FICImageTableChunk.m\n//  FastImageCache\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICImageTableChunk.h\"\n\n#import <sys/mman.h>\n\n#pragma mark - Class Extension\n\n@interface FICImageTableChunk () {\n    NSInteger _index;\n    void *_bytes;\n    size_t _length;\n    off_t _fileOffset;\n}\n\n@end\n\n#pragma mark\n\n@implementation FICImageTableChunk\n\n@synthesize bytes = _bytes;\n@synthesize fileOffset = _fileOffset;\n@synthesize length = _length;\n\n#pragma mark - Object Lifecycle\n\n- (instancetype)initWithFileDescriptor:(int)fileDescriptor index:(NSInteger)index length:(size_t)length {\n    self = [super init];\n    \n    if (self != nil) {\n        _index = index;\n        _length = length;\n        _fileOffset = _index * _length;\n        _bytes = mmap(NULL, _length, (PROT_READ|PROT_WRITE), (MAP_FILE|MAP_SHARED), fileDescriptor, _fileOffset);\n\n        if (_bytes == MAP_FAILED) {\n            NSLog(@\"Failed to map chunk. errno=%d\", errno);\n            _bytes = NULL;\n            self = nil;\n        }\n    }\n    \n    return self;\n}\n\n- (void)dealloc {\n    if (_bytes != NULL) {\n        munmap(_bytes, _length);\n    }\n}\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache/FICImageTableEntry.h",
    "content": "//\n//  FICImageTableEntry.h\n//  FastImageCache\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICImports.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n@class FICImageTableChunk;\n@class FICImageCache;\n\ntypedef struct {\n    CFUUIDBytes _entityUUIDBytes;\n    CFUUIDBytes _sourceImageUUIDBytes;\n} FICImageTableEntryMetadata;\n\n/**\n `FICImageTableEntry` represents an entry in an image table. It contains the necessary data and metadata to store a single entry of image data. Entries are created from instances of\n `<FICImageTableChunk>`.\n */\n@interface FICImageTableEntry : NSObject\n\n///---------------------------------------------\n/// @name Accessing Image Table Entry Properties\n///---------------------------------------------\n\n/**\n The length, in bytes, of the entry data.\n \n @discussion Entries begin with the image data, followed by the metadata struct.\n */\n@property (nonatomic, assign, readonly) size_t length;\n\n/**\n The length, in bytes, of just the image data.\n */\n@property (nonatomic, assign, readonly) size_t imageLength;\n\n/**\n The bytes that represent the entry data.\n */\n@property (nonatomic, assign, readonly) void *bytes;\n\n/**\n The entity UUID, in byte form, associated with the entry.\n */\n@property (nonatomic, assign) CFUUIDBytes entityUUIDBytes;\n\n/**\n The source image UUID, in byte form, associated with the entry.\n */\n@property (nonatomic, assign) CFUUIDBytes sourceImageUUIDBytes;\n\n/**\n The image table chunk that contains this entry.\n */\n@property (nonatomic, readonly) FICImageTableChunk *imageTableChunk;\n\n/**\n A weak reference to the image cache that contains the image table chunk that contains this entry.\n */\n@property (nonatomic, weak) FICImageCache *imageCache;\n\n/**\n The index where this entry exists in the image table.\n */\n@property (nonatomic, assign) NSInteger index;\n\n///----------------------------------\n/// @name Image Table Entry Lifecycle\n///----------------------------------\n\n/**\n Initializes a new image table entry from an image table chunk.\n \n @param imageTableChunk The image table chunk that contains the entry data.\n \n @param bytes The bytes from the chunk that contain the entry data.\n \n @param length The length, in bytes, of the entry data.\n \n @return A new image table entry.\n */\n- (nullable instancetype)initWithImageTableChunk:(FICImageTableChunk *)imageTableChunk bytes:(void *)bytes length:(size_t)length;\n\n/**\n Adds a block to be executed when this image table entry is deallocated.\n \n @param block A block that will be called when this image table entry is deallocated.\n \n @note Because of the highly-concurrent nature of Fast Image Cache, image tables must know when any of their entries are about to be deallocated to disassociate them with its internal data structures.\n */\n- (void)executeBlockOnDealloc:(dispatch_block_t)block;\n\n/**\n Forces the kernel to page in the memory-mapped, on-disk data backing this entry right away.\n */\n- (void)preheat;\n\n///--------------------------------------------\n/// @name Flushing a Modified Image Table Entry\n///--------------------------------------------\n\n/**\n Writes a modified image table entry back to disk.\n */\n- (void)flush;\n\n///--------------------------------------------\n/// @name Versioning Image Table Entry Metadata\n///--------------------------------------------\n\n/**\n Returns the current metadata version for image table entries.\n \n @return The integer version number of the current metadata version.\n \n @discussion Whenever the `<FICImageTableEntryMetadata>` struct is changed in any way, the metadata version must be changed.\n */\n+ (NSInteger)metadataVersion;\n\n@end\n\nNS_ASSUME_NONNULL_END"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache/FICImageTableEntry.m",
    "content": "//\n//  FICImageTableEntry.m\n//  FastImageCache\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICImageTableEntry.h\"\n#import \"FICImageTable.h\"\n#import \"FICImageTableChunk.h\"\n#import \"FICImageCache.h\"\n\n#import \"FICImageCache+FICErrorLogging.h\"\n\n#import <sys/mman.h>\n\n#pragma mark Class Extension\n\n@interface FICImageTableEntry () {\n    FICImageTableChunk *_imageTableChunk;\n    void *_bytes;\n    size_t _length;\n    NSMutableArray *_deallocBlocks;\n    NSInteger _index;\n}\n\n@end\n\n#pragma mark\n\n@implementation FICImageTableEntry\n\n@synthesize bytes = _bytes;\n@synthesize length = _length;\n@synthesize imageTableChunk = _imageTableChunk;\n@synthesize index = _index;\n@synthesize imageCache;\n\n#pragma mark - Property Accessors\n\n- (size_t)imageLength {\n    return _length - sizeof(FICImageTableEntryMetadata);\n}\n\n- (CFUUIDBytes)entityUUIDBytes {\n    return [self _metadata]->_entityUUIDBytes;\n}\n\n- (void)setEntityUUIDBytes:(CFUUIDBytes)entityUUIDBytes {\n    [self _metadata]->_entityUUIDBytes = entityUUIDBytes;\n}\n\n- (CFUUIDBytes)sourceImageUUIDBytes {\n    return [self _metadata]->_sourceImageUUIDBytes;\n}\n\n- (void)setSourceImageUUIDBytes:(CFUUIDBytes)sourceImageUUIDBytes {\n    [self _metadata]->_sourceImageUUIDBytes = sourceImageUUIDBytes;\n}\n\n#pragma mark - Object Lifecycle\n\n- (id)initWithImageTableChunk:(FICImageTableChunk *)imageTableChunk bytes:(void *)bytes length:(size_t)length {\n    self = [super init];\n    \n    if (self != nil) {\n        // Safety check\n        void *entryMax = bytes + length;\n        void *chunkMax = [imageTableChunk bytes] + [imageTableChunk length];\n        if (entryMax > chunkMax) {\n            self = nil;\n        } else {\n            _imageTableChunk = imageTableChunk;\n            _bytes = bytes;\n            _length = length;\n            _deallocBlocks = [[NSMutableArray alloc] init];\n        }\n    }\n    \n    return self;\n}\n\n- (void)executeBlockOnDealloc:(dispatch_block_t)block {\n    [_deallocBlocks addObject:[block copy]];\n}\n\n- (void)dealloc {\n    for (dispatch_block_t block in _deallocBlocks) {\n        dispatch_async([FICImageCache dispatchQueue], block);\n    }\n}\n\n#pragma mark - Other Accessors\n\n+ (NSInteger)metadataVersion {\n    return 8;\n}\n\n- (FICImageTableEntryMetadata *)_metadata {\n    return (FICImageTableEntryMetadata *)(_bytes + [self imageLength]);\n}\n\n#pragma mark - Flushing a Modified Image Table Entry\n\n- (void)flush {\n    int pageSize = [FICImageTable pageSize];\n    void *address = _bytes;\n    size_t pageIndex = (size_t)address / pageSize;\n    void *pageAlignedAddress = (void *)(pageIndex * pageSize);\n    size_t bytesBeforeData = address - pageAlignedAddress;\n    size_t bytesToFlush = (bytesBeforeData + _length);\n    int result = msync(pageAlignedAddress, bytesToFlush, MS_SYNC);\n    \n    if (result) {\n        NSString *message = [NSString stringWithFormat:@\"*** FIC Error: %s msync(%p, %ld) returned %d errno=%d\", __PRETTY_FUNCTION__, pageAlignedAddress, bytesToFlush, result, errno];\n        [self.imageCache _logMessage:message];\n    }\n}\n\n- (void)preheat {\n    int pageSize = [FICImageTable pageSize];\n    void *bytes = [self bytes];\n    NSUInteger length = [self length];\n    \n    // Read a byte off of each VM page to force the kernel to page in the data\n    for (NSUInteger i = 0; i < length; i += pageSize) {\n        *((volatile uint8_t *)bytes + i);\n    }\n}\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache/FICImports.h",
    "content": "//\n//  FICImports.h\n//  FastImageCache\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import <Foundation/Foundation.h>\n#import <CoreGraphics/CoreGraphics.h>\n#import <UIKit/UIKit.h>\n"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache/FICUtilities.h",
    "content": "//\n//  FICUtilities.h\n//  FastImageCache\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICImports.h\"\n\nsize_t FICByteAlign(size_t bytesPerRow, size_t alignment);\nsize_t FICByteAlignForCoreAnimation(size_t bytesPerRow);\n\nNSString * _Nullable FICStringWithUUIDBytes(CFUUIDBytes UUIDBytes);\nCFUUIDBytes FICUUIDBytesWithString(NSString * _Nonnull string);\nCFUUIDBytes FICUUIDBytesFromMD5HashOfString(NSString * _Nonnull MD5Hash); // Useful for computing an entity's UUID from a URL, for example\n\n"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache/FICUtilities.m",
    "content": "//\n//  FICUtilities.m\n//  FastImageCache\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICUtilities.h\"\n\n#import <CommonCrypto/CommonDigest.h>\n\n#pragma mark Internal Definitions\n\n// Core Animation will make a copy of any image that a client application provides whose backing store isn't properly byte-aligned.\n// This copy operation can be prohibitively expensive, so we want to avoid this by properly aligning any UIImages we're working with.\n// To produce a UIImage that is properly aligned, we need to ensure that the backing store's bytes per row is a multiple of 64.\n\n#pragma mark - Byte Alignment\n\ninline size_t FICByteAlign(size_t width, size_t alignment) {\n    return ((width + (alignment - 1)) / alignment) * alignment;\n}\n\ninline size_t FICByteAlignForCoreAnimation(size_t bytesPerRow) {\n    return FICByteAlign(bytesPerRow, 64);\n}\n\n#pragma mark - Strings and UUIDs\n\nNSString * FICStringWithUUIDBytes(CFUUIDBytes UUIDBytes) {\n    NSString *UUIDString = nil;\n    CFUUIDRef UUIDRef = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, UUIDBytes);\n    \n    if (UUIDRef != NULL) {\n        UUIDString = (__bridge_transfer NSString *)CFUUIDCreateString(kCFAllocatorDefault, UUIDRef);\n        CFRelease(UUIDRef);\n    }\n    \n    return UUIDString;\n}\n\nCFUUIDBytes FICUUIDBytesWithString(NSString *string) {\n    CFUUIDBytes UUIDBytes;\n    CFUUIDRef UUIDRef = CFUUIDCreateFromString(kCFAllocatorDefault, (CFStringRef)string);\n    \n    if (UUIDRef != NULL) {\n        UUIDBytes = CFUUIDGetUUIDBytes(UUIDRef);\n        CFRelease(UUIDRef);\n    }\n    \n    return UUIDBytes;\n}\n\nCFUUIDBytes FICUUIDBytesFromMD5HashOfString(NSString *MD5Hash) {\n    const char *UTF8String = [MD5Hash UTF8String];\n    CFUUIDBytes UUIDBytes;\n    \n    CC_MD5(UTF8String, (CC_LONG)strlen(UTF8String), (unsigned char*)&UUIDBytes);\n    \n    return UUIDBytes;\n}\n"
  },
  {
    "path": "FastImageCache/FastImageCache/FastImageCache.h",
    "content": "//\n//  FastImageCache.h\n//  FastImageCache\n//\n//  Created by Rui Peres on 17/06/2015.\n//  Copyright (c) 2015 Path. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n\n//! Project version number for FastImageCache.\nFOUNDATION_EXPORT double FastImageCacheVersionNumber;\n\n//! Project version string for FastImageCache.\nFOUNDATION_EXPORT const unsigned char FastImageCacheVersionString[];\n\n#import <FastImageCache/FICImageCache.h>\n#import <FastImageCache/FICEntity.h>\n#import <FastImageCache/FICUtilities.h>"
  },
  {
    "path": "FastImageCache/FastImageCache/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>FMWK</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n\t<key>NSPrincipalClass</key>\n\t<string></string>\n</dict>\n</plist>\n"
  },
  {
    "path": "FastImageCache/FastImageCache.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\tB2E5676E1B316D5800906840 /* FastImageCache.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E5676D1B316D5800906840 /* FastImageCache.h */; settings = {ATTRIBUTES = (Public, ); }; };\n\t\tB2E567741B316D5800906840 /* FastImageCache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2E567681B316D5800906840 /* FastImageCache.framework */; };\n\t\tB2E5677B1B316D5800906840 /* FastImageCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5677A1B316D5800906840 /* FastImageCacheTests.m */; };\n\t\tB2E567941B316D9600906840 /* FICEntity.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E567851B316D9600906840 /* FICEntity.h */; settings = {ATTRIBUTES = (Public, ); }; };\n\t\tB2E567951B316D9600906840 /* FICImageCache+FICErrorLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E567861B316D9600906840 /* FICImageCache+FICErrorLogging.h */; };\n\t\tB2E567961B316D9600906840 /* FICImageCache.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E567871B316D9600906840 /* FICImageCache.h */; settings = {ATTRIBUTES = (Public, ); }; };\n\t\tB2E567971B316D9600906840 /* FICImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567881B316D9600906840 /* FICImageCache.m */; };\n\t\tB2E567981B316D9600906840 /* FICImageFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E567891B316D9600906840 /* FICImageFormat.h */; settings = {ATTRIBUTES = (Public, ); }; };\n\t\tB2E567991B316D9600906840 /* FICImageFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5678A1B316D9600906840 /* FICImageFormat.m */; };\n\t\tB2E5679A1B316D9600906840 /* FICImageTable.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E5678B1B316D9600906840 /* FICImageTable.h */; };\n\t\tB2E5679B1B316D9600906840 /* FICImageTable.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5678C1B316D9600906840 /* FICImageTable.m */; };\n\t\tB2E5679C1B316D9600906840 /* FICImageTableChunk.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E5678D1B316D9600906840 /* FICImageTableChunk.h */; };\n\t\tB2E5679D1B316D9600906840 /* FICImageTableChunk.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5678E1B316D9600906840 /* FICImageTableChunk.m */; };\n\t\tB2E5679E1B316D9600906840 /* FICImageTableEntry.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E5678F1B316D9600906840 /* FICImageTableEntry.h */; };\n\t\tB2E5679F1B316D9600906840 /* FICImageTableEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567901B316D9600906840 /* FICImageTableEntry.m */; };\n\t\tB2E567A01B316D9600906840 /* FICImports.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E567911B316D9600906840 /* FICImports.h */; settings = {ATTRIBUTES = (Public, ); }; };\n\t\tB2E567A11B316D9600906840 /* FICUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E567921B316D9600906840 /* FICUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; };\n\t\tB2E567A21B316D9600906840 /* FICUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567931B316D9600906840 /* FICUtilities.m */; };\n\t\tB2E567AC1B316DCA00906840 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567AB1B316DCA00906840 /* main.m */; };\n\t\tB2E567DA1B316E1000906840 /* FICDAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567CF1B316E1000906840 /* FICDAppDelegate.m */; };\n\t\tB2E567DB1B316E1000906840 /* FICDFullscreenPhotoDisplayController.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567D11B316E1000906840 /* FICDFullscreenPhotoDisplayController.m */; };\n\t\tB2E567DC1B316E1000906840 /* FICDPhoto.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567D31B316E1000906840 /* FICDPhoto.m */; };\n\t\tB2E567DD1B316E1000906840 /* FICDPhotosTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567D51B316E1000906840 /* FICDPhotosTableViewCell.m */; };\n\t\tB2E567DE1B316E1000906840 /* FICDTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567D71B316E1000906840 /* FICDTableView.m */; };\n\t\tB2E567DF1B316E1000906840 /* FICDViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567D91B316E1000906840 /* FICDViewController.m */; };\n\t\tB2E567E41B316E2200906840 /* fetch_demo_images.sh in Resources */ = {isa = PBXBuildFile; fileRef = B2E567E31B316E2200906840 /* fetch_demo_images.sh */; };\n\t\tB2E567E61B316E3700906840 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B2E567E51B316E3700906840 /* Assets.xcassets */; };\n\t\tB2E567E71B316E5F00906840 /* FICUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567931B316D9600906840 /* FICUtilities.m */; };\n\t\tB2E567E81B316E6600906840 /* FICImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567881B316D9600906840 /* FICImageCache.m */; };\n\t\tB2E567E91B316E6600906840 /* FICImageFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5678A1B316D9600906840 /* FICImageFormat.m */; };\n\t\tB2E567EA1B316E6600906840 /* FICImageTable.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5678C1B316D9600906840 /* FICImageTable.m */; };\n\t\tB2E567EB1B316E6600906840 /* FICImageTableChunk.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5678E1B316D9600906840 /* FICImageTableChunk.m */; };\n\t\tB2E567EC1B316E6600906840 /* FICImageTableEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567901B316D9600906840 /* FICImageTableEntry.m */; };\n\t\tBFD6BFFB1B68FD5D005292DC /* Demo Images in Resources */ = {isa = PBXBuildFile; fileRef = BFD6BFFA1B68FD5D005292DC /* Demo Images */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\tB2E567751B316D5800906840 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = B2E5675F1B316D5800906840 /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = B2E567671B316D5800906840;\n\t\t\tremoteInfo = FastImageCache;\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXFileReference section */\n\t\tB2E567681B316D5800906840 /* FastImageCache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FastImageCache.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tB2E5676C1B316D5800906840 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\tB2E5676D1B316D5800906840 /* FastImageCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FastImageCache.h; sourceTree = \"<group>\"; };\n\t\tB2E567731B316D5800906840 /* FastImageCacheTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FastImageCacheTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tB2E567791B316D5800906840 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\tB2E5677A1B316D5800906840 /* FastImageCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FastImageCacheTests.m; sourceTree = \"<group>\"; };\n\t\tB2E567851B316D9600906840 /* FICEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICEntity.h; sourceTree = \"<group>\"; };\n\t\tB2E567861B316D9600906840 /* FICImageCache+FICErrorLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = \"FICImageCache+FICErrorLogging.h\"; sourceTree = \"<group>\"; };\n\t\tB2E567871B316D9600906840 /* FICImageCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICImageCache.h; sourceTree = \"<group>\"; };\n\t\tB2E567881B316D9600906840 /* FICImageCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICImageCache.m; sourceTree = \"<group>\"; };\n\t\tB2E567891B316D9600906840 /* FICImageFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICImageFormat.h; sourceTree = \"<group>\"; };\n\t\tB2E5678A1B316D9600906840 /* FICImageFormat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICImageFormat.m; sourceTree = \"<group>\"; };\n\t\tB2E5678B1B316D9600906840 /* FICImageTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICImageTable.h; sourceTree = \"<group>\"; };\n\t\tB2E5678C1B316D9600906840 /* FICImageTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICImageTable.m; sourceTree = \"<group>\"; };\n\t\tB2E5678D1B316D9600906840 /* FICImageTableChunk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICImageTableChunk.h; sourceTree = \"<group>\"; };\n\t\tB2E5678E1B316D9600906840 /* FICImageTableChunk.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICImageTableChunk.m; sourceTree = \"<group>\"; };\n\t\tB2E5678F1B316D9600906840 /* FICImageTableEntry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICImageTableEntry.h; sourceTree = \"<group>\"; };\n\t\tB2E567901B316D9600906840 /* FICImageTableEntry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICImageTableEntry.m; sourceTree = \"<group>\"; };\n\t\tB2E567911B316D9600906840 /* FICImports.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICImports.h; sourceTree = \"<group>\"; };\n\t\tB2E567921B316D9600906840 /* FICUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICUtilities.h; sourceTree = \"<group>\"; };\n\t\tB2E567931B316D9600906840 /* FICUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICUtilities.m; sourceTree = \"<group>\"; };\n\t\tB2E567A71B316DCA00906840 /* FastImageCacheDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FastImageCacheDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tB2E567AA1B316DCA00906840 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\tB2E567AB1B316DCA00906840 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = \"<group>\"; };\n\t\tB2E567CE1B316E1000906840 /* FICDAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICDAppDelegate.h; sourceTree = \"<group>\"; };\n\t\tB2E567CF1B316E1000906840 /* FICDAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICDAppDelegate.m; sourceTree = \"<group>\"; };\n\t\tB2E567D01B316E1000906840 /* FICDFullscreenPhotoDisplayController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICDFullscreenPhotoDisplayController.h; sourceTree = \"<group>\"; };\n\t\tB2E567D11B316E1000906840 /* FICDFullscreenPhotoDisplayController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICDFullscreenPhotoDisplayController.m; sourceTree = \"<group>\"; };\n\t\tB2E567D21B316E1000906840 /* FICDPhoto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICDPhoto.h; sourceTree = \"<group>\"; };\n\t\tB2E567D31B316E1000906840 /* FICDPhoto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICDPhoto.m; sourceTree = \"<group>\"; };\n\t\tB2E567D41B316E1000906840 /* FICDPhotosTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICDPhotosTableViewCell.h; sourceTree = \"<group>\"; };\n\t\tB2E567D51B316E1000906840 /* FICDPhotosTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICDPhotosTableViewCell.m; sourceTree = \"<group>\"; };\n\t\tB2E567D61B316E1000906840 /* FICDTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICDTableView.h; sourceTree = \"<group>\"; };\n\t\tB2E567D71B316E1000906840 /* FICDTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICDTableView.m; sourceTree = \"<group>\"; };\n\t\tB2E567D81B316E1000906840 /* FICDViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICDViewController.h; sourceTree = \"<group>\"; };\n\t\tB2E567D91B316E1000906840 /* FICDViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICDViewController.m; sourceTree = \"<group>\"; };\n\t\tB2E567E31B316E2200906840 /* fetch_demo_images.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = fetch_demo_images.sh; sourceTree = \"<group>\"; };\n\t\tB2E567E51B316E3700906840 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\tB2E567ED1B316EBF00906840 /* FastImageCacheDemo-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = \"FastImageCacheDemo-Prefix.pch\"; sourceTree = \"<group>\"; };\n\t\tBFD6BFFA1B68FD5D005292DC /* Demo Images */ = {isa = PBXFileReference; lastKnownFileType = folder; path = \"Demo Images\"; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\tB2E567641B316D5800906840 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB2E567701B316D5800906840 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB2E567741B316D5800906840 /* FastImageCache.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB2E567A41B316DCA00906840 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\tB2E5675E1B316D5800906840 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB2E5676A1B316D5800906840 /* FastImageCache */,\n\t\t\t\tB2E567771B316D5800906840 /* FastImageCacheTests */,\n\t\t\t\tB2E567A81B316DCA00906840 /* FastImageCacheDemo */,\n\t\t\t\tB2E567691B316D5800906840 /* Products */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB2E567691B316D5800906840 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB2E567681B316D5800906840 /* FastImageCache.framework */,\n\t\t\t\tB2E567731B316D5800906840 /* FastImageCacheTests.xctest */,\n\t\t\t\tB2E567A71B316DCA00906840 /* FastImageCacheDemo.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB2E5676A1B316D5800906840 /* FastImageCache */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB2E567841B316D9600906840 /* FastImageCache */,\n\t\t\t\tB2E5676D1B316D5800906840 /* FastImageCache.h */,\n\t\t\t\tB2E5676B1B316D5800906840 /* Supporting Files */,\n\t\t\t);\n\t\t\tpath = FastImageCache;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB2E5676B1B316D5800906840 /* Supporting Files */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB2E5676C1B316D5800906840 /* Info.plist */,\n\t\t\t);\n\t\t\tname = \"Supporting Files\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB2E567771B316D5800906840 /* FastImageCacheTests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB2E5677A1B316D5800906840 /* FastImageCacheTests.m */,\n\t\t\t\tB2E567781B316D5800906840 /* Supporting Files */,\n\t\t\t);\n\t\t\tpath = FastImageCacheTests;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB2E567781B316D5800906840 /* Supporting Files */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB2E567791B316D5800906840 /* Info.plist */,\n\t\t\t);\n\t\t\tname = \"Supporting Files\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB2E567841B316D9600906840 /* FastImageCache */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB2E567851B316D9600906840 /* FICEntity.h */,\n\t\t\t\tB2E567861B316D9600906840 /* FICImageCache+FICErrorLogging.h */,\n\t\t\t\tB2E567871B316D9600906840 /* FICImageCache.h */,\n\t\t\t\tB2E567881B316D9600906840 /* FICImageCache.m */,\n\t\t\t\tB2E567891B316D9600906840 /* FICImageFormat.h */,\n\t\t\t\tB2E5678A1B316D9600906840 /* FICImageFormat.m */,\n\t\t\t\tB2E5678B1B316D9600906840 /* FICImageTable.h */,\n\t\t\t\tB2E5678C1B316D9600906840 /* FICImageTable.m */,\n\t\t\t\tB2E5678D1B316D9600906840 /* FICImageTableChunk.h */,\n\t\t\t\tB2E5678E1B316D9600906840 /* FICImageTableChunk.m */,\n\t\t\t\tB2E5678F1B316D9600906840 /* FICImageTableEntry.h */,\n\t\t\t\tB2E567901B316D9600906840 /* FICImageTableEntry.m */,\n\t\t\t\tB2E567911B316D9600906840 /* FICImports.h */,\n\t\t\t\tB2E567921B316D9600906840 /* FICUtilities.h */,\n\t\t\t\tB2E567931B316D9600906840 /* FICUtilities.m */,\n\t\t\t);\n\t\t\tpath = FastImageCache;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB2E567A81B316DCA00906840 /* FastImageCacheDemo */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB2E567E31B316E2200906840 /* fetch_demo_images.sh */,\n\t\t\t\tBFD6BFFA1B68FD5D005292DC /* Demo Images */,\n\t\t\t\tB2E567CD1B316E1000906840 /* Classes */,\n\t\t\t\tB2E567A91B316DCA00906840 /* Supporting Files */,\n\t\t\t);\n\t\t\tpath = FastImageCacheDemo;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB2E567A91B316DCA00906840 /* Supporting Files */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB2E567ED1B316EBF00906840 /* FastImageCacheDemo-Prefix.pch */,\n\t\t\t\tB2E567E51B316E3700906840 /* Assets.xcassets */,\n\t\t\t\tB2E567AA1B316DCA00906840 /* Info.plist */,\n\t\t\t\tB2E567AB1B316DCA00906840 /* main.m */,\n\t\t\t);\n\t\t\tname = \"Supporting Files\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB2E567CD1B316E1000906840 /* Classes */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB2E567CE1B316E1000906840 /* FICDAppDelegate.h */,\n\t\t\t\tB2E567CF1B316E1000906840 /* FICDAppDelegate.m */,\n\t\t\t\tB2E567D01B316E1000906840 /* FICDFullscreenPhotoDisplayController.h */,\n\t\t\t\tB2E567D11B316E1000906840 /* FICDFullscreenPhotoDisplayController.m */,\n\t\t\t\tB2E567D21B316E1000906840 /* FICDPhoto.h */,\n\t\t\t\tB2E567D31B316E1000906840 /* FICDPhoto.m */,\n\t\t\t\tB2E567D41B316E1000906840 /* FICDPhotosTableViewCell.h */,\n\t\t\t\tB2E567D51B316E1000906840 /* FICDPhotosTableViewCell.m */,\n\t\t\t\tB2E567D61B316E1000906840 /* FICDTableView.h */,\n\t\t\t\tB2E567D71B316E1000906840 /* FICDTableView.m */,\n\t\t\t\tB2E567D81B316E1000906840 /* FICDViewController.h */,\n\t\t\t\tB2E567D91B316E1000906840 /* FICDViewController.m */,\n\t\t\t);\n\t\t\tpath = Classes;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXHeadersBuildPhase section */\n\t\tB2E567651B316D5800906840 /* Headers */ = {\n\t\t\tisa = PBXHeadersBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB2E5679A1B316D9600906840 /* FICImageTable.h in Headers */,\n\t\t\t\tB2E5679E1B316D9600906840 /* FICImageTableEntry.h in Headers */,\n\t\t\t\tB2E567941B316D9600906840 /* FICEntity.h in Headers */,\n\t\t\t\tB2E567A01B316D9600906840 /* FICImports.h in Headers */,\n\t\t\t\tB2E567981B316D9600906840 /* FICImageFormat.h in Headers */,\n\t\t\t\tB2E567A11B316D9600906840 /* FICUtilities.h in Headers */,\n\t\t\t\tB2E5679C1B316D9600906840 /* FICImageTableChunk.h in Headers */,\n\t\t\t\tB2E567951B316D9600906840 /* FICImageCache+FICErrorLogging.h in Headers */,\n\t\t\t\tB2E567961B316D9600906840 /* FICImageCache.h in Headers */,\n\t\t\t\tB2E5676E1B316D5800906840 /* FastImageCache.h in Headers */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXHeadersBuildPhase section */\n\n/* Begin PBXNativeTarget section */\n\t\tB2E567671B316D5800906840 /* FastImageCache */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = B2E5677E1B316D5800906840 /* Build configuration list for PBXNativeTarget \"FastImageCache\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tB2E567631B316D5800906840 /* Sources */,\n\t\t\t\tB2E567641B316D5800906840 /* Frameworks */,\n\t\t\t\tB2E567651B316D5800906840 /* Headers */,\n\t\t\t\tB2E567661B316D5800906840 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = FastImageCache;\n\t\t\tproductName = FastImageCache;\n\t\t\tproductReference = B2E567681B316D5800906840 /* FastImageCache.framework */;\n\t\t\tproductType = \"com.apple.product-type.framework\";\n\t\t};\n\t\tB2E567721B316D5800906840 /* FastImageCacheTests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = B2E567811B316D5800906840 /* Build configuration list for PBXNativeTarget \"FastImageCacheTests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tB2E5676F1B316D5800906840 /* Sources */,\n\t\t\t\tB2E567701B316D5800906840 /* Frameworks */,\n\t\t\t\tB2E567711B316D5800906840 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\tB2E567761B316D5800906840 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = FastImageCacheTests;\n\t\t\tproductName = FastImageCacheTests;\n\t\t\tproductReference = B2E567731B316D5800906840 /* FastImageCacheTests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n\t\tB2E567A61B316DCA00906840 /* FastImageCacheDemo */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = B2E567C71B316DCB00906840 /* Build configuration list for PBXNativeTarget \"FastImageCacheDemo\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tB2E567A31B316DCA00906840 /* Sources */,\n\t\t\t\tB2E567A41B316DCA00906840 /* Frameworks */,\n\t\t\t\tB2E567A51B316DCA00906840 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = FastImageCacheDemo;\n\t\t\tproductName = FastImageCacheDemo;\n\t\t\tproductReference = B2E567A71B316DCA00906840 /* FastImageCacheDemo.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\tB2E5675F1B316D5800906840 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastUpgradeCheck = 0720;\n\t\t\t\tORGANIZATIONNAME = Path;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\tB2E567671B316D5800906840 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 6.3.2;\n\t\t\t\t\t};\n\t\t\t\t\tB2E567721B316D5800906840 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 6.3.2;\n\t\t\t\t\t};\n\t\t\t\t\tB2E567A61B316DCA00906840 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 6.3.2;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = B2E567621B316D5800906840 /* Build configuration list for PBXProject \"FastImageCache\" */;\n\t\t\tcompatibilityVersion = \"Xcode 3.2\";\n\t\t\tdevelopmentRegion = English;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = B2E5675E1B316D5800906840;\n\t\t\tproductRefGroup = B2E567691B316D5800906840 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\tB2E567671B316D5800906840 /* FastImageCache */,\n\t\t\t\tB2E567721B316D5800906840 /* FastImageCacheTests */,\n\t\t\t\tB2E567A61B316DCA00906840 /* FastImageCacheDemo */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\tB2E567661B316D5800906840 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB2E567711B316D5800906840 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB2E567A51B316DCA00906840 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB2E567E41B316E2200906840 /* fetch_demo_images.sh in Resources */,\n\t\t\t\tBFD6BFFB1B68FD5D005292DC /* Demo Images in Resources */,\n\t\t\t\tB2E567E61B316E3700906840 /* Assets.xcassets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\tB2E567631B316D5800906840 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB2E5679B1B316D9600906840 /* FICImageTable.m in Sources */,\n\t\t\t\tB2E5679D1B316D9600906840 /* FICImageTableChunk.m in Sources */,\n\t\t\t\tB2E567971B316D9600906840 /* FICImageCache.m in Sources */,\n\t\t\t\tB2E567A21B316D9600906840 /* FICUtilities.m in Sources */,\n\t\t\t\tB2E5679F1B316D9600906840 /* FICImageTableEntry.m in Sources */,\n\t\t\t\tB2E567991B316D9600906840 /* FICImageFormat.m in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB2E5676F1B316D5800906840 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB2E5677B1B316D5800906840 /* FastImageCacheTests.m in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB2E567A31B316DCA00906840 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB2E567DA1B316E1000906840 /* FICDAppDelegate.m in Sources */,\n\t\t\t\tB2E567EB1B316E6600906840 /* FICImageTableChunk.m in Sources */,\n\t\t\t\tB2E567E91B316E6600906840 /* FICImageFormat.m in Sources */,\n\t\t\t\tB2E567AC1B316DCA00906840 /* main.m in Sources */,\n\t\t\t\tB2E567EA1B316E6600906840 /* FICImageTable.m in Sources */,\n\t\t\t\tB2E567DD1B316E1000906840 /* FICDPhotosTableViewCell.m in Sources */,\n\t\t\t\tB2E567E81B316E6600906840 /* FICImageCache.m in Sources */,\n\t\t\t\tB2E567EC1B316E6600906840 /* FICImageTableEntry.m in Sources */,\n\t\t\t\tB2E567DF1B316E1000906840 /* FICDViewController.m in Sources */,\n\t\t\t\tB2E567DC1B316E1000906840 /* FICDPhoto.m in Sources */,\n\t\t\t\tB2E567DB1B316E1000906840 /* FICDFullscreenPhotoDisplayController.m in Sources */,\n\t\t\t\tB2E567DE1B316E1000906840 /* FICDTableView.m in Sources */,\n\t\t\t\tB2E567E71B316E5F00906840 /* FICUtilities.m in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\tB2E567761B316D5800906840 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = B2E567671B316D5800906840 /* FastImageCache */;\n\t\t\ttargetProxy = B2E567751B316D5800906840 /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin XCBuildConfiguration section */\n\t\tB2E5677C1B316D5800906840 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_SYMBOLS_PRIVATE_EXTERN = NO;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.3;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t\tVERSION_INFO_PREFIX = \"\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tB2E5677D1B316D5800906840 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.3;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t\tVERSION_INFO_PREFIX = \"\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tB2E5677F1B316D5800906840 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tAPPLICATION_EXTENSION_API_ONLY = YES;\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tINFOPLIST_FILE = FastImageCache/Info.plist;\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks @loader_path/Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.path.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tB2E567801B316D5800906840 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tAPPLICATION_EXTENSION_API_ONLY = YES;\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tINFOPLIST_FILE = FastImageCache/Info.plist;\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks @loader_path/Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.path.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tB2E567821B316D5800906840 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(SDKROOT)/Developer/Library/Frameworks\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = FastImageCacheTests/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks @loader_path/Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.path.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tB2E567831B316D5800906840 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(SDKROOT)/Developer/Library/Frameworks\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = FastImageCacheTests/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks @loader_path/Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.path.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tB2E567C81B316DCB00906840 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = Icon;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = FastImageCacheDemo/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.path.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tB2E567C91B316DCB00906840 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = Icon;\n\t\t\t\tINFOPLIST_FILE = FastImageCacheDemo/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.path.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\tB2E567621B316D5800906840 /* Build configuration list for PBXProject \"FastImageCache\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB2E5677C1B316D5800906840 /* Debug */,\n\t\t\t\tB2E5677D1B316D5800906840 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tB2E5677E1B316D5800906840 /* Build configuration list for PBXNativeTarget \"FastImageCache\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB2E5677F1B316D5800906840 /* Debug */,\n\t\t\t\tB2E567801B316D5800906840 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tB2E567811B316D5800906840 /* Build configuration list for PBXNativeTarget \"FastImageCacheTests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB2E567821B316D5800906840 /* Debug */,\n\t\t\t\tB2E567831B316D5800906840 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tB2E567C71B316DCB00906840 /* Build configuration list for PBXNativeTarget \"FastImageCacheDemo\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB2E567C81B316DCB00906840 /* Debug */,\n\t\t\t\tB2E567C91B316DCB00906840 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = B2E5675F1B316D5800906840 /* Project object */;\n}\n"
  },
  {
    "path": "FastImageCache/FastImageCache.xcodeproj/xcshareddata/xcschemes/FastImageCache.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0720\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"B2E567671B316D5800906840\"\n               BuildableName = \"FastImageCache.framework\"\n               BlueprintName = \"FastImageCache\"\n               ReferencedContainer = \"container:FastImageCache.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"NO\"\n            buildForArchiving = \"NO\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"B2E567721B316D5800906840\"\n               BuildableName = \"FastImageCacheTests.xctest\"\n               BlueprintName = \"FastImageCacheTests\"\n               ReferencedContainer = \"container:FastImageCache.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"B2E567721B316D5800906840\"\n               BuildableName = \"FastImageCacheTests.xctest\"\n               BlueprintName = \"FastImageCacheTests\"\n               ReferencedContainer = \"container:FastImageCache.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"B2E567671B316D5800906840\"\n            BuildableName = \"FastImageCache.framework\"\n            BlueprintName = \"FastImageCache\"\n            ReferencedContainer = \"container:FastImageCache.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n      <AdditionalOptions>\n      </AdditionalOptions>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"B2E567671B316D5800906840\"\n            BuildableName = \"FastImageCache.framework\"\n            BlueprintName = \"FastImageCache\"\n            ReferencedContainer = \"container:FastImageCache.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n      <AdditionalOptions>\n      </AdditionalOptions>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"B2E567671B316D5800906840\"\n            BuildableName = \"FastImageCache.framework\"\n            BlueprintName = \"FastImageCache\"\n            ReferencedContainer = \"container:FastImageCache.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Assets.xcassets/Icon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"29x29\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"29x29\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"40x40\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"57x57\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"iPhone-App.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"57x57\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"iPhone-App@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"60x60\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"iPhone-App-iOS7@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"60x60\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"size\" : \"29x29\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"size\" : \"29x29\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"size\" : \"40x40\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"size\" : \"40x40\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"size\" : \"50x50\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"size\" : \"50x50\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"72x72\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"iPad-App.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"72x72\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"iPad-App@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"76x76\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"iPad-App-iOS7.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"76x76\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"iPad-App-iOS7@2x.png\",\n      \"scale\" : \"2x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  },\n  \"properties\" : {\n    \"pre-rendered\" : true\n  }\n}"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Assets.xcassets/Launch Image.launchimage/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"orientation\" : \"portrait\",\n      \"idiom\" : \"iphone\",\n      \"extent\" : \"full-screen\",\n      \"minimum-system-version\" : \"7.0\",\n      \"filename\" : \"iPhone-Portrait-iOS7@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"extent\" : \"full-screen\",\n      \"idiom\" : \"iphone\",\n      \"subtype\" : \"retina4\",\n      \"filename\" : \"iPhone-Portrait-R4@2x.png\",\n      \"minimum-system-version\" : \"7.0\",\n      \"orientation\" : \"portrait\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"orientation\" : \"portrait\",\n      \"idiom\" : \"ipad\",\n      \"extent\" : \"full-screen\",\n      \"minimum-system-version\" : \"7.0\",\n      \"filename\" : \"iPad-Portrait-iOS7.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"orientation\" : \"portrait\",\n      \"idiom\" : \"ipad\",\n      \"extent\" : \"full-screen\",\n      \"minimum-system-version\" : \"7.0\",\n      \"filename\" : \"iPad-Portrait-iOS7@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"orientation\" : \"portrait\",\n      \"idiom\" : \"iphone\",\n      \"extent\" : \"full-screen\",\n      \"filename\" : \"iPhone-Portrait.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"orientation\" : \"portrait\",\n      \"idiom\" : \"iphone\",\n      \"extent\" : \"full-screen\",\n      \"filename\" : \"iPhone-Portrait@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"orientation\" : \"portrait\",\n      \"idiom\" : \"iphone\",\n      \"extent\" : \"full-screen\",\n      \"filename\" : \"iPhone-Portrait-R4@2x-1.png\",\n      \"subtype\" : \"retina4\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"orientation\" : \"portrait\",\n      \"idiom\" : \"ipad\",\n      \"extent\" : \"to-status-bar\",\n      \"filename\" : \"iPad-Portrait-1004h.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"orientation\" : \"portrait\",\n      \"idiom\" : \"ipad\",\n      \"extent\" : \"to-status-bar\",\n      \"filename\" : \"iPad-Portrait-1004h@2x.png\",\n      \"scale\" : \"2x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Classes/FICDAppDelegate.h",
    "content": "//\n//  FICDAppDelegate.h\n//  FastImageCacheDemo\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import <UIKit/UIKit.h>\n\n@class FICDViewController;\n\n@interface FICDAppDelegate : UIResponder <UIApplicationDelegate>\n\n@property (nonatomic, strong) UIWindow *window;\n@property (nonatomic, strong) FICDViewController *viewController;\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Classes/FICDAppDelegate.m",
    "content": "//\n//  FICDAppDelegate.m\n//  FastImageCacheDemo\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICDAppDelegate.h\"\n#import \"FICImageCache.h\"\n#import \"FICDViewController.h\"\n#import \"FICDPhoto.h\"\n\n#pragma mark Class Extension\n\n@interface FICDAppDelegate () <FICImageCacheDelegate>\n\n@end\n\n#pragma mark\n\n@implementation FICDAppDelegate\n\n#pragma mark - Application Lifecycle\n\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {\n    NSMutableArray *mutableImageFormats = [NSMutableArray array];\n    \n    // Square image formats...\n    NSInteger squareImageFormatMaximumCount = 400;\n    FICImageFormatDevices squareImageFormatDevices = FICImageFormatDevicePhone | FICImageFormatDevicePad;\n    \n    // ...32-bit BGR\n    FICImageFormat *squareImageFormat32BitBGRA = [FICImageFormat formatWithName:FICDPhotoSquareImage32BitBGRAFormatName family:FICDPhotoImageFormatFamily imageSize:FICDPhotoSquareImageSize style:FICImageFormatStyle32BitBGRA\n        maximumCount:squareImageFormatMaximumCount devices:squareImageFormatDevices protectionMode:FICImageFormatProtectionModeNone];\n    \n    [mutableImageFormats addObject:squareImageFormat32BitBGRA];\n    \n    // ...32-bit BGR\n    FICImageFormat *squareImageFormat32BitBGR = [FICImageFormat formatWithName:FICDPhotoSquareImage32BitBGRFormatName family:FICDPhotoImageFormatFamily imageSize:FICDPhotoSquareImageSize style:FICImageFormatStyle32BitBGR\n        maximumCount:squareImageFormatMaximumCount devices:squareImageFormatDevices protectionMode:FICImageFormatProtectionModeNone];\n    \n    [mutableImageFormats addObject:squareImageFormat32BitBGR];\n    \n    // ...16-bit BGR\n    FICImageFormat *squareImageFormat16BitBGR = [FICImageFormat formatWithName:FICDPhotoSquareImage16BitBGRFormatName family:FICDPhotoImageFormatFamily imageSize:FICDPhotoSquareImageSize style:FICImageFormatStyle16BitBGR\n        maximumCount:squareImageFormatMaximumCount devices:squareImageFormatDevices protectionMode:FICImageFormatProtectionModeNone];\n    \n    [mutableImageFormats addObject:squareImageFormat16BitBGR];\n    \n    // ...8-bit Grayscale\n    FICImageFormat *squareImageFormat8BitGrayscale = [FICImageFormat formatWithName:FICDPhotoSquareImage8BitGrayscaleFormatName family:FICDPhotoImageFormatFamily imageSize:FICDPhotoSquareImageSize style:FICImageFormatStyle8BitGrayscale\n        maximumCount:squareImageFormatMaximumCount devices:squareImageFormatDevices protectionMode:FICImageFormatProtectionModeNone];\n    \n    [mutableImageFormats addObject:squareImageFormat8BitGrayscale];\n    \n    if ([UIViewController instancesRespondToSelector:@selector(preferredStatusBarStyle)]) {\n        // Pixel image format\n        NSInteger pixelImageFormatMaximumCount = 1000;\n        FICImageFormatDevices pixelImageFormatDevices = FICImageFormatDevicePhone | FICImageFormatDevicePad;\n        \n        FICImageFormat *pixelImageFormat = [FICImageFormat formatWithName:FICDPhotoPixelImageFormatName family:FICDPhotoImageFormatFamily imageSize:FICDPhotoPixelImageSize style:FICImageFormatStyle32BitBGR\n            maximumCount:pixelImageFormatMaximumCount devices:pixelImageFormatDevices protectionMode:FICImageFormatProtectionModeNone];\n    \n        [mutableImageFormats addObject:pixelImageFormat];\n    }\n    \n    // Configure the image cache\n    FICImageCache *sharedImageCache = [FICImageCache sharedImageCache];\n    [sharedImageCache setDelegate:self];\n    [sharedImageCache setFormats:mutableImageFormats];\n    \n    // Configure the window\n    CGRect windowFrame = [[UIScreen mainScreen] bounds];\n    UIWindow *window = [[UIWindow alloc] initWithFrame:windowFrame];\n    [self setWindow:window];\n    \n    UIViewController *rootViewController = [[FICDViewController alloc] init];\n    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];\n    \n    [[self window] setRootViewController:navigationController];\n    [[self window] makeKeyAndVisible];\n    \n    return YES;\n}\n\n#pragma mark - Protocol Implementations\n\n#pragma mark - FICImageCacheDelegate\n\n- (void)imageCache:(FICImageCache *)imageCache wantsSourceImageForEntity:(id<FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(FICImageRequestCompletionBlock)completionBlock {\n    // Images typically come from the Internet rather than from the app bundle directly, so this would be the place to fire off a network request to download the image.\n    // For the purposes of this demo app, we'll just access images stored locally on disk.\n    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{\n        UIImage *sourceImage = [(FICDPhoto *)entity sourceImage];\n        dispatch_async(dispatch_get_main_queue(), ^{\n            completionBlock(sourceImage);\n        });\n    });\n}\n\n- (BOOL)imageCache:(FICImageCache *)imageCache shouldProcessAllFormatsInFamily:(NSString *)formatFamily forEntity:(id<FICEntity>)entity {\n    return NO;\n}\n\n- (void)imageCache:(FICImageCache *)imageCache errorDidOccurWithMessage:(NSString *)errorMessage {\n    NSLog(@\"%@\", errorMessage);\n}\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Classes/FICDFullscreenPhotoDisplayController.h",
    "content": "//\n//  FICDFullscreenPhotoDisplayController.h\n//  FastImageCacheDemo\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\n\n@class FICDPhoto;\n\n@protocol FICDFullscreenPhotoDisplayControllerDelegate;\n\n@interface FICDFullscreenPhotoDisplayController : NSObject\n\n@property (nonatomic, weak) id <FICDFullscreenPhotoDisplayControllerDelegate> delegate;\n\n+ (instancetype)sharedDisplayController;\n\n@property (nonatomic, assign, readonly, getter = isDisplayingPhoto) BOOL displayingPhoto;\n\n- (void)showFullscreenPhoto:(FICDPhoto *)photo forImageFormatName:(NSString *)imageFormatName withThumbnailImageView:(UIImageView *)thumbnailImageView;\n- (void)hideFullscreenPhoto;\n\n@end\n\n@protocol FICDFullscreenPhotoDisplayControllerDelegate <NSObject>\n\n@optional\n- (void)photoDisplayController:(FICDFullscreenPhotoDisplayController *)photoDisplayController willShowSourceImage:(UIImage *)sourceImage forPhoto:(FICDPhoto *)photo withThumbnailImageView:(UIImageView *)thumbnailImageView;\n- (void)photoDisplayController:(FICDFullscreenPhotoDisplayController *)photoDisplayController didShowSourceImage:(UIImage *)sourceImage forPhoto:(FICDPhoto *)photo withThumbnailImageView:(UIImageView *)thumbnailImageView;\n- (void)photoDisplayController:(FICDFullscreenPhotoDisplayController *)photoDisplayController willHideSourceImage:(UIImage *)sourceImage forPhoto:(FICDPhoto *)photo withThumbnailImageView:(UIImageView *)thumbnailImageView;\n- (void)photoDisplayController:(FICDFullscreenPhotoDisplayController *)photoDisplayController didHideSourceImage:(UIImage *)sourceImage forPhoto:(FICDPhoto *)photo withThumbnailImageView:(UIImageView *)thumbnailImageView;\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Classes/FICDFullscreenPhotoDisplayController.m",
    "content": "//\n//  FICDFullscreenPhotoDisplayController.m\n//  FastImageCacheDemo\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICDFullscreenPhotoDisplayController.h\"\n#import \"FICDPhoto.h\"\n\n#import <CoreImage/CoreImage.h>\n\n#pragma mark Class Extension\n\n@interface FICDFullscreenPhotoDisplayController () <UIGestureRecognizerDelegate> {\n    __weak id <FICDFullscreenPhotoDisplayControllerDelegate> _delegate;\n    BOOL _delegateImplementsWillShowSourceImageForPhotoWithThumbnailImageView;\n    BOOL _delegateImplementsDidShowSourceImageForPhotoWithThumbnailImageView;\n    BOOL _delegateImplementsWillHideSourceImageForPhotoWithThumbnailImageView;\n    BOOL _delegateImplementsDidHideSourceImageForPhotoWithThumbnailImageView;\n    \n    UIView *_fullscreenView;\n    UIView *_backgroundView;\n    \n    UIImageView *_thumbnailImageView;\n    CGRect _originalThumbnailImageViewFrame;\n    NSUInteger _originalThumbnailImageViewSubviewIndex;\n    UIView *_originalThumbnailImageViewSuperview;\n    \n    UIImageView *_sourceImageView;\n    FICDPhoto *_photo;\n    \n    UITapGestureRecognizer *_tapGestureRecognizer;\n    \n    CIContext* _CoreImageContext;\n}\n\n@end\n\n#pragma mark\n\n@implementation FICDFullscreenPhotoDisplayController\n\n@synthesize delegate = _delegate;\n\n#pragma mark - Property Accessors\n\n- (void)setDelegate:(id<FICDFullscreenPhotoDisplayControllerDelegate>)delegate {\n    _delegate = delegate;\n    \n    _delegateImplementsWillShowSourceImageForPhotoWithThumbnailImageView = [_delegate respondsToSelector:@selector(photoDisplayController:willShowSourceImage:forPhoto:withThumbnailImageView:)];\n    _delegateImplementsDidShowSourceImageForPhotoWithThumbnailImageView = [_delegate respondsToSelector:@selector(photoDisplayController:didShowSourceImage:forPhoto:withThumbnailImageView:)];\n    _delegateImplementsWillHideSourceImageForPhotoWithThumbnailImageView = [_delegate respondsToSelector:@selector(photoDisplayController:willHideSourceImage:forPhoto:withThumbnailImageView:)];\n    _delegateImplementsDidHideSourceImageForPhotoWithThumbnailImageView = [_delegate respondsToSelector:@selector(photoDisplayController:didHideSourceImage:forPhoto:withThumbnailImageView:)];\n}\n\n- (BOOL)isDisplayingPhoto {\n    return _photo != nil;\n}\n\n#pragma mark - Object Lifecycle\n\n+ (instancetype)sharedDisplayController {\n    static FICDFullscreenPhotoDisplayController *__sharedDisplayController = nil;\n    \n    if (__sharedDisplayController == nil) {\n        static dispatch_once_t onceToken;\n        dispatch_once(&onceToken, ^{\n            __sharedDisplayController = [[[self class] alloc] init];\n        });\n    }\n    \n    return __sharedDisplayController;\n}\n\n- (id)init {\n    self = [super init];\n    \n    if (self != nil) {\n        UIViewAutoresizing autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);\n        \n        _fullscreenView = [[UIView alloc] init];\n        [_fullscreenView setAutoresizingMask:autoresizingMask];\n        \n        _backgroundView = [[UIView alloc] init];\n        [_backgroundView setAutoresizingMask:autoresizingMask];\n        [_backgroundView setBackgroundColor:[UIColor colorWithWhite:0 alpha:0.8]];\n        \n        _sourceImageView = [[UIImageView alloc] init];\n        [_sourceImageView setAutoresizingMask:autoresizingMask];\n        [_sourceImageView setContentMode:UIViewContentModeScaleAspectFill];\n        [_sourceImageView setClipsToBounds:YES];\n        \n        _tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_tapGestureRecognizerStateDidChange)];\n        [_fullscreenView addGestureRecognizer:_tapGestureRecognizer];\n        \n        _CoreImageContext = [CIContext contextWithOptions:nil];\n    }\n    \n    return self;\n}\n\n- (void)dealloc {\n    [_tapGestureRecognizer setDelegate:nil];\n}\n\n#pragma mark - Showing and Hiding a Fullscreen Photo\n\n- (void)showFullscreenPhoto:(FICDPhoto *)photo forImageFormatName:(NSString *)imageFormatName withThumbnailImageView:(UIImageView *)thumbnailImageView {\n    // Stash away the photo\n    _photo = photo;\n\n    // Stash away original thumbnail image view information\n    _thumbnailImageView = thumbnailImageView;\n    _originalThumbnailImageViewSuperview = [thumbnailImageView superview];\n    _originalThumbnailImageViewFrame = [thumbnailImageView frame];\n    _originalThumbnailImageViewSubviewIndex = [[[thumbnailImageView superview] subviews] indexOfObject:thumbnailImageView];\n    \n    // Configure the fullscreen view\n    UIView *rootViewControllerView = [[[[UIApplication sharedApplication] keyWindow] rootViewController] view];\n    [_fullscreenView setFrame:[rootViewControllerView bounds]];\n    [rootViewControllerView addSubview:_fullscreenView];\n    \n    // Configure the background view\n    [_backgroundView setFrame:[_fullscreenView bounds]];\n    [_backgroundView setAlpha:0];\n    [_fullscreenView addSubview:_backgroundView];\n    \n    // Configure the thumbnail image view\n    CGRect convertedThumbnailImageViewFrame = [_originalThumbnailImageViewSuperview convertRect:_originalThumbnailImageViewFrame toView:_fullscreenView];\n    [_thumbnailImageView setFrame:convertedThumbnailImageViewFrame];\n    [_fullscreenView addSubview:_thumbnailImageView];\n    \n    // Configure the source image view\n    UIImage *sourceImage = [photo sourceImage];\n    \n    // Desaturate the source image with Core Image if the image format name is FICDPhotoSquareImage8BitGrayscaleFormatName\n    if ([imageFormatName isEqualToString:FICDPhotoSquareImage8BitGrayscaleFormatName]) {\n        CIFilter *colorControlsFilter = [CIFilter filterWithName:@\"CIColorControls\"];\n        [colorControlsFilter setValue:[CIImage imageWithCGImage:[sourceImage CGImage]] forKey:kCIInputImageKey];\n        [colorControlsFilter setValue:[NSNumber numberWithFloat:0] forKey:@\"inputSaturation\"];\n        \n        CIImage *outputCIImage = [colorControlsFilter outputImage];\n        CGImageRef outputImageRef = [_CoreImageContext createCGImage:outputCIImage fromRect:[outputCIImage extent]];\n        sourceImage = [UIImage imageWithCGImage:outputImageRef];\n        \n        CGImageRelease(outputImageRef);\n    }\n    \n    [_sourceImageView setImage:sourceImage];\n    [_sourceImageView setFrame:convertedThumbnailImageViewFrame];\n    [_sourceImageView setAlpha:0];\n    [_fullscreenView addSubview:_sourceImageView];\n    \n    // Inform the delegate that we're about to show a fullscreen photo\n    if (_delegateImplementsWillShowSourceImageForPhotoWithThumbnailImageView) {\n        [_delegate photoDisplayController:self willShowSourceImage:sourceImage forPhoto:_photo withThumbnailImageView:_thumbnailImageView];\n    }\n    \n    // Animate fullscreen photo appearance\n    [UIView animateWithDuration:0.3 animations:^{\n        [_backgroundView setAlpha:1];\n        [_thumbnailImageView setFrame:[_fullscreenView bounds]];\n        [_sourceImageView setAlpha:1];\n        [_sourceImageView setFrame:[_fullscreenView bounds]];\n    } completion:^(BOOL finished) {\n        // Inform the delegate that we just showed a fullscreen photo\n        if (_delegateImplementsDidShowSourceImageForPhotoWithThumbnailImageView) {\n            [_delegate photoDisplayController:self didShowSourceImage:sourceImage forPhoto:_photo withThumbnailImageView:_thumbnailImageView];\n        }\n    }];\n}\n\n- (void)hideFullscreenPhoto {\n    UIImage *sourceImage = [_sourceImageView image];\n    // Inform the delegate that we're about to hide a fullscreen photo\n    if (_delegateImplementsWillHideSourceImageForPhotoWithThumbnailImageView) {\n        [_delegate photoDisplayController:self willHideSourceImage:sourceImage forPhoto:_photo withThumbnailImageView:_thumbnailImageView];\n    }\n\n    CGRect convertedThumbnailImageViewFrame = [_originalThumbnailImageViewSuperview convertRect:_originalThumbnailImageViewFrame toView:_fullscreenView];\n\n    // Animate fullscreen photo appearance\n    [UIView animateWithDuration:0.3 animations:^{\n        [_backgroundView setAlpha:0];\n        [_thumbnailImageView setFrame:convertedThumbnailImageViewFrame];\n        [_sourceImageView setAlpha:0];\n        [_sourceImageView setFrame:convertedThumbnailImageViewFrame];\n    } completion:^(BOOL finished) {\n        [_thumbnailImageView setFrame:_originalThumbnailImageViewFrame];\n        [_originalThumbnailImageViewSuperview insertSubview:_thumbnailImageView atIndex:_originalThumbnailImageViewSubviewIndex];\n        \n        [_fullscreenView removeFromSuperview];\n        \n        // Inform the delegate that we just hide a fullscreen photo\n        if (_delegateImplementsDidHideSourceImageForPhotoWithThumbnailImageView) {\n            [_delegate photoDisplayController:self didHideSourceImage:sourceImage forPhoto:_photo withThumbnailImageView:_thumbnailImageView];\n        }\n        \n        // Clean up\n        _photo = nil;\n        \n        _thumbnailImageView = nil;\n        _originalThumbnailImageViewSuperview = nil;\n        \n        _originalThumbnailImageViewFrame = CGRectZero;\n        _originalThumbnailImageViewSubviewIndex = 0;\n        \n        _sourceImageView.image = nil;\n    }];\n}\n\n- (void)_tapGestureRecognizerStateDidChange {\n    if ([_tapGestureRecognizer state] == UIGestureRecognizerStateEnded) {\n        [self hideFullscreenPhoto];\n    }\n}\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Classes/FICDPhoto.h",
    "content": "//\n//  FICDPhoto.h\n//  FastImageCacheDemo\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICEntity.h\"\n\nextern NSString *const FICDPhotoImageFormatFamily;\n\nextern NSString *const FICDPhotoSquareImage32BitBGRAFormatName;\nextern NSString *const FICDPhotoSquareImage32BitBGRFormatName;\nextern NSString *const FICDPhotoSquareImage16BitBGRFormatName;\nextern NSString *const FICDPhotoSquareImage8BitGrayscaleFormatName;\nextern NSString *const FICDPhotoPixelImageFormatName;\n\nextern CGSize const FICDPhotoSquareImageSize;\nextern CGSize const FICDPhotoPixelImageSize;\n\n@interface FICDPhoto : NSObject <FICEntity>\n\n@property (nonatomic, copy) NSURL *sourceImageURL;\n@property (nonatomic, strong, readonly) UIImage *sourceImage;\n@property (nonatomic, strong, readonly) UIImage *thumbnailImage;\n@property (nonatomic, assign, readonly) BOOL thumbnailImageExists;\n\n// Methods for demonstrating more conventional caching techniques\n- (void)generateThumbnail;\n- (void)deleteThumbnail;\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Classes/FICDPhoto.m",
    "content": "//\n//  FICDPhoto.m\n//  FastImageCacheDemo\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICDPhoto.h\"\n#import \"FICUtilities.h\"\n\n#pragma mark External Definitions\n\nNSString *const FICDPhotoImageFormatFamily = @\"FICDPhotoImageFormatFamily\";\n\nNSString *const FICDPhotoSquareImage32BitBGRAFormatName = @\"com.path.FastImageCacheDemo.FICDPhotoSquareImage32BitBGRAFormatName\";\nNSString *const FICDPhotoSquareImage32BitBGRFormatName = @\"com.path.FastImageCacheDemo.FICDPhotoSquareImage32BitBGRFormatName\";\nNSString *const FICDPhotoSquareImage16BitBGRFormatName = @\"com.path.FastImageCacheDemo.FICDPhotoSquareImage16BitBGRFormatName\";\nNSString *const FICDPhotoSquareImage8BitGrayscaleFormatName = @\"com.path.FastImageCacheDemo.FICDPhotoSquareImage8BitGrayscaleFormatName\";\nNSString *const FICDPhotoPixelImageFormatName = @\"com.path.FastImageCacheDemo.FICDPhotoPixelImageFormatName\";\n\nCGSize const FICDPhotoSquareImageSize = {75, 75};\nCGSize const FICDPhotoPixelImageSize = {1, 1};\n\n#pragma mark - Class Extension\n\n@interface FICDPhoto () {\n    NSURL *_sourceImageURL;\n    NSString *_UUID;\n    NSString *_thumbnailFilePath;\n    BOOL _thumbnailFileExists;\n    BOOL _didCheckForThumbnailFile;\n}\n\n@end\n\n#pragma mark\n\n@implementation FICDPhoto\n\n@synthesize sourceImageURL = _sourceImageURL;\n\n#pragma mark - Property Accessors\n\n- (UIImage *)sourceImage {\n    UIImage *sourceImage = [UIImage imageWithContentsOfFile:[_sourceImageURL path]];\n    \n    return sourceImage;\n}\n\n- (UIImage *)thumbnailImage {\n    UIImage *thumbnailImage = [UIImage imageWithContentsOfFile:[self _thumbnailFilePath]];\n    \n    return thumbnailImage;\n}\n\n- (BOOL)thumbnailImageExists {\n    BOOL thumbnailImageExists = [[NSFileManager defaultManager] fileExistsAtPath:[self _thumbnailFilePath]];\n    \n    return thumbnailImageExists;\n}\n\n#pragma mark - Image Helper Functions\n\nstatic CGMutablePathRef _FICDCreateRoundedRectPath(CGRect rect, CGFloat cornerRadius) {\n    CGMutablePathRef path = CGPathCreateMutable();\n    \n    CGFloat minX = CGRectGetMinX(rect);\n    CGFloat midX = CGRectGetMidX(rect);\n    CGFloat maxX = CGRectGetMaxX(rect);\n    CGFloat minY = CGRectGetMinY(rect);\n    CGFloat midY = CGRectGetMidY(rect);\n    CGFloat maxY = CGRectGetMaxY(rect);\n    \n    CGPathMoveToPoint(path, NULL, minX, midY);\n    CGPathAddArcToPoint(path, NULL, minX, maxY, midX, maxY, cornerRadius);\n    CGPathAddArcToPoint(path, NULL, maxX, maxY, maxX, midY, cornerRadius);\n    CGPathAddArcToPoint(path, NULL, maxX, minY, midX, minY, cornerRadius);\n    CGPathAddArcToPoint(path, NULL, minX, minY, minX, midY, cornerRadius);\n    \n    return path;\n}\n\nstatic UIImage * _FICDSquareImageFromImage(UIImage *image) {\n    UIImage *squareImage = nil;\n    CGSize imageSize = [image size];\n    \n    if (imageSize.width == imageSize.height) {\n        squareImage = image;\n    } else {\n        // Compute square crop rect\n        CGFloat smallerDimension = MIN(imageSize.width, imageSize.height);\n        CGRect cropRect = CGRectMake(0, 0, smallerDimension, smallerDimension);\n        \n        // Center the crop rect either vertically or horizontally, depending on which dimension is smaller\n        if (imageSize.width <= imageSize.height) {\n            cropRect.origin = CGPointMake(0, rintf((imageSize.height - smallerDimension) / 2.0));\n        } else {\n            cropRect.origin = CGPointMake(rintf((imageSize.width - smallerDimension) / 2.0), 0);\n        }\n        \n        CGImageRef croppedImageRef = CGImageCreateWithImageInRect([image CGImage], cropRect);\n        squareImage = [UIImage imageWithCGImage:croppedImageRef];\n        CGImageRelease(croppedImageRef);\n    }\n    \n    return squareImage;\n}\n\nstatic UIImage * _FICDStatusBarImageFromImage(UIImage *image) {\n    CGSize imageSize = [image size];\n    CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size;\n    CGRect cropRect = CGRectMake(0, 0, imageSize.width, statusBarSize.height);\n    \n    CGImageRef croppedImageRef = CGImageCreateWithImageInRect([image CGImage], cropRect);\n    UIImage *statusBarImage = [UIImage imageWithCGImage:croppedImageRef];\n    CGImageRelease(croppedImageRef);\n    \n    return statusBarImage;\n}\n\n#pragma mark - Conventional Image Caching Techniques\n\n- (NSString *)_thumbnailFilePath {\n    if (!_thumbnailFilePath) {\n        NSURL *photoURL = [self sourceImageURL];\n        _thumbnailFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[photoURL absoluteString] lastPathComponent]];\n    }\n    \n    return _thumbnailFilePath;\n}\n\n- (void)generateThumbnail {\n    NSString *thumbnailFilePath = [self _thumbnailFilePath];\n    if (!_didCheckForThumbnailFile) {\n        _didCheckForThumbnailFile = YES;\n        _thumbnailFileExists = [[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath];\n    }\n    \n    if (_thumbnailFileExists == NO) {\n        CGFloat screenScale = [[UIScreen mainScreen] scale];\n        CGRect contextBounds = CGRectZero;\n        contextBounds.size = CGSizeMake(FICDPhotoSquareImageSize.width * screenScale, FICDPhotoSquareImageSize.height * screenScale);\n        \n        UIImage *sourceImage = [self sourceImage];\n        UIImage *squareImage = _FICDSquareImageFromImage(sourceImage);\n\n        UIGraphicsBeginImageContext(contextBounds.size);\n        \n        [squareImage drawInRect:contextBounds];\n        UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();\n        NSData *scaledImageJPEGRepresentation = UIImageJPEGRepresentation(scaledImage, 0.8);\n        \n        [scaledImageJPEGRepresentation writeToFile:thumbnailFilePath atomically:NO];\n        \n        UIGraphicsEndImageContext();\n        _thumbnailFileExists = YES;\n    }\n}\n\n- (void)deleteThumbnail {\n    [[NSFileManager defaultManager] removeItemAtPath:[self _thumbnailFilePath] error:NULL];\n    _thumbnailFileExists = NO;\n}\n\n#pragma mark - Protocol Implementations\n\n#pragma mark - FICImageCacheEntity\n\n- (NSString *)fic_UUID {\n    if (_UUID == nil) {\n        // MD5 hashing is expensive enough that we only want to do it once\n        NSString *imageName = [_sourceImageURL lastPathComponent];\n        CFUUIDBytes UUIDBytes = FICUUIDBytesFromMD5HashOfString(imageName);\n        _UUID = FICStringWithUUIDBytes(UUIDBytes);\n    }\n    \n    return _UUID;\n}\n\n- (NSString *)fic_sourceImageUUID {\n    return [self fic_UUID];\n}\n\n- (NSURL *)fic_sourceImageURLWithFormatName:(NSString *)formatName {\n    return _sourceImageURL;\n}\n\n- (FICEntityImageDrawingBlock)fic_drawingBlockForImage:(UIImage *)image withFormatName:(NSString *)formatName {\n    FICEntityImageDrawingBlock drawingBlock = ^(CGContextRef contextRef, CGSize contextSize) {\n        CGRect contextBounds = CGRectZero;\n        contextBounds.size = contextSize;\n        CGContextClearRect(contextRef, contextBounds);\n        \n        if ([formatName isEqualToString:FICDPhotoPixelImageFormatName]) {\n            UIImage *statusBarImage = _FICDStatusBarImageFromImage(image);\n            CGContextSetInterpolationQuality(contextRef, kCGInterpolationMedium);\n            \n            UIGraphicsPushContext(contextRef);\n            [statusBarImage drawInRect:contextBounds];\n            UIGraphicsPopContext();\n        } else {\n            if ([formatName isEqualToString:FICDPhotoSquareImage32BitBGRAFormatName] == NO) {\n                // Fill with white for image formats that are opaque\n                CGContextSetFillColorWithColor(contextRef, [[UIColor whiteColor] CGColor]);\n                CGContextFillRect(contextRef, contextBounds);\n            }\n            \n            UIImage *squareImage = _FICDSquareImageFromImage(image);\n            \n            // Clip to a rounded rect\n            CGPathRef path = _FICDCreateRoundedRectPath(contextBounds, 12);\n            CGContextAddPath(contextRef, path);\n            CFRelease(path);\n            CGContextEOClip(contextRef);\n            \n            UIGraphicsPushContext(contextRef);\n            [squareImage drawInRect:contextBounds];\n            UIGraphicsPopContext();\n        }\n    };\n    \n    return drawingBlock;\n}\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Classes/FICDPhotosTableViewCell.h",
    "content": "//\n//  FICDPhotosTableViewCell.h\n//  FastImageCacheDemo\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import <UIKit/UIKit.h>\n\n@class FICDPhoto;\n\n@protocol FICDPhotosTableViewCellDelegate;\n\n@interface FICDPhotosTableViewCell : UITableViewCell\n\n@property (nonatomic, weak) id <FICDPhotosTableViewCellDelegate> delegate;\n@property (nonatomic, assign) BOOL usesImageTable;\n@property (nonatomic, copy) NSArray *photos;\n@property (nonatomic, copy) NSString *imageFormatName;\n\n+ (NSString *)reuseIdentifier;\n+ (NSInteger)photosPerRow;\n+ (CGFloat)outerPadding;\n+ (CGFloat)rowHeight;\n\n@end\n\n@protocol FICDPhotosTableViewCellDelegate <NSObject>\n\n@required\n- (void)photosTableViewCell:(FICDPhotosTableViewCell *)photosTableViewCell didSelectPhoto:(FICDPhoto *)photo withImageView:(UIImageView *)imageView;\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Classes/FICDPhotosTableViewCell.m",
    "content": "//\n//  FICDPhotosTableViewCell.m\n//  FastImageCacheDemo\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICDPhotosTableViewCell.h\"\n#import \"FICDPhoto.h\"\n#import \"FICImageCache.h\"\n#import \"FICDAppDelegate.h\"\n\n#pragma mark Class Extension\n\n@interface FICDPhotosTableViewCell () <UIGestureRecognizerDelegate> {\n    __weak id <FICDPhotosTableViewCellDelegate> _delegate;\n    BOOL _usesImageTable;\n    NSArray *_photos;\n    NSString *_imageFormatName;\n    \n    NSArray *_imageViews;\n    UITapGestureRecognizer *_tapGestureRecognizer;\n}\n\n@end\n\n#pragma mark\n\n@implementation FICDPhotosTableViewCell\n\n@synthesize delegate = _delegate;\n@synthesize usesImageTable = _usesImageTable;\n@synthesize photos = _photos;\n@synthesize imageFormatName = _imageFormatName;\n\n#pragma mark - Property Accessors\n\n- (void)setPhotos:(NSArray *)photos {\n    if (photos != _photos) {\n        _photos = [photos copy];\n\n        for (NSInteger i = 0; i < [_imageViews count]; i++) {\n            UIImageView *imageView = [_imageViews objectAtIndex:i];\n\n            if (i < [_photos count]) {\n                FICDPhoto *photo = [_photos objectAtIndex:i];\n\n                if (_usesImageTable) {\n                    [[FICImageCache sharedImageCache] retrieveImageForEntity:photo withFormatName:_imageFormatName completionBlock:^(id<FICEntity> entity, NSString *formatName, UIImage *image) {\n                        // This completion block may be called much later. We should check to make sure this cell hasn't been reused for different photos before displaying the image that has loaded.\n                        if (photos == [self photos]) {\n                            [imageView setImage:image];\n                        }\n                    }];\n                } else {\n                    [imageView setImage:[photo thumbnailImage]];\n                }\n            } else {\n                // Last row might not be full\n                [imageView setImage:nil];\n            }\n        }\n    }\n}\n\n#pragma mark - Class-Level Definitions\n\n+ (NSString *)reuseIdentifier {\n    static NSString *__reuseIdentifier = nil;\n    \n    static dispatch_once_t onceToken;\n    dispatch_once(&onceToken, ^{\n        __reuseIdentifier = NSStringFromClass([FICDPhotosTableViewCell class]);\n    });\n\n    return __reuseIdentifier;\n}\n\n+ (NSInteger)photosPerRow {\n    NSInteger photosPerRow = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? 9 : 4;\n    \n    return photosPerRow;\n}\n\n+ (CGFloat)outerPadding {\n    CGFloat outerPadding = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? 10 : 4;\n    \n    return outerPadding;\n}\n\n+ (CGFloat)rowHeight {\n    CGFloat rowHeight = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? 84 : 79;\n    \n    return rowHeight;\n}\n\n#pragma mark - Object Lifecycle\n\n- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {\n    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];\n    \n    if (self != nil) {\n        _tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_tapGestureRecognizerStateDidChange)];\n        [self addGestureRecognizer:_tapGestureRecognizer];\n\n        NSInteger photosPerRow = [[self class] photosPerRow];\n        NSMutableArray *imageViews = [[NSMutableArray alloc] initWithCapacity:photosPerRow];\n\n        for (NSInteger i = 0; i < photosPerRow; i++) {\n            UIImageView *imageView = [[UIImageView alloc] init];\n            [imageView setContentMode:UIViewContentModeScaleAspectFill];\n            [imageViews addObject:imageView];\n            [self.contentView addSubview:imageView];\n        }\n\n        _imageViews = [imageViews copy];\n    }\n    \n    return self;\n}\n\n- (id)init {\n    return [self initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];\n}\n\n- (void)dealloc {\n    [_tapGestureRecognizer setDelegate:nil];\n}\n\n#pragma mark - Configuring the View Hierarchy\n\n- (void)layoutSubviews {\n    [super layoutSubviews];\n    \n    CGFloat innerPadding = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? 9 : 4;\n    CGFloat outerPadding = [[self class] outerPadding];\n    \n    CGRect imageViewFrame = CGRectMake(outerPadding, outerPadding, FICDPhotoSquareImageSize.width, FICDPhotoSquareImageSize.height);\n\n    NSInteger count = [_photos count];\n    \n    for (NSInteger i = 0; i < count; i++) {\n        UIImageView *imageView = [_imageViews objectAtIndex:i];\n        [imageView setFrame:imageViewFrame];\n\n        imageViewFrame.origin.x += imageViewFrame.size.width + innerPadding;\n    }\n}\n\n#pragma mark - Responding to User Interaction Events\n\n- (void)_tapGestureRecognizerStateDidChange {\n    if ([_tapGestureRecognizer state] == UIGestureRecognizerStateEnded) {\n        CGPoint tapLocationInSelf = [_tapGestureRecognizer locationInView:self];\n        UIImageView *selectedImageView = nil;\n        \n        for (UIImageView *imageView in _imageViews) {\n            CGRect imageViewFrame = [imageView frame];\n            BOOL frameContainsTapLocation = CGRectContainsPoint(imageViewFrame, tapLocationInSelf);\n            \n            if (frameContainsTapLocation) {\n                selectedImageView = imageView;\n            }\n        }\n        \n        if (selectedImageView != nil) {\n            NSUInteger imageViewIndex = [_imageViews indexOfObject:selectedImageView];\n            FICDPhoto *selectedPhoto = [_photos objectAtIndex:imageViewIndex];\n            \n            [_delegate photosTableViewCell:self didSelectPhoto:selectedPhoto withImageView:selectedImageView];\n        }\n    }\n}\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Classes/FICDTableView.h",
    "content": "//\n//  FICDTableView.h\n//  FastImageCacheDemo\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import <UIKit/UIKit.h>\n\n@interface FICDTableView : UITableView\n\n@property (nonatomic, assign, readonly) CGFloat averageFPS;\n\n- (void)resetScrollingPerformanceCounters;\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Classes/FICDTableView.m",
    "content": "//\n//  FICDTableView.m\n//  FastImageCacheDemo\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICDTableView.h\"\n\n#pragma mark Class Extension\n\n@interface FICDTableView () {\n    CADisplayLink *_displayLink;\n    NSInteger _framesInLastInterval;\n    CFAbsoluteTime _lastLogTime;\n    NSInteger _totalFrames;\n    NSTimeInterval _scrollingTime;\n    CGFloat _averageFPS;\n}\n\n@property (nonatomic, assign, readwrite) CGFloat averageFPS;\n\n@end\n\n#pragma mark\n\n@implementation FICDTableView\n\n@synthesize averageFPS = _averageFPS;\n\n#pragma mark - Object Lifecycle\n\n- (void)dealloc {\n    [_displayLink invalidate];\n}\n\n- (void)didMoveToWindow {\n    if ([self window] != nil) {\n        [self _scrollingStatusDidChange];\n    } else {\n         [_displayLink invalidate];\n        _displayLink = nil;\n    }\n}\n\n#pragma mark - Monitoring Scrolling Performance\n\n- (void)resetScrollingPerformanceCounters {\n    _framesInLastInterval = 0;\n    _lastLogTime = CFAbsoluteTimeGetCurrent();\n    _scrollingTime = 0;\n    _totalFrames = 0;\n}\n\n- (void)_scrollingStatusDidChange {\n    NSString *currentRunLoopMode = [[NSRunLoop currentRunLoop] currentMode];\n    BOOL isScrolling = [currentRunLoopMode isEqualToString:UITrackingRunLoopMode];\n    \n    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_scrollingStatusDidChange) object:nil];\n    \n    if (isScrolling) {\n        if (_displayLink == nil) {\n            _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_screenDidUpdateWhileScrolling:)];\n            [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:UITrackingRunLoopMode];\n        }\n        \n        _framesInLastInterval = 0;\n        _lastLogTime = CFAbsoluteTimeGetCurrent();\n        [_displayLink setPaused:NO];\n        \n        // Let us know when scrolling has stopped\n        [self performSelector:@selector(_scrollingStatusDidChange) withObject:nil afterDelay:0 inModes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];\n    } else {\n        [_displayLink setPaused:YES];\n        \n        // Let us know when scrolling begins\n        [self performSelector:@selector(_scrollingStatusDidChange) withObject:nil afterDelay:0 inModes:[NSArray arrayWithObject:UITrackingRunLoopMode]];\n    }\n}\n\n- (void)_screenDidUpdateWhileScrolling:(CADisplayLink *)displayLink {\n    CFAbsoluteTime currentTime = CFAbsoluteTimeGetCurrent();\n    if (!_lastLogTime) {\n        _lastLogTime = currentTime;\n    }\n    CGFloat delta = currentTime - _lastLogTime;\n    if (delta >= 1) {\n        _scrollingTime += delta;\n        _totalFrames += _framesInLastInterval;\n        NSInteger lastFPS = (NSInteger)rintf((CGFloat)_framesInLastInterval / delta);\n        CGFloat averageFPS = (CGFloat)(_totalFrames / _scrollingTime);\n        [self setAverageFPS:averageFPS];\n        \n        static dispatch_queue_t __dispatchQueue = nil;\n        static dispatch_once_t onceToken;\n        dispatch_once(&onceToken, ^{\n            __dispatchQueue = dispatch_queue_create(\"com.path.FastImageCacheDemo.ScrollingPerformanceMeasurement\", 0);\n        });\n        \n        // We don't want the logging of scrolling performance to be able to impact the scrolling performance,\n        // so move both the logging and the string formatting onto a GCD serial queue.\n        dispatch_async(__dispatchQueue, ^{\n            NSLog(@\"*** FIC Demo: Last FPS = %ld, Average FPS = %.2f\", (long)lastFPS, averageFPS);\n        });\n        \n        _framesInLastInterval = 0;\n        _lastLogTime = currentTime;\n    } else {\n        _framesInLastInterval++;\n    }\n}\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Classes/FICDViewController.h",
    "content": "//\n//  FICDViewController.h\n//  FastImageCacheDemo\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import <UIKit/UIKit.h>\n\n@class FICDTableView;\n\n@interface FICDViewController : UIViewController\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Classes/FICDViewController.m",
    "content": "//\n//  FICDViewController.m\n//  FastImageCacheDemo\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import \"FICDViewController.h\"\n#import \"FICImageCache.h\"\n#import \"FICDTableView.h\"\n#import \"FICDAppDelegate.h\"\n#import \"FICDPhoto.h\"\n#import \"FICDFullscreenPhotoDisplayController.h\"\n#import \"FICDPhotosTableViewCell.h\"\n\n#pragma mark Class Extension\n\n@interface FICDViewController () <UITableViewDataSource, UITableViewDelegate, UIAlertViewDelegate, FICDPhotosTableViewCellDelegate, FICDFullscreenPhotoDisplayControllerDelegate> {\n    FICDTableView *_tableView;\n    NSArray *_photos;\n    \n    NSString *_imageFormatName;\n    NSArray *_imageFormatStyleToolbarItems;\n    \n    BOOL _usesImageTable;\n    BOOL _shouldReloadTableViewAfterScrollingAnimationEnds;\n    BOOL _shouldResetData;\n    NSInteger _selectedMethodSegmentControlIndex;\n    NSInteger _callbackCount;\n    UIAlertView *_noImagesAlertView;\n    UILabel *_averageFPSLabel;\n}\n\n@end\n\n#pragma mark\n\n@implementation FICDViewController\n\n#pragma mark - Object Lifecycle\n\n- (id)init {\n    self = [super init];\n    \n    if (self != nil) {\n        NSBundle *mainBundle = [NSBundle mainBundle];\n        NSArray *imageURLs = [mainBundle URLsForResourcesWithExtension:@\"jpg\" subdirectory:@\"Demo Images\"];\n        \n        if ([imageURLs count] > 0) {\n            NSMutableArray *photos = [[NSMutableArray alloc] init];\n            for (NSURL *imageURL in imageURLs) {\n                FICDPhoto *photo = [[FICDPhoto alloc] init];\n                [photo setSourceImageURL:imageURL];\n                [photos addObject:photo];\n            }\n            \n            while ([photos count] < 5000) {\n                [photos addObjectsFromArray:photos]; // Create lots of photos to scroll through\n            }\n            \n            _photos = photos;\n        } else {\n            NSString *title = @\"No Source Images\";\n            NSString *message = @\"There are no JPEG images in the Demo Images folder. Please run the fetch_demo_images.sh script, or add your own JPEG images to this folder before running the demo app.\";\n            _noImagesAlertView = [[UIAlertView alloc] initWithTitle:title message:message delegate:self cancelButtonTitle:@\"OK\" otherButtonTitles:nil];\n            [_noImagesAlertView show];\n        }\n    }\n    \n    return self;\n}\n\n- (void)dealloc {\n    [_tableView setDelegate:nil];\n    [_tableView setDataSource:nil];\n    \n    [_noImagesAlertView setDelegate:nil];\n}\n\n#pragma mark - View Controller Lifecycle\n\n- (void)loadView {\n    CGRect viewFrame = [[UIScreen mainScreen] bounds];\n    UIView *view = [[UIView alloc] initWithFrame:viewFrame];\n    [view setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];\n    [view setBackgroundColor:[UIColor whiteColor]];\n    \n    [self setView:view];\n    \n    // Configure the table view\n    if (_tableView == nil) {\n        _tableView = [[FICDTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];\n        [_tableView setDataSource:self];\n        [_tableView setDelegate:self];\n        [_tableView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];\n        [_tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];\n        [_tableView registerClass:[FICDPhotosTableViewCell class] forCellReuseIdentifier:[FICDPhotosTableViewCell reuseIdentifier]];\n        \n        CGFloat tableViewCellOuterPadding = [FICDPhotosTableViewCell outerPadding];\n        [_tableView setContentInset:UIEdgeInsetsMake(0, 0, tableViewCellOuterPadding, 0)];\n        \n        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {\n            [_tableView setScrollIndicatorInsets:UIEdgeInsetsMake(7, 0, 7, 1)];\n        }\n    }\n    \n    [_tableView setFrame:[view bounds]];\n    [view addSubview:_tableView];\n    \n    // Configure the navigation item\n    UINavigationItem *navigationItem = [self navigationItem];\n    \n    UIBarButtonItem *resetBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@\"Reset\" style:UIBarButtonItemStylePlain target:self action:@selector(_reset)];\n    [navigationItem setLeftBarButtonItem:resetBarButtonItem];\n    \n    UISegmentedControl *methodSegmentedControl = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:@\"Conventional\", @\"Image Table\", nil]];\n    [methodSegmentedControl setSelectedSegmentIndex:0];\n    [methodSegmentedControl addTarget:self action:@selector(_methodSegmentedControlValueChanged:) forControlEvents:UIControlEventValueChanged];\n    [methodSegmentedControl sizeToFit];\n    [navigationItem setTitleView:methodSegmentedControl];\n    \n    // Configure the average FPS label\n    if (_averageFPSLabel == nil) {\n        _averageFPSLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 54, 22)];\n        [_averageFPSLabel setBackgroundColor:[UIColor clearColor]];\n        [_averageFPSLabel setFont:[UIFont boldSystemFontOfSize:16]];\n        [_averageFPSLabel setTextAlignment:NSTextAlignmentRight];\n        \n        [_tableView addObserver:self forKeyPath:@\"averageFPS\" options:NSKeyValueObservingOptionNew context:NULL];\n    }\n    \n    UIBarButtonItem *averageFPSLabelBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:_averageFPSLabel];\n    [navigationItem setRightBarButtonItem:averageFPSLabelBarButtonItem];\n    \n    // Configure the image format styles toolbar\n    if (_imageFormatStyleToolbarItems == nil) {\n        NSMutableArray *mutableImageFormatStyleToolbarItems = [NSMutableArray array];\n        \n        UIBarButtonItem *flexibleSpaceToolbarItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:NULL];\n        [mutableImageFormatStyleToolbarItems addObject:flexibleSpaceToolbarItem];\n        \n        NSArray *imageFormatStyleSegmentedControlTitles = nil;\n        BOOL userInterfaceIdiomIsPhone = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone;\n        \n        if (userInterfaceIdiomIsPhone) {\n            imageFormatStyleSegmentedControlTitles = [NSArray arrayWithObjects:@\"32BGRA\", @\"32BGR\", @\"16BGR\", @\"8Grayscale\", nil];\n        } else {\n            imageFormatStyleSegmentedControlTitles = [NSArray arrayWithObjects:@\"32-bit BGRA\", @\"32-bit BGR\", @\"16-bit BGR\", @\"8-bit Grayscale\", nil];\n        }\n        \n        UISegmentedControl *imageFormatStyleSegmentedControl = [[UISegmentedControl alloc] initWithItems:imageFormatStyleSegmentedControlTitles];\n        [imageFormatStyleSegmentedControl setSelectedSegmentIndex:0];\n        [imageFormatStyleSegmentedControl addTarget:self action:@selector(_imageFormatStyleSegmentedControlValueChanged:) forControlEvents:UIControlEventValueChanged];\n        [imageFormatStyleSegmentedControl setApportionsSegmentWidthsByContent:userInterfaceIdiomIsPhone];\n        [imageFormatStyleSegmentedControl sizeToFit];\n        \n        UIBarButtonItem *imageFormatStyleSegmentedControlToolbarItem = [[UIBarButtonItem alloc] initWithCustomView:imageFormatStyleSegmentedControl];\n        [mutableImageFormatStyleToolbarItems addObject:imageFormatStyleSegmentedControlToolbarItem];\n        \n        [mutableImageFormatStyleToolbarItems addObject:flexibleSpaceToolbarItem];\n        \n        _imageFormatStyleToolbarItems = [mutableImageFormatStyleToolbarItems copy];\n    }\n    \n    [self setToolbarItems:_imageFormatStyleToolbarItems];\n    \n    _imageFormatName = FICDPhotoSquareImage32BitBGRAFormatName;\n}\n\n- (void)viewDidAppear:(BOOL)animated {\n    [super viewDidAppear:animated];\n    \n    [[FICDFullscreenPhotoDisplayController sharedDisplayController] setDelegate:self];\n    [self reloadTableViewAndScrollToTop:YES];\n}\n\n#pragma mark - Reloading Data\n\n- (void)reloadTableViewAndScrollToTop:(BOOL)scrollToTop {\n    UIApplication *sharedApplication = [UIApplication sharedApplication];\n    \n    // Don't allow interaction events to interfere with thumbnail generation\n    if ([sharedApplication isIgnoringInteractionEvents] == NO) {\n        [sharedApplication beginIgnoringInteractionEvents];\n    }\n\n    if (scrollToTop) {\n        // If the table view isn't already scrolled to top, we do that now, deferring the actual table view reloading logic until the animation finishes.\n        CGFloat tableViewTopmostContentOffsetY = 0;\n        CGFloat tableViewCurrentContentOffsetY = [_tableView contentOffset].y;\n        \n        if ([self respondsToSelector:@selector(topLayoutGuide)]) {\n            id <UILayoutSupport> topLayoutGuide = [self topLayoutGuide];\n            tableViewTopmostContentOffsetY = -[topLayoutGuide length];\n        }\n        \n        if (tableViewCurrentContentOffsetY > tableViewTopmostContentOffsetY) {\n            [_tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];\n            \n            _shouldReloadTableViewAfterScrollingAnimationEnds = YES;\n        }\n    }\n    \n    if (_shouldReloadTableViewAfterScrollingAnimationEnds == NO) {\n        // Reset the data now\n        if (_shouldResetData) {\n            _shouldResetData = NO;\n            [[FICImageCache sharedImageCache] reset];\n            \n            // Delete all cached thumbnail images as well\n            for (FICDPhoto *photo in _photos) {\n                [photo deleteThumbnail];\n            }\n        }\n        \n        _usesImageTable = _selectedMethodSegmentControlIndex == 1;\n        \n        [[self navigationController] setToolbarHidden:(_usesImageTable == NO) animated:YES];\n        \n        dispatch_block_t tableViewReloadBlock = ^{\n            [_tableView reloadData];\n            [_tableView resetScrollingPerformanceCounters];\n            \n            if ([_tableView isHidden]) {\n                [[_tableView layer] addAnimation:[CATransition animation] forKey:kCATransition];\n            }\n            \n            [_tableView setHidden:NO];\n            \n            // Re-enable interaction events once every thumbnail has been generated\n            if ([sharedApplication isIgnoringInteractionEvents]) {\n                [sharedApplication endIgnoringInteractionEvents];\n            }\n        };\n        \n        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{\n            // In order to make a fair comparison for both methods, we ensure that the cached data is ready to go before updating the UI.\n            if (_usesImageTable) {\n                _callbackCount = 0;\n                NSSet *uniquePhotos = [NSSet setWithArray:_photos];\n                for (FICDPhoto *photo in uniquePhotos) {\n                    CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();\n                    FICImageCache *sharedImageCache = [FICImageCache sharedImageCache];\n                    \n                    if ([sharedImageCache imageExistsForEntity:photo withFormatName:_imageFormatName] == NO) {\n                        if (_callbackCount == 0) {\n                            NSLog(@\"*** FIC Demo: Fast Image Cache: Generating thumbnails...\");\n                            \n                            // Hide the table view's contents while we generate new thumbnails\n                            dispatch_async(dispatch_get_main_queue(), ^{\n                                [_tableView setHidden:YES];\n                                [[_tableView layer] addAnimation:[CATransition animation] forKey:kCATransition];\n                            });\n                        }\n                        \n                        _callbackCount++;\n                        \n                        [sharedImageCache asynchronouslyRetrieveImageForEntity:photo withFormatName:_imageFormatName completionBlock:^(id<FICEntity> entity, NSString *formatName, UIImage *image) {\n                            _callbackCount--;\n                            \n                            if (_callbackCount == 0) {\n                                NSLog(@\"*** FIC Demo: Fast Image Cache: Generated thumbnails in %g seconds\", CFAbsoluteTimeGetCurrent() - startTime);\n                                dispatch_async(dispatch_get_main_queue(), tableViewReloadBlock);\n                            }\n                        }];\n                    }\n                }\n                \n                if (_callbackCount == 0) {\n                    dispatch_async(dispatch_get_main_queue(), tableViewReloadBlock);\n                }\n            } else {\n                [self _generateConventionalThumbnails];\n                \n                dispatch_async(dispatch_get_main_queue(), tableViewReloadBlock);\n            }\n        });\n    }\n}\n\n- (void)_reset {\n    _shouldResetData = YES;\n    \n    [self reloadTableViewAndScrollToTop:YES];\n}\n\n- (void)_methodSegmentedControlValueChanged:(UISegmentedControl *)segmentedControl {\n    _selectedMethodSegmentControlIndex = [segmentedControl selectedSegmentIndex];\n    \n    // If there's any scrolling momentum, we want to stop it now\n    CGPoint tableViewContentOffset = [_tableView contentOffset];\n    [_tableView setContentOffset:tableViewContentOffset animated:NO];\n    \n    [self reloadTableViewAndScrollToTop:NO];\n}\n\n- (void)_imageFormatStyleSegmentedControlValueChanged:(UISegmentedControl *)segmentedControl {\n    NSInteger selectedSegmentedControlIndex = [segmentedControl selectedSegmentIndex];\n    \n    if (selectedSegmentedControlIndex == 0) {\n        _imageFormatName = FICDPhotoSquareImage32BitBGRAFormatName;\n    } else if (selectedSegmentedControlIndex == 1) {\n        _imageFormatName = FICDPhotoSquareImage32BitBGRFormatName;\n    } else if (selectedSegmentedControlIndex == 2) {\n        _imageFormatName = FICDPhotoSquareImage16BitBGRFormatName;\n    } else if (selectedSegmentedControlIndex == 3) {\n        _imageFormatName = FICDPhotoSquareImage8BitGrayscaleFormatName;\n    }\n    \n    [self reloadTableViewAndScrollToTop:NO];\n}\n\n#pragma mark - Image Helper Functions\n\nstatic UIImage * _FICDColorAveragedImageFromImage(UIImage *image) {\n    // Crop the image to the area occupied by the status bar\n    CGSize imageSize = [image size];\n    CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size;\n    CGRect cropRect = CGRectMake(0, 0, imageSize.width, statusBarSize.height);\n    \n    CGImageRef croppedImageRef = CGImageCreateWithImageInRect([image CGImage], cropRect);\n    UIImage *statusBarImage = [UIImage imageWithCGImage:croppedImageRef];\n    CGImageRelease(croppedImageRef);\n    \n    // Draw the cropped image into a 1x1 bitmap context; this automatically averages the color values of every pixel\n    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();\n    CGSize contextSize = CGSizeMake(1, 1);\n    CGContextRef bitmapContextRef = CGBitmapContextCreate(NULL, contextSize.width, contextSize.height, 8, 0, colorSpaceRef, (kCGImageAlphaNoneSkipFirst & kCGBitmapAlphaInfoMask));\n    CGContextSetInterpolationQuality(bitmapContextRef, kCGInterpolationMedium);\n    \n    CGRect drawRect = CGRectZero;\n    drawRect.size = contextSize;\n    \n    UIGraphicsPushContext(bitmapContextRef);\n    [statusBarImage drawInRect:drawRect];\n    UIGraphicsPopContext();\n    \n    // Create an image from the bitmap context\n    CGImageRef colorAveragedImageRef = CGBitmapContextCreateImage(bitmapContextRef);\n    UIImage *colorAveragedImage = [UIImage imageWithCGImage:colorAveragedImageRef];\n    \n    CGColorSpaceRelease(colorSpaceRef);\n    CGImageRelease(colorAveragedImageRef);\n    CGContextRelease(bitmapContextRef);\n    \n    return colorAveragedImage;\n}\n\nstatic BOOL _FICDImageIsLight(UIImage *image) {\n    BOOL imageIsLight = NO;\n    \n    CGImageRef imageRef = [image CGImage];\n    CGDataProviderRef dataProviderRef = CGImageGetDataProvider(imageRef);\n    NSData *pixelData = (__bridge_transfer NSData *)CGDataProviderCopyData(dataProviderRef);\n    \n    if ([pixelData length] > 0) {\n        const UInt8 *pixelBytes = [pixelData bytes];\n        \n        // Whether or not the image format is opaque, the first byte is always the alpha component, followed by RGB.\n        UInt8 pixelR = pixelBytes[1];\n        UInt8 pixelG = pixelBytes[2];\n        UInt8 pixelB = pixelBytes[3];\n        \n        // Calculate the perceived luminance of the pixel; the human eye favors green, followed by red, then blue.\n        double percievedLuminance = 1 - (((0.299 * pixelR) + (0.587 * pixelG) + (0.114 * pixelB)) / 255);\n        \n        imageIsLight = percievedLuminance < 0.5;\n    }\n    \n    return imageIsLight;\n}\n\n- (void)_updateStatusBarStyleForColorAveragedImage:(UIImage *)colorAveragedImage {\n    BOOL imageIsLight = _FICDImageIsLight(colorAveragedImage);\n    \n    UIStatusBarStyle statusBarStyle = imageIsLight ? UIStatusBarStyleDefault : UIStatusBarStyleLightContent;\n    [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle animated:YES];\n}\n\n#pragma mark - Working with Thumbnails\n\n- (void)_generateConventionalThumbnails {\n    BOOL neededToGenerateThumbnail = NO;\n    CFAbsoluteTime startTime = 0;\n    \n    NSSet *uniquePhotos = [NSSet setWithArray:_photos];\n    for (FICDPhoto *photo in uniquePhotos) {\n        if ([photo thumbnailImageExists] == NO) {\n            if (neededToGenerateThumbnail == NO) {\n                NSLog(@\"*** FIC Demo: Conventional Method: Generating thumbnails...\");\n                startTime = CFAbsoluteTimeGetCurrent();\n                \n                neededToGenerateThumbnail = YES;\n                \n                // Hide the table view's contents while we generate new thumbnails\n                dispatch_async(dispatch_get_main_queue(), ^{\n                    [_tableView setHidden:YES];\n                    [[_tableView layer] addAnimation:[CATransition animation] forKey:kCATransition];\n                });\n            }\n            \n            @autoreleasepool {\n                [photo generateThumbnail];\n            }\n        }\n    }\n    \n    if (neededToGenerateThumbnail) {\n        NSLog(@\"*** FIC Demo: Conventional Method: Generated thumbnails in %g seconds\", CFAbsoluteTimeGetCurrent() - startTime);\n    }\n}\n\n#pragma mark - Displaying the Average Framerate\n\n- (void)_displayAverageFPS:(CGFloat)averageFPS {\n    if ([_averageFPSLabel attributedText] == nil) {\n        CATransition *fadeTransition = [CATransition animation];\n        [[_averageFPSLabel layer] addAnimation:fadeTransition forKey:kCATransition];\n    }\n    \n    NSString *averageFPSString = [NSString stringWithFormat:@\"%.0f\", averageFPS];\n    NSUInteger averageFPSStringLength = [averageFPSString length];\n    NSString *displayString = [NSString stringWithFormat:@\"%@ FPS\", averageFPSString];\n    \n    UIColor *averageFPSColor = [UIColor blackColor];\n    \n    if (averageFPS > 45) {\n        averageFPSColor = [UIColor colorWithHue:(114 / 359.0) saturation:0.99 brightness:0.89 alpha:1]; // Green\n    } else if (averageFPS <= 45 && averageFPS > 30) {\n        averageFPSColor = [UIColor colorWithHue:(38 / 359.0) saturation:0.99 brightness:0.89 alpha:1];  // Orange\n    } else if (averageFPS < 30) {\n        averageFPSColor = [UIColor colorWithHue:(6 / 359.0) saturation:0.99 brightness:0.89 alpha:1];   // Red\n    }\n    \n    NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:displayString];\n    [mutableAttributedString addAttribute:NSForegroundColorAttributeName value:averageFPSColor range:NSMakeRange(0, averageFPSStringLength)];\n    \n    [_averageFPSLabel setAttributedText:mutableAttributedString];\n    \n    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_hideAverageFPSLabel) object:nil];\n    [self performSelector:@selector(_hideAverageFPSLabel) withObject:nil afterDelay:1.5];\n}\n\n- (void)_hideAverageFPSLabel {\n    CATransition *fadeTransition = [CATransition animation];\n    \n    [_averageFPSLabel setAttributedText:nil];\n    [[_averageFPSLabel layer] addAnimation:fadeTransition forKey:kCATransition];\n}\n\n#pragma mark - Protocol Implementations\n\n#pragma mark - UITableViewDataSource\n\n- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {\n    NSInteger numberOfRows = ceilf((CGFloat)[_photos count] / (CGFloat)[FICDPhotosTableViewCell photosPerRow]);\n    \n    return numberOfRows;\n}\n\n- (UITableViewCell*)tableView:(UITableView*)table cellForRowAtIndexPath:(NSIndexPath*)indexPath {\n    NSString *reuseIdentifier = [FICDPhotosTableViewCell reuseIdentifier];\n    \n    FICDPhotosTableViewCell *tableViewCell = (FICDPhotosTableViewCell *)[table dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath];\n    tableViewCell.selectionStyle = UITableViewCellSeparatorStyleNone;\n\n    [tableViewCell setDelegate:self];\n    [tableViewCell setImageFormatName:_imageFormatName];\n    \n    NSInteger photosPerRow = [FICDPhotosTableViewCell photosPerRow];\n    NSInteger startIndex = [indexPath row] * photosPerRow;\n    NSInteger count = MIN(photosPerRow, [_photos count] - startIndex);\n    NSArray *photos = [_photos subarrayWithRange:NSMakeRange(startIndex, count)];\n    \n    [tableViewCell setUsesImageTable:_usesImageTable];\n    [tableViewCell setPhotos:photos];\n    \n    return tableViewCell;\n}\n\n#pragma mark - UITableViewDelegate\n\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {\n    return [FICDPhotosTableViewCell rowHeight];\n}\n\n- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {\n    [tableView deselectRowAtIndexPath:indexPath animated:NO];\n}\n\n#pragma mark - UIScrollViewDelegate\n\n- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)willDecelerate {\n    if (willDecelerate == NO) {\n        [_tableView resetScrollingPerformanceCounters];\n    }\n}\n\n- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {\n    [_tableView resetScrollingPerformanceCounters];\n}\n\n- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {\n    [_tableView resetScrollingPerformanceCounters];\n\n    if (_shouldReloadTableViewAfterScrollingAnimationEnds) {\n        _shouldReloadTableViewAfterScrollingAnimationEnds = NO;\n        \n        // Add a slight delay before reloading the data\n        double delayInSeconds = 0.1;\n        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));\n        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){\n            [self reloadTableViewAndScrollToTop:NO];\n        });\n    }\n}\n\n#pragma mark - UIAlertViewDelegate\n\n- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {\n    if (alertView == _noImagesAlertView) {\n        [NSThread exit];\n    }\n}\n\n#pragma mark - FICDPhotosTableViewCellDelegate\n\n- (void)photosTableViewCell:(FICDPhotosTableViewCell *)photosTableViewCell didSelectPhoto:(FICDPhoto *)photo withImageView:(UIImageView *)imageView {\n    [[FICDFullscreenPhotoDisplayController sharedDisplayController] showFullscreenPhoto:photo forImageFormatName:_imageFormatName withThumbnailImageView:imageView];\n}\n\n#pragma mark - FICDFullscreenPhotoDisplayControllerDelegate\n\n- (void)photoDisplayController:(FICDFullscreenPhotoDisplayController *)photoDisplayController willShowSourceImage:(UIImage *)sourceImage forPhoto:(FICDPhoto *)photo withThumbnailImageView:(UIImageView *)thumbnailImageView {\n    // If we're running on iOS 7, we'll try to intelligently determine whether the photo contents underneath the status bar is light or dark.\n    if ([self respondsToSelector:@selector(preferredStatusBarStyle)]) {\n        if (_usesImageTable) {\n            [[FICImageCache sharedImageCache] retrieveImageForEntity:photo withFormatName:FICDPhotoPixelImageFormatName completionBlock:^(id<FICEntity> entity, NSString *formatName, UIImage *image) {\n                if (image != nil && [photoDisplayController isDisplayingPhoto]) {\n                    [self _updateStatusBarStyleForColorAveragedImage:image];\n                }\n            }];\n        } else {\n            UIImage *colorAveragedImage = _FICDColorAveragedImageFromImage(sourceImage);\n            [self _updateStatusBarStyleForColorAveragedImage:colorAveragedImage];\n        }\n    } else {\n        [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent animated:YES];\n    }\n}\n\n- (void)photoDisplayController:(FICDFullscreenPhotoDisplayController *)photoDisplayController willHideSourceImage:(UIImage *)sourceImage forPhoto:(FICDPhoto *)photo withThumbnailImageView:(UIImageView *)thumbnailImageView {\n    [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault animated:YES];\n}\n\n#pragma mark - NSObject (NSKeyValueObserving)\n\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {\n    if (object == _tableView && [keyPath isEqualToString:@\"averageFPS\"]) {\n        CGFloat averageFPS = [[change valueForKey:NSKeyValueChangeNewKey] floatValue];\n        averageFPS = MIN(MAX(0, averageFPS), 60);\n        [self _displayAverageFPS:averageFPS];\n    }\n}\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Demo Images/README",
    "content": "Either place your own .jpg files in this directory, or run the fetch_demo_images.sh script in the FastImageCacheDemo directory."
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/FastImageCacheDemo-Prefix.pch",
    "content": "//\n//  FastImageCacheDemo-Prefix.pch\n//  FastImageCacheDemo\n//\n//  Copyright (c) 2013 Path, Inc.\n//  See LICENSE for full license agreement.\n//\n\n#import <Availability.h>\n\n#ifndef FastImageCacheDemo_Prefix_pch\n#define FastImageCacheDemo_Prefix_pch\n\n#ifndef __IPHONE_7_0\n#warning \"This project uses features only available in iOS SDK 7.0 and later.\"\n#endif\n\n#ifdef __OBJC__\n#import <Foundation/Foundation.h>\n#import <QuartzCore/QuartzCore.h>\n#import <UIKit/UIKit.h>\n#endif\n\n#endif\n"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>UIRequiredDeviceCapabilities</key>\n\t<array>\n\t\t<string>armv7</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationPortraitUpsideDown</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/fetch_demo_images.sh",
    "content": "#!/bin/bash\n\necho \"Fetching demo images...\"\n`curl \"https://s3.amazonaws.com/fast-image-cache/demo-images/FICDDemoImage[000-099].jpg\" --create-dirs -o \"Demo Images/FICDDemoImage#1.jpg\" --silent`"
  },
  {
    "path": "FastImageCache/FastImageCacheDemo/main.m",
    "content": "//\n//  main.m\n//  FastImageCacheDemo\n//\n//  Created by Rui Peres on 17/06/2015.\n//  Copyright (c) 2015 Path. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import \"FICDAppDelegate.h\"\n\nint main(int argc, char * argv[]) {\n    @autoreleasepool {\n        return UIApplicationMain(argc, argv, nil, NSStringFromClass([FICDAppDelegate class]));\n    }\n}\n"
  },
  {
    "path": "FastImageCache/FastImageCacheTests/FastImageCacheTests.m",
    "content": "//\n//  FastImageCacheTests.m\n//  FastImageCacheTests\n//\n//  Created by Rui Peres on 17/06/2015.\n//  Copyright (c) 2015 Path. All rights reserved.\n//\n\n#import <UIKit/UIKit.h>\n#import <XCTest/XCTest.h>\n\n@interface FastImageCacheTests : XCTestCase\n\n@end\n\n@implementation FastImageCacheTests\n\n- (void)setUp {\n    [super setUp];\n    // Put setup code here. This method is called before the invocation of each test method in the class.\n}\n\n- (void)tearDown {\n    // Put teardown code here. This method is called after the invocation of each test method in the class.\n    [super tearDown];\n}\n\n- (void)testExample {\n    // This is an example of a functional test case.\n    XCTAssert(YES, @\"Pass\");\n}\n\n- (void)testPerformanceExample {\n    // This is an example of a performance test case.\n    [self measureBlock:^{\n        // Put the code you want to measure the time of here.\n    }];\n}\n\n@end\n"
  },
  {
    "path": "FastImageCache/FastImageCacheTests/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>BNDL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2013 Path, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "![Fast Image Cache Logo](https://s3.amazonaws.com/fast-image-cache/readme-resources/logo.png)\n\n---\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n\nFast 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.\n\nA significant burden on performance for graphics-rich applications like [Path](http://www.path.com) 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.\n\n## Table of Contents\n\n- [Version History](#version-history)\n- [What Fast Image Cache Does](#what-fast-image-cache-does)\n- [How Fast Image Cache Works](#how-fast-image-cache-works)\n- [Considerations](#considerations)\n- [Requirements](#requirements)\n- [Getting Started](#getting-started)\n    - [Integrating Fast Image Cache](#integrating-fast-image-cache)\n    - [Initial Configuration](#initial-configuration)\n    - [Requesting Images from the Image Cache](#requesting-images-from-the-image-cache)\n    - [Providing Source Images to the Image Cache](#providing-source-images-to-the-image-cache)\n    - [Canceling Source Image Requests](#canceling-source-image-requests)\n    - [Working with Image Format Families](#working-with-image-format-families)\n- [Documentation](#documentation)\n- [Demo Application](#demo-application)\n- [Contributors](#contributors)\n- [Credits](#credits)\n- [License](#license)\n\n## Version History\n\n- [**1.0**](https://github.com/path/FastImageCache/releases/tag/1.0)   (10/18/2013): Initial release\n- [**1.1**](https://github.com/path/FastImageCache/releases/tag/1.1)   (10/22/2013): Added ARC support and more robust Core Animation byte alignment\n- [**1.2**](https://github.com/path/FastImageCache/releases/tag/1.2)   (10/30/2013): Added support for image format styles and canceling image requests\n- [**1.3**](https://github.com/path/FastImageCache/releases/tag/1.3)   (03/30/2014): Significant bug fixes and performance improvements\n\n## What Fast Image Cache Does\n\n- Stores images of similar sizes and styles together\n- Persists image data to disk\n- Returns images to the user significantly faster than traditional methods\n- Automatically manages cache expiry based on recency of usage\n- Utilizes a model-based approach for storing and retrieving images\n- Allows images to be processed on a per-model basis before being stored into the cache\n\n## How Fast Image Cache Works\n\nIn order to understand how Fast Image Cache works, it's helpful to understand a typical scenario encountered by many applications that work with images.\n\n### The Scenario\n\niOS 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.\n\nLater, 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.\n\n### The Problem\n\nIt 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.<sup>1</sup>\n\nConsider the workflow that occurs when loading an image from disk and displaying it on screen:\n\n1. [`+[UIImage imageWithContentsOfFile:]`](https://developer.apple.com/library/ios/documentation/uikit/reference/UIImage_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006890-CH3-SW12) uses [Image I/O](https://developer.apple.com/library/ios/documentation/graphicsimaging/conceptual/ImageIOGuide/imageio_intro/ikpg_intro.html#//apple_ref/doc/uid/TP40005462-CH201-TPXREF101) to create a [`CGImageRef`](http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CCwQFjAA&url=http%3A%2F%2Fdeveloper.apple.com%2Flibrary%2Fios%2Fdocumentation%2Fgraphicsimaging%2FReference%2FCGImage%2FReference%2Freference.html&ei=fG9XUpX_BqWqigLymIG4BQ&usg=AFQjCNHTelntXU5Gw0BQkQqj9HC5iZibyA&sig2=tLY7PDhyockUVlVFbrzyOQ) from memory-mapped data. At this point, the image has not yet been decoded.\n1. The returned image is assigned to a [`UIImageView`](http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CCwQFjAA&url=http%3A%2F%2Fdeveloper.apple.com%2Flibrary%2Fios%2Fdocumentation%2Fuikit%2Freference%2FUIImageView_Class%2F&ei=VX9YUpGUKcG1iwLN3oHwDg&usg=AFQjCNGJCra_NhnVaXH2_pqIKjIHiNX9zQ&sig2=Lk2CMoN4kO5OzLJYhGh6Uw).\n1. An implicit [`CATransaction`](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CCwQFjAA&url=https%3A%2F%2Fdeveloper.apple.com%2Flibrary%2Fios%2Fdocumentation%2FGraphicsImaging%2FReference%2FCATransaction_class%2F&ei=AINYUsSqIqPfiAKsk4CoBA&usg=AFQjCNG5CarCxgkwdV_br80YDI7UwMTrmA&sig2=aPE_IoQSPUltdCYqARjt9Q) captures these layer tree modifications.\n1. 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: <sup>2</sup>\n    1. Buffers are allocated to manage file IO and decompression operations.\n    1. The file data is read from disk into memory.\n    1. The compressed image data is decoded into its uncompressed bitmap form, which is typically a very CPU-intensive operation.<sup>3</sup>\n    1. The uncompressed bitmap data is then used by Core Animation to render the layer.\n\n**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.\n\n---\n\n<sup>1</sup> `60FPS` ≈ `0.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.\n\n<sup>2</sup> The documentation for [`CALayer`](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CCwQFjAA&url=https%3A%2F%2Fdeveloper.apple.com%2Flibrary%2Fios%2Fdocumentation%2Fgraphicsimaging%2Freference%2FCALayer_class%2FIntroduction%2FIntroduction.html&ei=P29XUpj2LeahiALptICgCQ&usg=AFQjCNGwJuHcQV4593kuookUcvNZYTvx5w&sig2=zi1audY4ZsNE_xLeESVD_Q)'s [`contents`](https://developer.apple.com/library/ios/documentation/graphicsimaging/reference/CALayer_class/Introduction/Introduction.html#//apple_ref/doc/uid/TP40004500-CH1-SW24) 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](https://developer.apple.com/library/prerelease/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/) 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](#byte-alignment). \n\n<sup>3</sup> 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.\n\n### The Solution\n\nFast Image Cache minimizes (or avoids entirely) much of the work described above using a variety of techniques:\n\n#### Mapped Memory\n\nAt the heart of how Fast Image Cache works are image tables. Image tables are similar to [sprite sheets](http://en.wikipedia.org/wiki/Sprite_sheet#Sprites_by_CSS), 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.\n\nImage tables use the [`mmap`](https://developer.apple.com/library/ios/documentation/system/conceptual/manpages_iphoneos/man2/mmap.2.html) system call to directly map file data into memory. No [`memcpy`](https://developer.apple.com/library/ios/documentation/system/conceptual/manpages_iphoneos/man3/memcpy.3.html) occurs. This system call merely creates a mapping between data on disk and a region of memory.\n\nWhen 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`](http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CCwQFjAA&url=http%3A%2F%2Fdeveloper.apple.com%2Flibrary%2Fios%2Fdocumentation%2Fgraphicsimaging%2FReference%2FCGImage%2FReference%2Freference.html&ei=fG9XUpX_BqWqigLymIG4BQ&usg=AFQjCNHTelntXU5Gw0BQkQqj9HC5iZibyA&sig2=tLY7PDhyockUVlVFbrzyOQ) whose backing store **is** the mapped file data is created.\n\nWhen the returned [`CGImageRef`](http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CCwQFjAA&url=http%3A%2F%2Fdeveloper.apple.com%2Flibrary%2Fios%2Fdocumentation%2Fgraphicsimaging%2FReference%2FCGImage%2FReference%2Freference.html&ei=fG9XUpX_BqWqigLymIG4BQ&usg=AFQjCNHTelntXU5Gw0BQkQqj9HC5iZibyA&sig2=tLY7PDhyockUVlVFbrzyOQ) (wrapped into a [`UIImage`](http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CC0QFjAA&url=http%3A%2F%2Fdeveloper.apple.com%2Flibrary%2Fios%2Fdocumentation%2Fuikit%2Freference%2FUIImage_Class%2F&ei=lG9XUtTdJIm9iwKDq4CACA&usg=AFQjCNEa2LN2puQYOfBRVPaEsvsSawOVMg&sig2=0TzbC6wzT5EdynHsDMIEUw)) 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.\n\nIn 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`](https://developer.apple.com/library/ios/documentation/system/conceptual/manpages_iphoneos/man2/mmap.2.html) marshals the drawn image data to disk, so no image buffer is allocated in memory.\n\n#### Uncompressed Image Data\n\nIn 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](#working-with-image-format-families) to perform this decompression exactly once for a collection of similar image formats.\n\nThere 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.\n\n#### Byte Alignment\n\nFor 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`](http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CCwQFjAA&url=http%3A%2F%2Fdeveloper.apple.com%2Flibrary%2Fios%2Fdocumentation%2Fgraphicsimaging%2FReference%2FCGImage%2FReference%2Freference.html&ei=fG9XUpX_BqWqigLymIG4BQ&usg=AFQjCNHTelntXU5Gw0BQkQqj9HC5iZibyA&sig2=tLY7PDhyockUVlVFbrzyOQ). 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.\n\n## Considerations\n\n### Image Table Size\n\nImage 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.\n\nImage tables allocate 4 bytes per pixel, so the maximum space occupied by an image table file can be determined as follows:\n\n`4 bytes per pixel × image width in pixels × image height in pixels × maximum number of entries`\n\nApplications 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.\n\n### Image Table Transience\n\nImage 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.\n\n> **Note**: As a reminder, data stored in a user's caches directory is not backed up to iTunes or iCloud.\n\n### Source Image Persistence\n\nFast Image Cache does not persist the original source images processed by entities to create the image data stored in its image tables.\n\nFor 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.\n\nImage format families can be specified to efficiently make use of a single source image. See [Working with Image Format Families](#working-with-image-format-families) for more information.\n\n### Data Protection\n\nIn 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.\n\nFast 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.\n\n## Requirements\n\nFast Image Cache requires iOS 6.0 or greater and relies on the following frameworks:\n\n- Foundation\n- Core Graphics\n- UIKit\n\n> **Note**: As of version 1.1, Fast Image Cache **does** use ARC.\n\n---\n\nThe `FastImageCacheDemo` Xcode project requires Xcode 5.0 or greater and is configured to deploy against iOS 6.0.\n\n## Getting Started\n\n### Integrating Fast Image Cache\n\n#### CocoaPods\n\nFor easy project integration, Fast Image Cache is available as a [CocoaPod](http://cocoapods.org).\n\n#### Manually\n\n- Clone this repository, or [download the latest archive of `master`](https://github.com/path/FastImageCache/archive/master.zip).\n- From the `FastImageCache` root directory, copy the source files from the inner [`FastImageCache`](./FastImageCache) subdirectory to your Xcode project.\n- Import [`FICImageCache.h`](./FastImageCache/FastImageCache/FastImageCache/FICImageCache.h) wherever you use the image cache.\n- Import [`FICEntity.h`](./FastImageCache/FastImageCache/FastImageCache/FICEntity.h) for each class that conforms to [`FICEntity`](https://s3.amazonaws.com/fast-image-cache/documentation/Protocols/FICEntity.html).\n\n### Initial Configuration\n\nBefore 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.\n\n#### Creating Image Formats\n\nEach 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](#working-with-image-format-families). See [Image Table Size](#image-table-size) for more information about how to determine an appropriate maximum count.\n\n```objective-c\nstatic NSString *XXImageFormatNameUserThumbnailSmall = @\"com.mycompany.myapp.XXImageFormatNameUserThumbnailSmall\";\nstatic NSString *XXImageFormatNameUserThumbnailMedium = @\"com.mycompany.myapp.XXImageFormatNameUserThumbnailMedium\";\nstatic NSString *XXImageFormatFamilyUserThumbnails = @\"com.mycompany.myapp.XXImageFormatFamilyUserThumbnails\";\n\nFICImageFormat *smallUserThumbnailImageFormat = [[FICImageFormat alloc] init];\nsmallUserThumbnailImageFormat.name = XXImageFormatNameUserThumbnailSmall;\nsmallUserThumbnailImageFormat.family = XXImageFormatFamilyUserThumbnails;\nsmallUserThumbnailImageFormat.style = FICImageFormatStyle16BitBGR;\nsmallUserThumbnailImageFormat.imageSize = CGSizeMake(50, 50);\nsmallUserThumbnailImageFormat.maximumCount = 250;\nsmallUserThumbnailImageFormat.devices = FICImageFormatDevicePhone;\nsmallUserThumbnailImageFormat.protectionMode = FICImageFormatProtectionModeNone;\n\nFICImageFormat *mediumUserThumbnailImageFormat = [[FICImageFormat alloc] init];\nmediumUserThumbnailImageFormat.name = XXImageFormatNameUserThumbnailMedium;\nmediumUserThumbnailImageFormat.family = XXImageFormatFamilyUserThumbnails;\nmediumUserThumbnailImageFormat.style = FICImageFormatStyle32BitBGRA;\nmediumUserThumbnailImageFormat.imageSize = CGSizeMake(100, 100);\nmediumUserThumbnailImageFormat.maximumCount = 250;\nmediumUserThumbnailImageFormat.devices = FICImageFormatDevicePhone;\nmediumUserThumbnailImageFormat.protectionMode = FICImageFormatProtectionModeNone;\n\nNSArray *imageFormats = @[smallUserThumbnailImageFormat, mediumUserThumbnailImageFormat];\n```\n\nAn image format's style effectively determines the bit depth of the images stored in an image table. The following styles are currently available:\n\n- 32-bit color plus an alpha component (default)\n- 32-bit color, no alpha component\n- 16-bit color, no alpha component\n- 8-bit grayscale, no alpha component\n\nIf 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.\n\n#### Configuring the Image Cache\n\nOnce 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.\n\n```objective-c\nFICImageCache *sharedImageCache = [FICImageCache sharedImageCache];\nsharedImageCache.delegate = self;\nsharedImageCache.formats = imageFormats;\n```\n\n#### Creating Entities\n\nEntities are objects that conform to the [`FICEntity`](https://s3.amazonaws.com/fast-image-cache/documentation/Protocols/FICEntity.html) 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.\n\n```objective-c\n@interface XXUser : NSObject <FICEntity>\n\n@property (nonatomic, assign, getter = isActive) BOOL active;\n@property (nonatomic, copy) NSString *userID;\n@property (nonatomic, copy) NSURL *userPhotoURL;\n\n@end\n```\n\nHere is an example implementation of the [`FICEntity`](https://s3.amazonaws.com/fast-image-cache/documentation/Protocols/FICEntity.html) protocol.\n\n```objective-c\n- (NSString *)UUID {\n    CFUUIDBytes UUIDBytes = FICUUIDBytesFromMD5HashOfString(_userID);\n    NSString *UUID = FICStringWithUUIDBytes(UUIDBytes);\n\n    return UUID;\n}\n\n- (NSString *)sourceImageUUID {\n    CFUUIDBytes sourceImageUUIDBytes = FICUUIDBytesFromMD5HashOfString([_userPhotoURL absoluteString]);\n    NSString *sourceImageUUID = FICStringWithUUIDBytes(sourceImageUUIDBytes);\n\n    return sourceImageUUID;\n}\n\n- (NSURL *)sourceImageURLWithFormatName:(NSString *)formatName {\n    return _sourceImageURL;\n}\n\n- (FICEntityImageDrawingBlock)drawingBlockForImage:(UIImage *)image withFormatName:(NSString *)formatName {\n    FICEntityImageDrawingBlock drawingBlock = ^(CGContextRef context, CGSize contextSize) {\n        CGRect contextBounds = CGRectZero;\n        contextBounds.size = contextSize;\n        CGContextClearRect(context, contextBounds);\n\n        // Clip medium thumbnails so they have rounded corners\n        if ([formatName isEqualToString:XXImageFormatNameUserThumbnailMedium]) {\n            UIBezierPath clippingPath = [self _clippingPath];\n            [clippingPath addClip];\n        }\n        \n        UIGraphicsPushContext(context);\n        [image drawInRect:contextBounds];\n        UIGraphicsPopContext();\n    };\n    \n    return drawingBlock;\n}\n```\n\nIdeally, an entity's [`UUID`](https://s3.amazonaws.com/fast-image-cache/documentation/Protocols/FICEntity.html#//api/name/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.\n\nAn entity's [`sourceImageUUID`](https://s3.amazonaws.com/fast-image-cache/documentation/Protocols/FICEntity.html#//api/name/sourceImageUUID) *can* change. For example, if a user updates their profile photo, the URL to that photo should change as well. The [`UUID`](https://s3.amazonaws.com/fast-image-cache/documentation/Protocols/FICEntity.html#//api/name/UUID) remains the same and identifies the same user, but the changed profile photo URL will indicate that there is a new source image.\n\n> **Note**: Often, it is best to hash whatever identifiers are being used to define [`UUID`](https://s3.amazonaws.com/fast-image-cache/documentation/Protocols/FICEntity.html#//api/name/UUID) and [`sourceImageUUID`](https://s3.amazonaws.com/fast-image-cache/documentation/Protocols/FICEntity.html#//api/name/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.\n\nWhen 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.\n\nThe 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.\n\n> **Note**: Fast Image Cache does not provide any mechanism for making network requests. This is the responsibility of the image cache's delegate.\n\nFinally, 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.\n\n### Requesting Images from the Image Cache\n\nFast Image Cache works under the on-demand, lazy-loading design pattern common to Cocoa.\n\n```objective-c\nXXUser *user = [self _currentUser];\nNSString *formatName = XXImageFormatNameUserThumbnailSmall;\nFICImageCacheCompletionBlock completionBlock = ^(id <FICEntity> entity, NSString *formatName, UIImage *image) {\n    _imageView.image = image;\n    [_imageView.layer addAnimation:[CATransition animation] forKey:kCATransition];\n};\n\nBOOL imageExists = [sharedImageCache retrieveImageForEntity:user withFormatName:formatName completionBlock:completionBlock];\n    \nif (imageExists == NO) {\n    _imageView.image = [self _userPlaceholderImage];\n}\n```\n\nThere are a few things to note here.\n\n1. 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.\n1. The image cache never returns a [`UIImage`](http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CC0QFjAA&url=http%3A%2F%2Fdeveloper.apple.com%2Flibrary%2Fios%2Fdocumentation%2Fuikit%2Freference%2FUIImage_Class%2F&ei=lG9XUtTdJIm9iwKDq4CACA&usg=AFQjCNEa2LN2puQYOfBRVPaEsvsSawOVMg&sig2=0TzbC6wzT5EdynHsDMIEUw) 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.\n1. [`-retrieveImageForEntity:withFormatName:completionBlock:`](https://s3.amazonaws.com/fast-image-cache/documentation/Classes/FICImageCache.html#//api/name/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:`](https://s3.amazonaws.com/fast-image-cache/documentation/Classes/FICImageCache.html#//api/name/asynchronouslyRetrieveImageForEntity:withFormatName:completionBlock:).\n1. 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.\n\n> **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.\n>\n> See the [`FICImageCache`](https://s3.amazonaws.com/fast-image-cache/documentation/Classes/FICImageCache.html) 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.\n\n### Providing Source Images to the Image Cache\n\nThere are two ways to provide source images to the image cache.\n\n1. **On Demand**: This is the preferred method. The image cache's delegate is responsible for supplying the image cache with source images.\n\n    ```objective-c\n    - (void)imageCache:(FICImageCache *)imageCache wantsSourceImageForEntity:(id<FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(FICImageRequestCompletionBlock)completionBlock {\n        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{\n            // Fetch the desired source image by making a network request\n            NSURL *requestURL = [entity sourceImageURLWithFormatName:formatName];\n            UIImage *sourceImage = [self _sourceImageForURL:requestURL];\n            \n            dispatch_async(dispatch_get_main_queue(), ^{\n                completionBlock(sourceImage);\n            });\n        });\n    }    \n    ```\n    \n    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.\n    \n    > **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.\n\n2. **Manually**: It is possible to manually insert image data into the image cache.\n\n    ```objective-c\n    // Just finished downloading new user photo\n    \n    XXUser *user = [self _currentUser];\n    NSString *formatName = XXImageFormatNameUserThumbnailSmall;\n    FICImageCacheCompletionBlock completionBlock = ^(id <FICEntity> entity, NSString *formatName, UIImage *image) {\n        NSLog(@\"Processed and stored image for entity: %@\", entity);\n    };\n    \n    [sharedImageCache setImage:newUserPhoto forEntity:user withFormatName:formatName completionBlock:completionBlock];\n    ```\n    \n> **Note**: Fast Image Cache does **not** persist source images. See [Source Image Persistence](#source-image-persistence) for more information.\n\n### Canceling Source Image Requests\n\nIf an image request is already in progress, it can be cancelled:\n\n```objective-c\n// We scrolled up far enough that the image we requested in no longer visible; cancel the request\nXXUser *user = [self _currentUser];\nNSString *formatName = XXImageFormatNameUserThumbnailSmall;\n[sharedImageCache cancelImageRetrievalForEntity:user withFormatName:formatName];\n```\n\nWhen 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:\n\n```objective-c\n- (void)imageCache:(FICImageCache *)imageCache cancelImageLoadingForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName {\n    [self _cancelNetworkRequestForSourceImageForEntity:entity withFormatName:formatName];\n}\n```\n\n### Working with Image Format Families\n\nThe 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. \n\n```objective-c\n- (BOOL)imageCache:(FICImageCache *)imageCache shouldProcessAllFormatsInFamily:(NSString *)formatFamily forEntity:(id<FICEntity>)entity {\n    BOOL shouldProcessAllFormats = NO;\n\n    if ([formatFamily isEqualToString:XXImageFormatFamilyUserThumbnails]) {\n        XXUser *user = (XXUser *)entity;\n        shouldProcessAllFormats = user.active;\n    }\n\n    return shouldProcessAllFormats;\n}\n```\n\nThe 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).\n\nFor 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`.\n\n## Documentation\n\nFast Image Cache's header files are fully documented, and [appledoc](http://gentlebytes.com/appledoc/) can be used to generate documentation in various forms, including HTML and Xcode DocSet.\n\nHTML documentation can be [found here](https://s3.amazonaws.com/fast-image-cache/documentation/index.html).\n\n## Demo Application\n\nIncluded 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](#requirements).\n\n> **Note**: The demo application must either be supplied with JPEG images, or the included [`fetch_demo_images.sh`](./FastImageCache/FastImageCacheDemo/fetch_demo_images.sh) script in the [`FastImageCacheDemo`](./FastImageCache/FastImageCacheDemo) directory must be run.\n\n### Video\n\n<p align=\"center\">\n    <a href=\"https://s3.amazonaws.com/fast-image-cache/readme-resources/demo-app-video.html\"><img src=\"https://s3.amazonaws.com/fast-image-cache/readme-resources/demo-app-video-placeholder.png\" alt=\"Fast Image Cache Demo App Video\"></a>\n</p>\n\n> **Note**: In this demo video, the first demonstrated method is the conventional approach. The second method is using image tables.\n\n### Statistics\n\nThe following statistics were measured from a run of the demo application:\n\n| Method           | Scrolling Performance   | Disk Usage   | [RPRVT](http://www.mikeash.com/pyblog/friday-qa-2009-06-19-mac-os-x-process-memory-statistics.html)<sup>1</sup>\n| ---------------- |:-----------------------:|:------------:|:-----------------------------:|\n| Conventional     | `~35FPS`                | `568KB`      | `2.40MB`: `1.06MB` + `1.34MB` |\n| Fast Image Cache | `~59FPS`                | `2.2MB`      | `1.15MB`: `1.06MB` + `0.09MB` |\n\nThe takeaway is that Fast Image Cache sacrifices disk usage to achieve a faster framerate and overall less memory usage.\n\n---\n<sup>1</sup> 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.\n\n## Contributors\n\n<a href=\"https://twitter.com/mallorypaine\" target=\"_blank\"><img src=\"http://www.gravatar.com/avatar/76db5d6bdcb64ac9e86e6a521ab57f03.jpg?s=85\" alt=\"Mallory Paine\"></a>  \n**Mallory Paine** — Author and Original API Design  \n<a href=\"https://twitter.com/mallorypaine\" target=\"_blank\">@mallorypaine</a>\n\n---\n\n<a href=\"https://twitter.com/LucasTizma\" target=\"_blank\"><img src=\"http://www.gravatar.com/avatar/2005b2ed368913850076bb52cee79713.jpg?s=85\" alt=\"Michael Potter\"></a>  \n**Michael Potter** — Documentation and API Refactoring  \n<a href=\"https://twitter.com/LucasTizma\" target=\"_blank\">@LucasTizma</a>\n\n## Credits\n\n- All [demo application](#demo-application) photos were taken from [morgueFile](http://www.morguefile.com) and are used according to the [morgueFile license](http://www.morguefile.com/license/full).\n- Fast Image Cache logo illustration by the amazing [Jake Mix](https://twitter.com/jakemix).\n\n## License\n\nFast Image Cache is made available under the [MIT license](http://opensource.org/licenses/MIT):\n\n<pre>\nThe MIT License (MIT)\n\nCopyright (c) 2013 Path, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n</pre>\n"
  }
]