Showing preview only (266K chars total). Download the full file or copy to clipboard to get everything.
Repository: path/FastImageCache
Branch: master
Commit: 2615d275abe6
Files: 43
Total size: 251.2 KB
Directory structure:
gitextract_frhwcarf/
├── .gitignore
├── FastImageCache/
│ ├── FastImageCache/
│ │ ├── FastImageCache/
│ │ │ ├── FICEntity.h
│ │ │ ├── FICImageCache+FICErrorLogging.h
│ │ │ ├── FICImageCache.h
│ │ │ ├── FICImageCache.m
│ │ │ ├── FICImageFormat.h
│ │ │ ├── FICImageFormat.m
│ │ │ ├── FICImageTable.h
│ │ │ ├── FICImageTable.m
│ │ │ ├── FICImageTableChunk.h
│ │ │ ├── FICImageTableChunk.m
│ │ │ ├── FICImageTableEntry.h
│ │ │ ├── FICImageTableEntry.m
│ │ │ ├── FICImports.h
│ │ │ ├── FICUtilities.h
│ │ │ └── FICUtilities.m
│ │ ├── FastImageCache.h
│ │ └── Info.plist
│ ├── FastImageCache.xcodeproj/
│ │ ├── project.pbxproj
│ │ └── xcshareddata/
│ │ └── xcschemes/
│ │ └── FastImageCache.xcscheme
│ ├── FastImageCacheDemo/
│ │ ├── Assets.xcassets/
│ │ │ ├── Icon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ └── Launch Image.launchimage/
│ │ │ └── Contents.json
│ │ ├── Classes/
│ │ │ ├── FICDAppDelegate.h
│ │ │ ├── FICDAppDelegate.m
│ │ │ ├── FICDFullscreenPhotoDisplayController.h
│ │ │ ├── FICDFullscreenPhotoDisplayController.m
│ │ │ ├── FICDPhoto.h
│ │ │ ├── FICDPhoto.m
│ │ │ ├── FICDPhotosTableViewCell.h
│ │ │ ├── FICDPhotosTableViewCell.m
│ │ │ ├── FICDTableView.h
│ │ │ ├── FICDTableView.m
│ │ │ ├── FICDViewController.h
│ │ │ └── FICDViewController.m
│ │ ├── Demo Images/
│ │ │ └── README
│ │ ├── FastImageCacheDemo-Prefix.pch
│ │ ├── Info.plist
│ │ ├── fetch_demo_images.sh
│ │ └── main.m
│ └── FastImageCacheTests/
│ ├── FastImageCacheTests.m
│ └── Info.plist
├── LICENSE
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Mac OS X Finder
.DS_Store
# Xcode
build/*
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
*.xcworkspace
!default.xcworkspace
xcuserdata
profile
*.moved-aside
DerivedData
# Demo Images
FastImageCache/FastImageCacheDemo/Demo Images/*.jpg
Carthage
================================================
FILE: FastImageCache/FastImageCache/FastImageCache/FICEntity.h
================================================
//
// FICEntity.h
// FastImageCache
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICImports.h"
@class FICImageFormat;
NS_ASSUME_NONNULL_BEGIN
typedef void (^FICEntityImageDrawingBlock)(CGContextRef context, CGSize contextSize);
/**
`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>`.
*/
@protocol FICEntity <NSObject>
@required
/**
A string that uniquely identifies this entity.
@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
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.
*/
@property (nonatomic, copy, readonly) NSString *fic_UUID;
/**
A string that uniquely identifies an entity's source image.
@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
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).
*/
@property (nonatomic, copy, readonly) NSString *fic_sourceImageUUID;
/**
Returns the source image URL associated with a specific format name.
@param formatName The name of the image format that identifies which image table is requesting the source image.
@return A URL representing the requested source image.
@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
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
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
URL returned by this method to key image cache requests. No network or file operations are performed by the image cache.
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
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.
@see FICImageFormat
@see [FICImageCacheDelegate imageCache:wantsSourceImageForEntity:withFormatName:completionBlock:]
*/
- (nullable NSURL *)fic_sourceImageURLWithFormatName:(NSString *)formatName;
/**
Returns the drawing block for a specific image and format name.
@param image The cached image that represents this entity.
@param formatName The name of the image format that identifies which image table is requesting the source image.
@return The drawing block used to draw the image data to be stored in the image table.
The drawing block's type is defined as follows:
typedef void (^FICEntityImageDrawingBlock)(CGContextRef context, CGSize contextSize)
@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
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),
you may use this block to do so.
@note This block will always be called from the serial dispatch queue used by the image cache.
*/
- (nullable FICEntityImageDrawingBlock)fic_drawingBlockForImage:(UIImage *)image withFormatName:(NSString *)formatName;
@optional
/**
Returns the image for a format
@param format The image format that identifies which image table is requesting the source image.
*/
- (nullable UIImage *)fic_imageForFormat:(FICImageFormat *)format;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: FastImageCache/FastImageCache/FastImageCache/FICImageCache+FICErrorLogging.h
================================================
//
// FICImageCache+FICErrorLogging.h
// FastImageCache
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICImageCache.h"
/**
This category on `<FICImageCache>` simply exposes its private logging mechanism to other classes.
*/
@interface FICImageCache (FICErrorLogging)
///-----------------------------
/// @name Logging Error Messages
///-----------------------------
/**
Passes an error message to the image cache.
@param message A string representing the error message.
@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
message.
@see [FICImageCacheDelegate imageCache:errorDidOccurWithMessage:]
*/
- (void)_logMessage:(NSString *)message;
@end
================================================
FILE: FastImageCache/FastImageCache/FastImageCache/FICImageCache.h
================================================
//
// FICImageCache.h
// FastImageCache
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICImports.h"
#import "FICImageFormat.h"
#import "FICEntity.h"
@protocol FICEntity;
@protocol FICImageCacheDelegate;
typedef void (^FICImageCacheCompletionBlock)(id <FICEntity> _Nullable entity, NSString * _Nonnull formatName, UIImage * _Nullable image);
typedef void (^FICImageRequestCompletionBlock)(UIImage * _Nullable sourceImage);
NS_ASSUME_NONNULL_BEGIN
/**
`FICImageCache` is the primary class for managing and interacting with the image cache. Applications using the image cache create one or more `<FICImageFormat>`
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
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
image format name.
*/
@interface FICImageCache : NSObject
/**
The namespace of the image cache.
@discussion Namespace is responsible for isolation of dirrerent image cache instances on file system level. Namespace should be unique across application.
*/
@property (readonly, nonatomic) NSString *nameSpace;
///----------------------------
/// @name Managing the Delegate
///----------------------------
/**
The delegate of the image cache.
@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
family for a particular entity be processed. Any errors that occur in the image cache are also communicated back to the delegate.
*/
@property (nonatomic, weak) id <FICImageCacheDelegate> delegate;
///---------------------------------------
/// @name Creating Image Cache instances
///---------------------------------------
/**
Returns new image cache.
@return A new instance of `FICImageCache`.
@param nameSpace The namespace that uniquely identifies current image cahce entity. If no nameSpace given, default namespace will be used.
@note Fast Image Cache can either be used as a singleton for convenience or can exist as multiple instances.
However, all instances of `FICImageCache` will make use same dispatch queue. To separate location on disk for storing image tables namespaces are used.
@see [FICImageCache dispatchQueue]
*/
- (instancetype)initWithNameSpace:(NSString *)nameSpace;
///---------------------------------------
/// @name Accessing the Shared Image Cache
///---------------------------------------
/**
Returns the shared image cache.
@return A shared instance of `FICImageCache`.
@note Shared instance always binded to default namespace.
@see [FICImageCache dispatchQueue]
*/
+ (instancetype)sharedImageCache;
/**
Returns the shared dispatch queue used by all instances of `FICImageCache`.
@return A generic, shared dispatch queue of type `dispatch_queue_t`.
@note All instances of `FICImageCache` make use a single, shared dispatch queue to do their work.
*/
+ (dispatch_queue_t)dispatchQueue;
///---------------------------------
/// @name Working with Image Formats
///---------------------------------
/**
Sets the image formats to be used by the image cache.
@param formats An array of `<FICImageFormat>` objects.
@note Once the image formats have been set, subsequent calls to this method will do nothing.
*/
- (void)setFormats:(NSArray<FICImageFormat*> *)formats;
/**
Returns an image format previously associated with the image cache.
@param formatName The name of the image format to return.
@return An image format with the name `formatName` or `nil` if no format with that name exists.
*/
- (nullable FICImageFormat *)formatWithName:(NSString *)formatName;
/**
Returns all the image formats of the same family previously associated with the image cache.
@param family The name of the family of image formats to return.
@return An array of `<FICImageFormat>` objects whose family is `family` or `nil` if no format belongs to that family.
*/
- (nullable NSArray<FICImageFormat *> *)formatsWithFamily:(NSString *)family;
///-----------------------------------------------
/// @name Storing, Retrieving, and Deleting Images
///-----------------------------------------------
/**
Manually sets the the image to be used by the image cache for a particular entity and format name.
@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
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.
After the image has been processed by the image cache, the completion block is called asynchronously on the main queue.
@param image The image to store in the image cache.
@param entity The entity that uniquely identifies the source image.
@param formatName The format name that uniquely identifies which image table to look in for the cached image.
@param completionBlock The completion block that is called after the image has been processed or if an error occurs.
The completion block's type is defined as follows:
typedef void (^FICImageCacheCompletionBlock)(id <FICEntity> entity, NSString *formatName, UIImage *image)
*/
- (void)setImage:(UIImage *)image forEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(nullable FICImageCacheCompletionBlock)completionBlock;
/**
Attempts to synchronously retrieve an image from the image cache.
@param entity The entity that uniquely identifies the source image.
@param formatName The format name that uniquely identifies which image table to look in for the cached image. Must not be nil.
@param completionBlock The completion block that is called when the requested image is available or if an error occurs.
The completion block's type is defined as follows:
typedef void (^FICImageCacheCompletionBlock)(id <FICEntity> entity, NSString *formatName, UIImage *image)
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
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.
@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.
@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
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
completion block.
@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
<[FICImageCacheDelegate imageCache:errorDidOccurWithMessage:]> for information about being notified when errors occur.
*/
- (BOOL)retrieveImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(nullable FICImageCacheCompletionBlock)completionBlock;
/**
Asynchronously retrieves an image from the image cache.
@param entity The entity that uniquely identifies the source image.
@param formatName The format name that uniquely identifies which image table to look in for the cached image. Must not be nil.
@param completionBlock The completion block that is called when the requested image is available or if an error occurs.
The completion block's type is defined as follows:
typedef void (^FICImageCacheCompletionBlock)(id <FICEntity> entity, NSString *formatName, UIImage *image)
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
image cache.
@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.
@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
<[FICImageCacheDelegate imageCache:errorDidOccurWithMessage:]> for information about being notified when errors occur.
@see [FICImageCache retrieveImageForEntity:withFormatName:completionBlock:]
*/
- (BOOL)asynchronouslyRetrieveImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(nullable FICImageCacheCompletionBlock)completionBlock;
/**
Deletes an image from the image cache.
@param entity The entity that uniquely identifies the source image.
@param formatName The format name that uniquely identifies which image table to look in for the cached image.
*/
- (void)deleteImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName;
///-------------------------------
/// @name Canceling Image Requests
///-------------------------------
/**
Cancels an active request for an image from the image cache.
@param entity The entity that uniquely identifies the source image.
@param formatName The format name that uniquely identifies which image table to look in for the cached image.
@discussion After this method is called, the completion block of the <[FICImageCacheDelegate imageCache:wantsSourceImageForEntity:withFormatName:completionBlock:]> delegate
method for the corresponding entity, if called, does nothing.
*/
- (void)cancelImageRetrievalForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName;
///-----------------------------------
/// @name Checking for Image Existence
///-----------------------------------
/**
Returns whether or not an image exists in the image cache.
@param entity The entity that uniquely identifies the source image.
@param formatName The format name that uniquely identifies which image table to look in for the cached image.
@return `YES` if an image exists in the image cache for a given entity and format name. Otherwise, `NO`.
*/
- (BOOL)imageExistsForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName;
///--------------------------------
/// @name Resetting the Image Cache
///--------------------------------
/**
Resets the image cache by deleting all image tables and their contents.
@note Resetting an image cache does not reset its image formats.
*/
- (void)reset;
@end
/**
`FICImageCacheDelegate` defines the required and optional actions that an image cache's delegate can perform.
*/
@protocol FICImageCacheDelegate <NSObject>
@optional
/**
This method is called on the delegate when the image cache needs a source image.
@param imageCache The image cache that is requesting the source image.
@param entity The entity that uniquely identifies the source image.
@param formatName The format name that uniquely identifies which image table to look in for the cached image.
@param completionBlock The completion block that the receiver must call when it has a source image ready.
The completion block's type is defined as follows:
typedef void (^FICImageRequestCompletionBlock)(UIImage *sourceImage)
The completion block must always be called on the main thread.
@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
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
implementation is expected to call the completion block once an image is available.
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
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
the URL returned by <[FICEntity sourceImageURLWithFormatName:]>, deserializing the image data when the request completes, and finally calling this method's completion
block to provide the image cache with the source image.
*/
- (void)imageCache:(FICImageCache *)imageCache wantsSourceImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(nullable FICImageRequestCompletionBlock)completionBlock;
/**
This method is called on the delegate when the image cache has received an image retrieval cancellation request.
@param imageCache The image cache that has received the image retrieval cancellation request.
@param entity The entity that uniquely identifies the source image.
@param formatName The format name that uniquely identifies which image table to look in for the cached image.
@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
delegate's responsibility to cancel whatever logic is it performing to provide a source image to the cache (e.g., a network request).
@see [FICImageCache cancelImageRetrievalForEntity:withFormatName:]
*/
- (void)imageCache:(FICImageCache *)imageCache cancelImageLoadingForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName;
/**
This method is called on the delegate to determine whether or not all formats in a family should be processed right now.
@note If this method is not implemented by the delegate, the default value is `YES`.
@param imageCache The image cache that is requesting the source image.
@param formatFamily The name of a format family.
@param entity The entity that uniquely identifies the source image.
@return `YES` if all formats in a format family should be processed. Otherwise, `NO`.
@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,
typically the delegate will want to return `YES` here so that other formats in the same family can be processed.
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
want every thumbnail size and style is updated with the new source image.
*/
- (BOOL)imageCache:(FICImageCache *)imageCache shouldProcessAllFormatsInFamily:(NSString *)formatFamily forEntity:(id <FICEntity>)entity;
/**
This method is called on the delegate whenever the image cache has an error message to log.
@param imageCache The image cache that is requesting the source image.
@param errorMessage The error message generated by the image cache.
@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.
*/
- (void)imageCache:(FICImageCache *)imageCache errorDidOccurWithMessage:(NSString *)errorMessage;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: FastImageCache/FastImageCache/FastImageCache/FICImageCache.m
================================================
//
// FICImageCache.m
// FastImageCache
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICImageCache.h"
#import "FICEntity.h"
#import "FICImageTable.h"
#import "FICImageFormat.h"
#pragma mark Internal Definitions
static void _FICAddCompletionBlockForEntity(NSString *formatName, NSMutableDictionary *entityRequestsDictionary, id <FICEntity> entity, FICImageCacheCompletionBlock completionBlock);
static NSString *const FICImageCacheFormatKey = @"FICImageCacheFormatKey";
static NSString *const FICImageCacheCompletionBlocksKey = @"FICImageCacheCompletionBlocksKey";
static NSString *const FICImageCacheEntityKey = @"FICImageCacheEntityKey";
#pragma mark - Class Extension
@interface FICImageCache () {
NSMutableDictionary *_formats;
NSMutableDictionary *_imageTables;
NSMutableDictionary *_requests;
BOOL _delegateImplementsWantsSourceImageForEntityWithFormatNameCompletionBlock;
BOOL _delegateImplementsShouldProcessAllFormatsInFamilyForEntity;
BOOL _delegateImplementsErrorDidOccurWithMessage;
BOOL _delegateImplementsCancelImageLoadingForEntityWithFormatName;
}
@end
#pragma mark
@implementation FICImageCache
@synthesize delegate = _delegate;
#pragma mark - Property Accessors
- (void)setDelegate:(id<FICImageCacheDelegate>)delegate {
if (delegate != _delegate) {
_delegate = delegate;
_delegateImplementsWantsSourceImageForEntityWithFormatNameCompletionBlock = [_delegate respondsToSelector:@selector(imageCache:wantsSourceImageForEntity:withFormatName:completionBlock:)];
_delegateImplementsShouldProcessAllFormatsInFamilyForEntity = [_delegate respondsToSelector:@selector(imageCache:shouldProcessAllFormatsInFamily:forEntity:)];
_delegateImplementsErrorDidOccurWithMessage = [_delegate respondsToSelector:@selector(imageCache:errorDidOccurWithMessage:)];
_delegateImplementsCancelImageLoadingForEntityWithFormatName = [_delegate respondsToSelector:@selector(imageCache:cancelImageLoadingForEntity:withFormatName:)];
}
}
#pragma mark - Object Lifecycle
+ (instancetype)sharedImageCache {
static dispatch_once_t onceToken;
static FICImageCache *__imageCache = nil;
dispatch_once(&onceToken, ^{
__imageCache = [[[self class] alloc] init];
});
return __imageCache;
}
+ (dispatch_queue_t)dispatchQueue {
static dispatch_queue_t __imageCacheDispatchQueue = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__imageCacheDispatchQueue = dispatch_queue_create("com.path.FastImageCacheQueue", NULL);
});
return __imageCacheDispatchQueue;
}
- (instancetype)init {
return [self initWithNameSpace:@"FICDefaultNamespace"];
}
- (instancetype)initWithNameSpace:(NSString *)nameSpace {
self = [super init];
if (self) {
_formats = [[NSMutableDictionary alloc] init];
_imageTables = [[NSMutableDictionary alloc] init];
_requests = [[NSMutableDictionary alloc] init];
_nameSpace = nameSpace;
}
return self;
}
#pragma mark - Working with Formats
- (void)setFormats:(NSArray *)formats {
if ([_formats count] > 0) {
[self _logMessage:[NSString stringWithFormat:@"*** FIC Error: %s FICImageCache has already been configured with its image formats.", __PRETTY_FUNCTION__]];
} else {
NSMutableSet *imageTableFiles = [NSMutableSet set];
FICImageFormatDevices currentDevice = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad ? FICImageFormatDevicePad : FICImageFormatDevicePhone;
for (FICImageFormat *imageFormat in formats) {
NSString *formatName = [imageFormat name];
FICImageFormatDevices devices = [imageFormat devices];
if (devices & currentDevice) {
// Only initialize an image table for this format if it is needed on the current device.
FICImageTable *imageTable = [[FICImageTable alloc] initWithFormat:imageFormat imageCache:self];
[_imageTables setObject:imageTable forKey:formatName];
[_formats setObject:imageFormat forKey:formatName];
[imageTableFiles addObject:[[imageTable tableFilePath] lastPathComponent]];
[imageTableFiles addObject:[[imageTable metadataFilePath] lastPathComponent]];
}
}
// Remove any extraneous files in the image tables directory
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *directoryPath = [FICImageTable directoryPath];
if (self.nameSpace) {
directoryPath = [directoryPath stringByAppendingPathComponent:self.nameSpace];
}
NSArray *fileNames = [fileManager contentsOfDirectoryAtPath:directoryPath error:nil];
for (NSString *fileName in fileNames) {
if ([imageTableFiles containsObject:fileName] == NO) {
// This is an extraneous file, which is no longer needed.
NSString* filePath = [directoryPath stringByAppendingPathComponent:fileName];
[fileManager removeItemAtPath:filePath error:nil];
}
}
}
}
- (FICImageFormat *)formatWithName:(NSString *)formatName {
return [_formats objectForKey:formatName];
}
- (NSArray *)formatsWithFamily:(NSString *)family {
NSMutableArray *formats = nil;
for (FICImageFormat *format in [_formats allValues]) {
if ([[format family] isEqualToString:family]) {
if (formats == nil) {
formats = [NSMutableArray array];
}
[formats addObject:format];
}
}
return [formats copy];
}
#pragma mark - Retrieving Images
- (BOOL)retrieveImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(FICImageCacheCompletionBlock)completionBlock {
return [self _retrieveImageForEntity:entity withFormatName:formatName loadSynchronously:YES completionBlock:completionBlock];
}
- (BOOL)asynchronouslyRetrieveImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(FICImageCacheCompletionBlock)completionBlock {
return [self _retrieveImageForEntity:entity withFormatName:formatName loadSynchronously:NO completionBlock:completionBlock];
}
- (BOOL)_retrieveImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName loadSynchronously:(BOOL)loadSynchronously completionBlock:(FICImageCacheCompletionBlock)completionBlock {
NSParameterAssert(formatName);
BOOL imageExists = NO;
FICImageTable *imageTable = [_imageTables objectForKey:formatName];
NSString *entityUUID = [entity fic_UUID];
NSString *sourceImageUUID = [entity fic_sourceImageUUID];
if (loadSynchronously == NO && [imageTable entryExistsForEntityUUID:entityUUID sourceImageUUID:sourceImageUUID]) {
imageExists = YES;
dispatch_async([FICImageCache dispatchQueue], ^{
UIImage *image = [imageTable newImageForEntityUUID:entityUUID sourceImageUUID:sourceImageUUID preheatData:YES];
if (completionBlock != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(entity, formatName, image);
});
}
});
} else {
UIImage *image = [imageTable newImageForEntityUUID:entityUUID sourceImageUUID:sourceImageUUID preheatData:NO];
imageExists = image != nil;
dispatch_block_t completionBlockCallingBlock = ^{
if (completionBlock != nil) {
if (loadSynchronously) {
completionBlock(entity, formatName, image);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(entity, formatName, image);
});
}
}
};
if (image == nil) {
// No image for this UUID exists in the image table. We'll need to ask the delegate to retrieve the source asset.
NSURL *sourceImageURL = [entity fic_sourceImageURLWithFormatName:formatName];
if (sourceImageURL != nil) {
// We check to see if this image is already being fetched.
BOOL needsToFetch = NO;
@synchronized (_requests) {
NSMutableDictionary *requestDictionary = [_requests objectForKey:sourceImageURL];
if (requestDictionary == nil) {
// If we're here, then we aren't currently fetching this image.
requestDictionary = [NSMutableDictionary dictionary];
[_requests setObject:requestDictionary forKey:sourceImageURL];
needsToFetch = YES;
}
_FICAddCompletionBlockForEntity(formatName, requestDictionary, entity, completionBlock);
}
if (needsToFetch) {
@autoreleasepool {
UIImage *image;
if ([entity respondsToSelector:@selector(fic_imageForFormat:)]){
FICImageFormat *format = [self formatWithName:formatName];
image = [entity fic_imageForFormat:format];
}
if (image){
[self _imageDidLoad:image forURL:sourceImageURL];
} else if (_delegateImplementsWantsSourceImageForEntityWithFormatNameCompletionBlock){
[_delegate imageCache:self wantsSourceImageForEntity:entity withFormatName:formatName completionBlock:^(UIImage *sourceImage) {
[self _imageDidLoad:sourceImage forURL:sourceImageURL];
}];
}
}
}
} else {
NSString *message = [NSString stringWithFormat:@"*** FIC Error: %s entity %@ returned a nil source image URL for image format %@.", __PRETTY_FUNCTION__, entity, formatName];
[self _logMessage:message];
completionBlockCallingBlock();
}
} else {
completionBlockCallingBlock();
}
}
return imageExists;
}
- (void)_imageDidLoad:(UIImage *)image forURL:(NSURL *)URL {
NSDictionary *requestDictionary;
@synchronized (_requests) {
requestDictionary = [_requests objectForKey:URL];
[_requests removeObjectForKey:URL];
// Now safe to use requestsDictionary outside the lock, because we've taken ownership from _requests
}
if (requestDictionary != nil) {
for (NSMutableDictionary *entityDictionary in [requestDictionary allValues]) {
id <FICEntity> entity = [entityDictionary objectForKey:FICImageCacheEntityKey];
NSString *formatName = [entityDictionary objectForKey:FICImageCacheFormatKey];
NSDictionary *completionBlocksDictionary = [entityDictionary objectForKey:FICImageCacheCompletionBlocksKey];
if (image != nil){
[self _processImage:image forEntity:entity completionBlocksDictionary:completionBlocksDictionary];
} else {
NSArray *completionBlocks = [completionBlocksDictionary objectForKey:formatName];
if (completionBlocks != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
for (FICImageCacheCompletionBlock completionBlock in completionBlocks) {
completionBlock(entity, formatName, nil);
}
});
}
}
}
}
}
static void _FICAddCompletionBlockForEntity(NSString *formatName, NSMutableDictionary *entityRequestsDictionary, id <FICEntity> entity, FICImageCacheCompletionBlock completionBlock) {
NSString *entityUUID = [entity fic_UUID];
NSMutableDictionary *requestDictionary = [entityRequestsDictionary objectForKey:entityUUID];
NSMutableDictionary *completionBlocks = nil;
if (requestDictionary == nil) {
// This is the first time we're dealing with this particular entity for this URL request.
requestDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:entity, FICImageCacheEntityKey, nil];
[entityRequestsDictionary setObject:requestDictionary forKey:entityUUID];
[requestDictionary setObject:formatName forKey:FICImageCacheFormatKey];
// Dictionary where keys are imageFormats, and each value is an array of the completion blocks for the requests for this
// URL at the specified format.
completionBlocks = [NSMutableDictionary dictionary];
[requestDictionary setObject:completionBlocks forKey:FICImageCacheCompletionBlocksKey];
} else {
// We already have a request dictionary for this entity, so we just need to append a completion block.
completionBlocks = [requestDictionary objectForKey:FICImageCacheCompletionBlocksKey];
}
if (completionBlock != nil) {
NSMutableArray *blocksArray = [completionBlocks objectForKey:formatName];
if (blocksArray == nil) {
blocksArray = [NSMutableArray array];
[completionBlocks setObject:blocksArray forKey:formatName];
}
FICImageCacheCompletionBlock completionBlockCopy = [completionBlock copy];
[blocksArray addObject:completionBlockCopy];
}
}
#pragma mark - Storing Images
- (void)setImage:(UIImage *)image forEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(FICImageCacheCompletionBlock)completionBlock {
if (image != nil && entity != nil) {
NSDictionary *completionBlocksDictionary = nil;
if (completionBlock != nil) {
completionBlocksDictionary = [NSDictionary dictionaryWithObject:[NSArray arrayWithObject:[completionBlock copy]] forKey:formatName];
}
NSString *entityUUID = [entity fic_UUID];
FICImageTable *imageTable = [_imageTables objectForKey:formatName];
if (imageTable) {
[imageTable deleteEntryForEntityUUID:entityUUID];
[self _processImage:image forEntity:entity completionBlocksDictionary:completionBlocksDictionary];
} else {
[self _logMessage:[NSString stringWithFormat:@"*** FIC Error: %s Couldn't find image table with format name %@", __PRETTY_FUNCTION__, formatName]];
}
}
}
- (void)_processImage:(UIImage *)image forEntity:(id <FICEntity>)entity completionBlocksDictionary:(NSDictionary *)completionBlocksDictionary {
for (NSString *formatToProcess in [self formatsToProcessForCompletionBlocks:completionBlocksDictionary
entity:entity]) {
FICImageTable *imageTable = [_imageTables objectForKey:formatToProcess];
NSArray *completionBlocks = [completionBlocksDictionary objectForKey:formatToProcess];
[self _processImage:image forEntity:entity imageTable:imageTable completionBlocks:completionBlocks];
}
}
- (void)_processImage:(UIImage *)image forEntity:(id <FICEntity>)entity imageTable:(FICImageTable *)imageTable completionBlocks:(NSArray *)completionBlocks {
if (imageTable != nil) {
if ([entity fic_UUID] == nil) {
[self _logMessage:[NSString stringWithFormat:@"*** FIC Error: %s entity %@ is missing its UUID.", __PRETTY_FUNCTION__, entity]];
return;
}
if ([entity fic_sourceImageUUID] == nil) {
[self _logMessage:[NSString stringWithFormat:@"*** FIC Error: %s entity %@ is missing its source image UUID.", __PRETTY_FUNCTION__, entity]];
return;
}
NSString *entityUUID = [entity fic_UUID];
NSString *sourceImageUUID = [entity fic_sourceImageUUID];
FICImageFormat *imageFormat = [imageTable imageFormat];
NSString *imageFormatName = [imageFormat name];
FICEntityImageDrawingBlock imageDrawingBlock = [entity fic_drawingBlockForImage:image withFormatName:imageFormatName];
dispatch_async([FICImageCache dispatchQueue], ^{
[imageTable setEntryForEntityUUID:entityUUID sourceImageUUID:sourceImageUUID imageDrawingBlock:imageDrawingBlock];
UIImage *resultImage = [imageTable newImageForEntityUUID:entityUUID sourceImageUUID:sourceImageUUID preheatData:NO];
if (completionBlocks != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *formatName = [[imageTable imageFormat] name];
for (FICImageCacheCompletionBlock completionBlock in completionBlocks) {
completionBlock(entity, formatName, resultImage);
}
});
}
});
}
}
- (NSSet *)formatsToProcessForCompletionBlocks:(NSDictionary *)completionBlocksDictionary entity:(id <FICEntity>)entity {
// At the very least, we must process all formats with pending completion blocks
NSMutableSet *formatsToProcess = [NSMutableSet setWithArray:completionBlocksDictionary.allKeys];
// Get the list of format families included by the formats we have to process
NSMutableSet *families;
for (NSString *formatToProcess in formatsToProcess) {
FICImageTable *imageTable = _imageTables[formatToProcess];
FICImageFormat *imageFormat = imageTable.imageFormat;
NSString *tableFormatFamily = imageFormat.family;
if (tableFormatFamily) {
if (!families) {
families = [NSMutableSet set];
}
[families addObject:tableFormatFamily];
}
}
// The delegate can override the list of families to process
if (_delegateImplementsShouldProcessAllFormatsInFamilyForEntity) {
[families minusSet:[families objectsPassingTest:^BOOL(NSString *familyName, BOOL *stop) {
return ![_delegate imageCache:self shouldProcessAllFormatsInFamily:familyName forEntity:entity];
}]];
}
// Ensure that all formats from all of those families are included in the list
if (families.count) {
for (FICImageTable *table in _imageTables.allValues) {
FICImageFormat *imageFormat = table.imageFormat;
NSString *imageFormatName = imageFormat.name;
// If we're already processing this format, keep looking
if ([formatsToProcess containsObject:imageFormatName]) {
continue;
}
// If this format isn't included in any referenced family, keep looking
if (![families containsObject:imageFormat.family]) {
continue;
}
// If the image already exists, keep going
if ([table entryExistsForEntityUUID:entity.fic_UUID sourceImageUUID:entity.fic_sourceImageUUID]) {
continue;
}
[formatsToProcess addObject:imageFormatName];
}
}
return formatsToProcess;
}
#pragma mark - Checking for Image Existence
- (BOOL)imageExistsForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName {
FICImageTable *imageTable = [_imageTables objectForKey:formatName];
NSString *entityUUID = [entity fic_UUID];
NSString *sourceImageUUID = [entity fic_sourceImageUUID];
BOOL imageExists = [imageTable entryExistsForEntityUUID:entityUUID sourceImageUUID:sourceImageUUID];
return imageExists;
}
#pragma mark - Invalidating Image Data
- (void)deleteImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName {
FICImageTable *imageTable = [_imageTables objectForKey:formatName];
NSString *entityUUID = [entity fic_UUID];
[imageTable deleteEntryForEntityUUID:entityUUID];
}
- (void)cancelImageRetrievalForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName {
NSURL *sourceImageURL = [entity fic_sourceImageURLWithFormatName:formatName];
NSString *entityUUID = [entity fic_UUID];
BOOL cancelImageLoadingForEntity = NO;
@synchronized (_requests) {
NSMutableDictionary *requestDictionary = [_requests objectForKey:sourceImageURL];
if (requestDictionary) {
NSMutableDictionary *entityRequestsDictionary = [requestDictionary objectForKey:entityUUID];
if (entityRequestsDictionary) {
NSMutableDictionary *completionBlocksDictionary = [entityRequestsDictionary objectForKey:FICImageCacheCompletionBlocksKey];
[completionBlocksDictionary removeObjectForKey:formatName];
if ([completionBlocksDictionary count] == 0) {
[requestDictionary removeObjectForKey:entityUUID];
}
if ([requestDictionary count] == 0) {
[_requests removeObjectForKey:sourceImageURL];
cancelImageLoadingForEntity = YES;
}
}
}
}
if (cancelImageLoadingForEntity && _delegateImplementsCancelImageLoadingForEntityWithFormatName) {
[_delegate imageCache:self cancelImageLoadingForEntity:entity withFormatName:formatName];
}
}
- (void)reset {
for (FICImageTable *imageTable in [_imageTables allValues]) {
dispatch_async([[self class] dispatchQueue], ^{
[imageTable reset];
});
}
}
#pragma mark - Logging Errors
- (void)_logMessage:(NSString *)message {
if (_delegateImplementsErrorDidOccurWithMessage) {
[_delegate imageCache:self errorDidOccurWithMessage:message];
}
}
@end
================================================
FILE: FastImageCache/FastImageCache/FastImageCache/FICImageFormat.h
================================================
//
// FICImageFormat.h
// FastImageCache
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICImports.h"
@class FICImageTable;
typedef NS_OPTIONS(NSUInteger, FICImageFormatDevices) {
FICImageFormatDevicePhone = 1 << UIUserInterfaceIdiomPhone,
FICImageFormatDevicePad = 1 << UIUserInterfaceIdiomPad,
};
typedef NS_ENUM(NSUInteger, FICImageFormatStyle) {
FICImageFormatStyle32BitBGRA,
FICImageFormatStyle32BitBGR,
FICImageFormatStyle16BitBGR,
FICImageFormatStyle8BitGrayscale,
};
typedef NS_ENUM(NSUInteger, FICImageFormatProtectionMode) {
FICImageFormatProtectionModeNone,
FICImageFormatProtectionModeComplete,
FICImageFormatProtectionModeCompleteUntilFirstUserAuthentication,
};
/**
`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.
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
prevent the image cache from consuming too much disk space. Each `<FICImageTable>` managed by the image cache is associated with a single image format.
*/
NS_ASSUME_NONNULL_BEGIN
@interface FICImageFormat : NSObject <NSCopying>
///------------------------------
/// @name Image Format Properties
///------------------------------
/**
The name of the image format. Each image format must have a unique name.
@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
is recommended (e.g., com.path.PTUserProfilePhotoLargeImageFormat).
*/
@property (nonatomic, copy) NSString *name;
/**
The optional family that the image format belongs to. Families group together related image formats.
@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.
For example, you might define a `userPhoto` family that groups together image formats with the following names: `userPhotoSmallThumbnail`, `userPhotoLargeThumbnail`, `userPhotoLargeThumbnailBorder`.
Ideally, the same source image can be processed to create cached image data for every image format belonging to the same family.
`<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
a source image once instead of having to download and process the same source image multiple times for different formats in the same family.
@see [FICImageCacheDelegate imageCache:shouldProcessAllFormatsInFamily:forEntity:]
*/
@property (nonatomic, copy) NSString *family;
/**
The size, in points, of the images stored in the image table created by this format.
*/
@property (nonatomic, assign) CGSize imageSize;
/**
A bitmask of type `<FICImageFormatStyle>` that defines the style of the image format.
`FICImageFormatStyle` has the following values:
- `FICImageFormatStyle32BitBGRA`: Full-color image format with alpha channel. 8 bits per color component, and 8 bits for the alpha channel.
- `FICImageFormatStyle32BitBGR`: Full-color image format with no alpha channel. 8 bits per color component. The remaining 8 bits are unused.
- `FICImageFormatStyle16BitBGR`: Reduced-color image format with no alpha channel. 5 bits per color component. The remaining bit is unused.
- `FICImageFormatStyle8BitGrayscale`: Grayscale-only image format with no alpha channel.
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
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
`FICImageFormatStyle8BitGrayscale` style is sufficient and further reduces disk space usage.
*/
@property (nonatomic, assign) FICImageFormatStyle style;
/**
The maximum number of entries that an image table can contain for this image format.
@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.
*/
@property (nonatomic, assign) NSInteger maximumCount;
/**
A bitmask of type `<FICImageFormatDevices>` that defines which devices are managed by an image table.
@discussion If the current device is not included in a particular image format, the image cache will not store image data for that device.
*/
@property (nonatomic, assign) FICImageFormatDevices devices;
/**
The size, in pixels, of the images stored in the image table created by this format. This takes into account the screen scale.
*/
@property (nonatomic, assign, readonly) CGSize pixelSize;
/**
The bitmap info associated with the images created with this image format.
*/
@property (nonatomic, assign, readonly) CGBitmapInfo bitmapInfo;
/**
The number of bytes each pixel of an image created with this image format occupies.
*/
@property (nonatomic, assign, readonly) NSInteger bytesPerPixel;
/**
The number of bits each pixel component (e.g., blue, green, red color channels) uses for images created with this image format.
*/
@property (nonatomic, assign, readonly) NSInteger bitsPerComponent;
/**
Whether or not the the images represented by this image format are grayscale.
*/
@property (nonatomic, assign, readonly) BOOL isGrayscale;
/**
The data protection mode that image table files will be created with.
`FICImageFormatProtectionMode` has the following values:
- `FICImageFormatProtectionModeNone`: No data protection is used. The image table file backing this image format will always be available for reading and writing.
- `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
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.
- `FICImageFormatProtectionModeCompleteUntilFirstUserAuthentication`: Partial data protection is used. After a device restart, until the user unlocks the device for the first time, complete data
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
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
locked.
@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,
consider using `FICImageFormatProtectionModeNone` to prevent any issues accessing image table files when the disk is encrypted.
*/
@property (nonatomic, assign) FICImageFormatProtectionMode protectionMode;
/**
The string representation of `<protectionMode>`.
*/
@property (nonatomic, assign, readonly) NSString *protectionModeString;
/**
The dictionary representation of this image format.
@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
image table associated with that image format. The image table is then recreated from the updated image format.
*/
@property (nonatomic, copy, readonly) NSDictionary<NSString*, id> *dictionaryRepresentation;
///-----------------------------------
/// @name Initializing an Image Format
///-----------------------------------
/**
Convenience initializer to create a new image format.
@param name The name of the image format. Each image format must have a unique name.
@param family The optional family that the image format belongs to. See the `<family>` property description for more information.
@param imageSize The size, in points, of the images stored in the image table created by this format.
@param style The style of the image format. See the `<style>` property description for more information.
@param maximumCount The maximum number of entries that an image table can contain for this image format.
@param devices A bitmask of type `<FICImageFormatDevices>` that defines which devices are managed by an image table.
@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.
@return An autoreleased instance of `FICImageFormat` or one of its subclasses, if any exist.
*/
+ (instancetype)formatWithName:(NSString *)name family:(NSString *)family imageSize:(CGSize)imageSize style:(FICImageFormatStyle)style maximumCount:(NSInteger)maximumCount devices:(FICImageFormatDevices)devices protectionMode:(FICImageFormatProtectionMode)protectionMode;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: FastImageCache/FastImageCache/FastImageCache/FICImageFormat.m
================================================
//
// FICImageFormat.m
// FastImageCache
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICImageFormat.h"
#import "FICImageTable.h"
#import "FICImageTableEntry.h"
#pragma mark Internal Definitions
static NSString *const FICImageFormatNameKey = @"name";
static NSString *const FICImageFormatFamilyKey = @"family";
static NSString *const FICImageFormatWidthKey = @"width";
static NSString *const FICImageFormatHeightKey = @"height";
static NSString *const FICImageFormatStyleKey = @"style";
static NSString *const FICImageFormatMaximumCountKey = @"maximumCount";
static NSString *const FICImageFormatDevicesKey = @"devices";
static NSString *const FICImageFormatProtectionModeKey = @"protectionMode";
#pragma mark - Class Extension
@interface FICImageFormat () {
NSString *_name;
NSString *_family;
CGSize _imageSize;
CGSize _pixelSize;
FICImageFormatStyle _style;
NSInteger _maximumCount;
FICImageFormatDevices _devices;
FICImageFormatProtectionMode _protectionMode;
}
@end
#pragma mark
@implementation FICImageFormat
@synthesize name = _name;
@synthesize family = _family;
@synthesize imageSize = _imageSize;
@synthesize pixelSize = _pixelSize;
@synthesize style = _style;
@synthesize maximumCount = _maximumCount;
@synthesize devices = _devices;
@synthesize protectionMode = _protectionMode;
#pragma mark - Property Accessors
- (void)setImageSize:(CGSize)imageSize {
BOOL currentSizeEqualToNewSize = CGSizeEqualToSize(imageSize, _imageSize);
if (currentSizeEqualToNewSize == NO) {
_imageSize = imageSize;
CGFloat screenScale = [[UIScreen mainScreen] scale];
_pixelSize = CGSizeMake(screenScale * _imageSize.width, screenScale * _imageSize.height);
}
}
- (CGBitmapInfo)bitmapInfo {
CGBitmapInfo info;
switch (_style) {
case FICImageFormatStyle32BitBGRA:
info = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
break;
case FICImageFormatStyle32BitBGR:
info = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
break;
case FICImageFormatStyle16BitBGR:
info = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder16Host;
break;
case FICImageFormatStyle8BitGrayscale:
info = (CGBitmapInfo)kCGImageAlphaNone;
break;
}
return info;
}
- (NSInteger)bytesPerPixel {
NSInteger bytesPerPixel;
switch (_style) {
case FICImageFormatStyle32BitBGRA:
case FICImageFormatStyle32BitBGR:
bytesPerPixel = 4;
break;
case FICImageFormatStyle16BitBGR:
bytesPerPixel = 2;
break;
case FICImageFormatStyle8BitGrayscale:
bytesPerPixel = 1;
break;
}
return bytesPerPixel;
}
- (NSInteger)bitsPerComponent {
NSInteger bitsPerComponent;
switch (_style) {
case FICImageFormatStyle32BitBGRA:
case FICImageFormatStyle32BitBGR:
case FICImageFormatStyle8BitGrayscale:
bitsPerComponent = 8;
break;
case FICImageFormatStyle16BitBGR:
bitsPerComponent = 5;
break;
}
return bitsPerComponent;
}
- (BOOL)isGrayscale {
BOOL isGrayscale;
switch (_style) {
case FICImageFormatStyle32BitBGRA:
case FICImageFormatStyle32BitBGR:
case FICImageFormatStyle16BitBGR:
isGrayscale = NO;
break;
case FICImageFormatStyle8BitGrayscale:
isGrayscale = YES;
break;
}
return isGrayscale;
}
- (NSString *)protectionModeString {
NSString *protectionModeString = nil;
switch (_protectionMode) {
case FICImageFormatProtectionModeNone:
protectionModeString = NSFileProtectionNone;
break;
case FICImageFormatProtectionModeComplete:
protectionModeString = NSFileProtectionComplete;
break;
case FICImageFormatProtectionModeCompleteUntilFirstUserAuthentication:
protectionModeString = NSFileProtectionCompleteUntilFirstUserAuthentication;
break;
}
return protectionModeString;
}
#pragma mark - Object Lifecycle
+ (instancetype)formatWithName:(NSString *)name family:(NSString *)family imageSize:(CGSize)imageSize style:(FICImageFormatStyle)style maximumCount:(NSInteger)maximumCount devices:(FICImageFormatDevices)devices protectionMode:(FICImageFormatProtectionMode)protectionMode {
FICImageFormat *imageFormat = [[FICImageFormat alloc] init];
[imageFormat setName:name];
[imageFormat setFamily:family];
[imageFormat setImageSize:imageSize];
[imageFormat setStyle:style];
[imageFormat setMaximumCount:maximumCount];
[imageFormat setDevices:devices];
[imageFormat setProtectionMode:protectionMode];
return imageFormat;
}
#pragma mark - Working with Dictionary Representations
- (NSDictionary *)dictionaryRepresentation {
NSMutableDictionary *dictionaryRepresentation = [NSMutableDictionary dictionary];
[dictionaryRepresentation setValue:_name forKey:FICImageFormatNameKey];
[dictionaryRepresentation setValue:_family forKey:FICImageFormatFamilyKey];
[dictionaryRepresentation setValue:[NSNumber numberWithUnsignedInteger:_imageSize.width] forKey:FICImageFormatWidthKey];
[dictionaryRepresentation setValue:[NSNumber numberWithUnsignedInteger:_imageSize.height] forKey:FICImageFormatHeightKey];
[dictionaryRepresentation setValue:[NSNumber numberWithInt:_style] forKey:FICImageFormatStyleKey];
[dictionaryRepresentation setValue:[NSNumber numberWithUnsignedInteger:_maximumCount] forKey:FICImageFormatMaximumCountKey];
[dictionaryRepresentation setValue:[NSNumber numberWithInt:_devices] forKey:FICImageFormatDevicesKey];
[dictionaryRepresentation setValue:[NSNumber numberWithUnsignedInteger:_protectionMode] forKey:FICImageFormatProtectionModeKey];
[dictionaryRepresentation setValue:[NSNumber numberWithFloat:[[UIScreen mainScreen] scale]] forKey:FICImageTableScreenScaleKey];
[dictionaryRepresentation setValue:[NSNumber numberWithUnsignedInteger:[FICImageTableEntry metadataVersion]] forKey:FICImageTableEntryDataVersionKey];
return dictionaryRepresentation;
}
#pragma mark - Protocol Implementations
#pragma mark - NSObject (NSCopying)
- (id)copyWithZone:(NSZone *)zone {
FICImageFormat *imageFormatCopy = [[FICImageFormat alloc] init];
[imageFormatCopy setName:[self name]];
[imageFormatCopy setFamily:[self family]];
[imageFormatCopy setImageSize:[self imageSize]];
[imageFormatCopy setStyle:[self style]];
[imageFormatCopy setMaximumCount:[self maximumCount]];
[imageFormatCopy setDevices:[self devices]];
[imageFormatCopy setProtectionMode:[self protectionMode]];
return imageFormatCopy;
}
@end
================================================
FILE: FastImageCache/FastImageCache/FastImageCache/FICImageTable.h
================================================
//
// FICImageTable.h
// FastImageCache
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICImports.h"
#import "FICImageCache.h"
#import "FICEntity.h"
@class FICImageFormat;
@class FICImageTableChunk;
@class FICImageTableEntry;
@class FICImage;
NS_ASSUME_NONNULL_BEGIN
extern NSString *const FICImageTableEntryDataVersionKey;
extern NSString *const FICImageTableScreenScaleKey;
/**
`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
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
mind that you cannot mix image dimensions or whether or not an image is opaque.
*/
@interface FICImageTable : NSObject
///-----------------------------
/// @name Image Table Properties
///-----------------------------
/**
The file system path where the image table's data file is located.
*/
@property (nonatomic, copy, readonly) NSString *tableFilePath;
/**
The file system path where the image table's metadata file is located.
*/
@property (nonatomic, copy, readonly) NSString *metadataFilePath;
/**
The image format that describes the image table.
*/
@property (nonatomic, strong, readonly) FICImageFormat *imageFormat;
///-----------------------------------------------
/// @name Accessing Information about Image Tables
///-----------------------------------------------
/**
Returns the page size for the current device.
@return The number of bytes in a page of memory.
@discussion This class method calls the UNIX function `getpagesize()` exactly once, storing the result in a static local variable.
*/
+ (int)pageSize;
/**
Returns the file system path for the directory that stores image table files.
@return The string representing the file system directory path where image table files are stored.
@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.
*/
+ (NSString *)directoryPath;
///----------------------------------
/// @name Initializing an Image Table
///----------------------------------
/**
Initializes a new image table described by the provided image format.
@param imageFormat The image format that describes the image table.
@param imageCache The instance of `<FICImageCache>` that owns this image table.
@return A new image table.
@warning `FICImageTable` raises an exception if `imageFormat` is `nil`. `FICImageTable`'s implementation of `-init` simply calls through to this initializer, passing `nil` for `imageFormat`.
*/
- (nullable instancetype)initWithFormat:(FICImageFormat *)imageFormat imageCache:(FICImageCache *)imageCache NS_DESIGNATED_INITIALIZER;
-(instancetype) init __attribute__((unavailable("Invoke the designated initializer initWithFormat:imageCache: instead")));
+(instancetype) new __attribute__((unavailable("Invoke the designated initializer initWithFormat:imageCache: instead")));
///------------------------------------------------
/// @name Storing, Retrieving, and Deleting Entries
///------------------------------------------------
/**
Stores new image entry data in the image table.
@param entityUUID The UUID of the entity that uniquely identifies an image table entry. Must not be `nil`.
@param sourceImageUUID The UUID of the source image that represents the actual image data stored in an image table entry. Must not be `nil`.
@param imageDrawingBlock The drawing block provided by the entity that actually draws the source image into a bitmap context. Must not be `nil`.
@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
by the image table. Drawing in the provided bitmap context writes the uncompressed image data directly to the image table file on disk.
@note If any of the parameters to this method are `nil`, this method does nothing.
@see [FICEntity drawingBlockForImage:withFormatName:]
*/
- (void)setEntryForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID imageDrawingBlock:(FICEntityImageDrawingBlock)imageDrawingBlock;
/**
Returns a new image from the image entry data in the image table.
@param entityUUID The UUID of the entity that uniquely identifies an image table entry. Must not be `nil`.
@param sourceImageUUID The UUID of the source image that represents the actual image data stored in an image table entry. Must not be `nil`.
@param preheatData A `BOOL` indicating whether or not the entry's image data should be preheated. See `<[FICImageTableEntry preheat]>` for more information.
@return A new image created from the entry data stored in the image table or `nil` if something went wrong.
@discussion The `UIImage` returned by this method is initialized by a `CGImageRef` backed directly by mapped file data, so no memory copy occurs.
@note If either of the first two parameters to this method are `nil`, the return value is `nil`.
@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
provided entity UUID, and `nil` is returned.
*/
- (nullable UIImage *)newImageForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID preheatData:(BOOL)preheatData;
/**
Deletes image entry data in the image table.
@param entityUUID The UUID of the entity that uniquely identifies an image table entry. Must not be `nil`.
@note If `entityUUID` is `nil`, this method does nothing.
*/
- (void)deleteEntryForEntityUUID:(NSString *)entityUUID;
///-----------------------------------
/// @name Checking for Entry Existence
///-----------------------------------
/**
Returns whether or not an entry exists in the image table.
@param entityUUID The UUID of the entity that uniquely identifies an image table entry. Must not be `nil`.
@param sourceImageUUID The UUID of the source image that represents the actual image data stored in an image table entry. Must not be `nil`.
@return `YES` if an entry exists in the image table for the provided entity UUID and source image UUID. Otherwise, `NO`.
@note If either of the parameters to this method are `nil`, the return value is `NO`.
@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
provided entity UUID, and `NO` is returned.
*/
- (BOOL)entryExistsForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID;
///--------------------------------
/// @name Resetting the Image Table
///--------------------------------
/**
Resets the image table by deleting all its data and metadata.
*/
- (void)reset;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: FastImageCache/FastImageCache/FastImageCache/FICImageTable.m
================================================
//
// FICImageTable.m
// FastImageCache
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICImageTable.h"
#import "FICImageFormat.h"
#import "FICImageCache.h"
#import "FICImageTableChunk.h"
#import "FICImageTableEntry.h"
#import "FICUtilities.h"
#import <libkern/OSAtomic.h>
#import "FICImageCache+FICErrorLogging.h"
#pragma mark External Definitions
NSString *const FICImageTableEntryDataVersionKey = @"FICImageTableEntryDataVersionKey";
NSString *const FICImageTableScreenScaleKey = @"FICImageTableScreenScaleKey";
#pragma mark - Internal Definitions
static NSString *const FICImageTableMetadataFileExtension = @"metadata";
static NSString *const FICImageTableFileExtension = @"imageTable";
static NSString *const FICImageTableIndexMapKey = @"indexMap";
static NSString *const FICImageTableContextMapKey = @"contextMap";
static NSString *const FICImageTableMRUArrayKey = @"mruArray";
static NSString *const FICImageTableFormatKey = @"format";
#pragma mark - Class Extension
@interface FICImageTable () {
FICImageFormat *_imageFormat;
CGFloat _screenScale;
NSInteger _imageRowLength;
NSString *_filePath;
int _fileDescriptor;
off_t _fileLength;
NSUInteger _entryCount;
NSInteger _entryLength;
NSUInteger _entriesPerChunk;
NSInteger _imageLength;
size_t _chunkLength;
NSInteger _chunkCount;
NSMutableDictionary *_chunkDictionary;
NSCountedSet *_chunkSet;
NSRecursiveLock *_lock;
CFMutableDictionaryRef _indexNumbers;
// Image table metadata
NSMutableDictionary *_indexMap; // Key: entity UUID, value: integer index into the table file
NSMutableDictionary *_sourceImageMap; // Key: entity UUID, value: source image UUID
NSMutableIndexSet *_occupiedIndexes;
NSMutableOrderedSet *_MRUEntries;
NSCountedSet *_inUseEntries;
NSDictionary *_imageFormatDictionary;
int32_t _metadataVersion;
NSString *_fileDataProtectionMode;
BOOL _canAccessData;
}
@property (nonatomic, weak) FICImageCache *imageCache;
@end
#pragma mark
@implementation FICImageTable
@synthesize imageFormat =_imageFormat;
#pragma mark - Property Accessors (Public)
- (NSString *)tableFilePath {
NSString *tableFilePath = [[_imageFormat name] stringByAppendingPathExtension:FICImageTableFileExtension];
tableFilePath = [[self directoryPath] stringByAppendingPathComponent:tableFilePath];
return tableFilePath;
}
- (NSString *)metadataFilePath {
NSString *metadataFilePath = [[_imageFormat name] stringByAppendingPathExtension:FICImageTableMetadataFileExtension];
metadataFilePath = [[self directoryPath] stringByAppendingPathComponent:metadataFilePath];
return metadataFilePath;
}
- (NSString *) directoryPath {
NSString *directoryPath = [FICImageTable directoryPath];
if (self.imageCache.nameSpace) {
directoryPath = [directoryPath stringByAppendingPathComponent:self.imageCache.nameSpace];
}
return directoryPath;
}
#pragma mark - Class-Level Definitions
+ (int)pageSize {
static int __pageSize = 0;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__pageSize = getpagesize();
});
return __pageSize;
}
+ (NSString *)directoryPath {
static NSString *__directoryPath = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
__directoryPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"ImageTables"];
NSFileManager *fileManager = [[NSFileManager alloc] init];
BOOL directoryExists = [fileManager fileExistsAtPath:__directoryPath];
if (directoryExists == NO) {
[fileManager createDirectoryAtPath:__directoryPath withIntermediateDirectories:YES attributes:nil error:nil];
}
});
return __directoryPath;
}
#pragma mark - Object Lifecycle
- (instancetype)initWithFormat:(FICImageFormat *)imageFormat imageCache:(FICImageCache *)imageCache {
self = [super init];
if (self != nil) {
if (imageFormat == nil) {
[NSException raise:NSInvalidArgumentException format:@"*** FIC Exception: %s must pass in an image format.", __PRETTY_FUNCTION__];
}
if (imageCache == nil) {
[NSException raise:NSInvalidArgumentException format:@"*** FIC Exception: %s must pass in an image cache.", __PRETTY_FUNCTION__];
}
self.imageCache = imageCache;
_lock = [[NSRecursiveLock alloc] init];
_indexNumbers = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
_imageFormat = [imageFormat copy];
_imageFormatDictionary = [imageFormat dictionaryRepresentation];
_screenScale = [[UIScreen mainScreen] scale];
CGSize pixelSize = [_imageFormat pixelSize];
NSInteger bytesPerPixel = [_imageFormat bytesPerPixel];
_imageRowLength = (NSInteger)FICByteAlignForCoreAnimation(pixelSize.width * bytesPerPixel);
_imageLength = _imageRowLength * (NSInteger)pixelSize.height;
_chunkDictionary = [[NSMutableDictionary alloc] init];
_chunkSet = [[NSCountedSet alloc] init];
_indexMap = [[NSMutableDictionary alloc] init];
_occupiedIndexes = [[NSMutableIndexSet alloc] init];
_MRUEntries = [[NSMutableOrderedSet alloc] init];
_inUseEntries = [NSCountedSet set];
_sourceImageMap = [[NSMutableDictionary alloc] init];
_filePath = [[self tableFilePath] copy];
[self _loadMetadata];
NSString *directoryPath = [self directoryPath];
NSFileManager *fileManager = [[NSFileManager alloc] init];
BOOL isDirectory;
if (self.imageCache.nameSpace && ![fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory]) {
[fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:nil];
}
if ([fileManager fileExistsAtPath:_filePath] == NO) {
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
[attributes setValue:[_imageFormat protectionModeString] forKeyPath:NSFileProtectionKey];
[fileManager createFileAtPath:_filePath contents:nil attributes:attributes];
}
NSDictionary *attributes = [fileManager attributesOfItemAtPath:_filePath error:NULL];
_fileDataProtectionMode = [attributes objectForKey:NSFileProtectionKey];
_fileDescriptor = open([_filePath fileSystemRepresentation], O_RDWR | O_CREAT, 0666);
if (_fileDescriptor >= 0) {
// The size of each entry in the table needs to be page-aligned. This will cause each entry to have a page-aligned base
// address, which will help us avoid Core Animation having to copy our images when we eventually set them on layers.
_entryLength = (NSInteger)FICByteAlign(_imageLength + sizeof(FICImageTableEntryMetadata), [FICImageTable pageSize]);
// Each chunk will map in n entries. Try to keep the chunkLength around 2MB.
NSInteger goalChunkLength = 2 * (1024 * 1024);
NSInteger goalEntriesPerChunk = goalChunkLength / _entryLength;
_entriesPerChunk = MAX(4, goalEntriesPerChunk);
if ([self _maximumCount] > [_imageFormat maximumCount]) {
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]];
[self.imageCache _logMessage:message];
}
_chunkLength = (size_t)(_entryLength * _entriesPerChunk);
_fileLength = lseek(_fileDescriptor, 0, SEEK_END);
_entryCount = (NSInteger)(_fileLength / _entryLength);
_chunkCount = (_entryCount + _entriesPerChunk - 1) / _entriesPerChunk;
if ([_indexMap count] > _entryCount) {
// It's possible that someone deleted the image table file but left behind the metadata file. If this happens, the metadata
// will obviously become out of sync with the image table file, so we need to reset the image table.
[self reset];
}
} else {
// If something goes wrong and we can't open the image table file, then we have no choice but to release and nil self.
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];
[self.imageCache _logMessage:message];
self = nil;
}
}
return self;
}
- (void)dealloc {
if (_fileDescriptor >= 0) {
close(_fileDescriptor);
}
}
#pragma mark - Working with Chunks
- (FICImageTableChunk *)_cachedChunkAtIndex:(NSInteger)index {
return [_chunkDictionary objectForKey:@(index)];
}
- (void)_setChunk:(FICImageTableChunk *)chunk index:(NSInteger)index {
NSNumber *indexNumber = @(index);
if (chunk != nil) {
[_chunkDictionary setObject:chunk forKey:indexNumber];
} else {
[_chunkDictionary removeObjectForKey:indexNumber];
}
}
- (FICImageTableChunk *)_chunkAtIndex:(NSInteger)index {
FICImageTableChunk *chunk = nil;
if (index < _chunkCount) {
chunk = [self _cachedChunkAtIndex:index];
if (chunk == nil) {
size_t chunkLength = _chunkLength;
off_t chunkOffset = index * (off_t)_chunkLength;
if (chunkOffset + chunkLength > _fileLength) {
chunkLength = (size_t)(_fileLength - chunkOffset);
}
chunk = [[FICImageTableChunk alloc] initWithFileDescriptor:_fileDescriptor index:index length:chunkLength];
[self _setChunk:chunk index:index];
}
}
if (!chunk) {
NSString *message = [NSString stringWithFormat:@"*** FIC Error: %s failed to get chunk for index %ld.", __PRETTY_FUNCTION__, (long)index];
[self.imageCache _logMessage:message];
}
return chunk;
}
#pragma mark - Storing, Retrieving, and Deleting Entries
- (void)setEntryForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID imageDrawingBlock:(FICEntityImageDrawingBlock)imageDrawingBlock {
if (entityUUID != nil && sourceImageUUID != nil && imageDrawingBlock != NULL) {
[_lock lock];
NSInteger newEntryIndex = [self _indexOfEntryForEntityUUID:entityUUID];
if (newEntryIndex == NSNotFound) {
newEntryIndex = [self _nextEntryIndex];
if (newEntryIndex >= _entryCount) {
// Determine how many chunks we need to support new entry index.
// Number of entries should always be a multiple of _entriesPerChunk
NSInteger numberOfEntriesRequired = newEntryIndex + 1;
NSInteger newChunkCount = _entriesPerChunk > 0 ? ((numberOfEntriesRequired + _entriesPerChunk - 1) / _entriesPerChunk) : 0;
NSInteger newEntryCount = newChunkCount * _entriesPerChunk;
[self _setEntryCount:newEntryCount];
}
}
if (newEntryIndex < _entryCount) {
CGSize pixelSize = [_imageFormat pixelSize];
CGBitmapInfo bitmapInfo = [_imageFormat bitmapInfo];
CGColorSpaceRef colorSpace = [_imageFormat isGrayscale] ? CGColorSpaceCreateDeviceGray() : CGColorSpaceCreateDeviceRGB();
NSInteger bitsPerComponent = [_imageFormat bitsPerComponent];
// Create context whose backing store *is* the mapped file data
FICImageTableEntry *entryData = [self _entryDataAtIndex:newEntryIndex];
if (entryData != nil) {
[entryData setEntityUUIDBytes:FICUUIDBytesWithString(entityUUID)];
[entryData setSourceImageUUIDBytes:FICUUIDBytesWithString(sourceImageUUID)];
// Update our book-keeping
[_indexMap setObject:[NSNumber numberWithUnsignedInteger:newEntryIndex] forKey:entityUUID];
[_occupiedIndexes addIndex:newEntryIndex];
[_sourceImageMap setObject:sourceImageUUID forKey:entityUUID];
// Update MRU array
[self _entryWasAccessedWithEntityUUID:entityUUID];
[self saveMetadata];
// Unique, unchanging pointer for this entry's index
NSNumber *indexNumber = [self _numberForEntryAtIndex:newEntryIndex];
// Relinquish the image table lock before calling potentially slow imageDrawingBlock to unblock other FIC operations
[_lock unlock];
CGContextRef context = CGBitmapContextCreate([entryData bytes], pixelSize.width, pixelSize.height, bitsPerComponent, _imageRowLength, colorSpace, bitmapInfo);
CGContextTranslateCTM(context, 0, pixelSize.height);
CGContextScaleCTM(context, _screenScale, -_screenScale);
@synchronized(indexNumber) {
// Call drawing block to allow client to draw into the context
imageDrawingBlock(context, [_imageFormat imageSize]);
CGContextRelease(context);
// Write the data back to the filesystem
[entryData flush];
}
} else {
[_lock unlock];
}
CGColorSpaceRelease(colorSpace);
} else {
[_lock unlock];
}
}
}
- (UIImage *)newImageForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID preheatData:(BOOL)preheatData {
UIImage *image = nil;
if (entityUUID != nil && sourceImageUUID != nil) {
[_lock lock];
FICImageTableEntry *entryData = [self _entryDataForEntityUUID:entityUUID];
if (entryData != nil) {
NSString *entryEntityUUID = FICStringWithUUIDBytes([entryData entityUUIDBytes]);
NSString *entrySourceImageUUID = FICStringWithUUIDBytes([entryData sourceImageUUIDBytes]);
BOOL entityUUIDIsCorrect = entityUUID == nil || [entityUUID caseInsensitiveCompare:entryEntityUUID] == NSOrderedSame;
BOOL sourceImageUUIDIsCorrect = sourceImageUUID == nil || [sourceImageUUID caseInsensitiveCompare:entrySourceImageUUID] == NSOrderedSame;
NSNumber *indexNumber = [self _numberForEntryAtIndex:[entryData index]];
@synchronized(indexNumber) {
if (entityUUIDIsCorrect == NO || sourceImageUUIDIsCorrect == NO) {
// The UUIDs don't match, so we need to invalidate the entry.
[self deleteEntryForEntityUUID:entityUUID];
} else {
[self _entryWasAccessedWithEntityUUID:entityUUID];
// Create CGImageRef whose backing store *is* the mapped image table entry. We avoid a memcpy this way.
CGDataProviderRef dataProvider = CGDataProviderCreateWithData((__bridge_retained void *)entryData, [entryData bytes], [entryData imageLength], _FICReleaseImageData);
[_inUseEntries addObject:entityUUID];
__weak FICImageTable *weakSelf = self;
[entryData executeBlockOnDealloc:^{
[weakSelf removeInUseForEntityUUID:entityUUID];
}];
CGSize pixelSize = [_imageFormat pixelSize];
CGBitmapInfo bitmapInfo = [_imageFormat bitmapInfo];
NSInteger bitsPerComponent = [_imageFormat bitsPerComponent];
NSInteger bitsPerPixel = [_imageFormat bytesPerPixel] * 8;
CGColorSpaceRef colorSpace = [_imageFormat isGrayscale] ? CGColorSpaceCreateDeviceGray() : CGColorSpaceCreateDeviceRGB();
CGImageRef imageRef = CGImageCreate(pixelSize.width, pixelSize.height, bitsPerComponent, bitsPerPixel, _imageRowLength, colorSpace, bitmapInfo, dataProvider, NULL, false, (CGColorRenderingIntent)0);
CGDataProviderRelease(dataProvider);
CGColorSpaceRelease(colorSpace);
if (imageRef != NULL) {
image = [[UIImage alloc] initWithCGImage:imageRef scale:_screenScale orientation:UIImageOrientationUp];
CGImageRelease(imageRef);
} else {
NSString *message = [NSString stringWithFormat:@"*** FIC Error: %s could not create a new CGImageRef for entity UUID %@.", __PRETTY_FUNCTION__, entityUUID];
[self.imageCache _logMessage:message];
}
if (image != nil && preheatData) {
[entryData preheat];
}
}
}
}
[_lock unlock];
}
return image;
}
static void _FICReleaseImageData(void *info, const void *data, size_t size) {
if (info) {
CFRelease(info);
}
}
- (void)removeInUseForEntityUUID:(NSString *)entityUUID {
[_lock lock];
[_inUseEntries removeObject:entityUUID];
[_lock unlock];
}
- (void)deleteEntryForEntityUUID:(NSString *)entityUUID {
if (entityUUID != nil) {
[_lock lock];
NSInteger MRUIndex = [_MRUEntries indexOfObject:entityUUID];
if (MRUIndex != NSNotFound) {
[_MRUEntries removeObjectAtIndex:MRUIndex];
}
NSInteger index = [self _indexOfEntryForEntityUUID:entityUUID];
if (index != NSNotFound) {
[_sourceImageMap removeObjectForKey:entityUUID];
[_indexMap removeObjectForKey:entityUUID];
[_occupiedIndexes removeIndex:index];
[self saveMetadata];
}
[_lock unlock];
}
}
#pragma mark - Checking for Entry Existence
- (BOOL)entryExistsForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID {
BOOL imageExists = NO;
[_lock lock];
FICImageTableEntry *entryData = [self _entryDataForEntityUUID:entityUUID];
if (entryData != nil && sourceImageUUID != nil) {
NSString *existingEntityUUID = FICStringWithUUIDBytes([entryData entityUUIDBytes]);
BOOL entityUUIDIsCorrect = [entityUUID isEqualToString:existingEntityUUID];
NSString *existingSourceImageUUID = FICStringWithUUIDBytes([entryData sourceImageUUIDBytes]);
BOOL sourceImageUUIDIsCorrect = [sourceImageUUID isEqualToString:existingSourceImageUUID];
if (entityUUIDIsCorrect == NO || sourceImageUUIDIsCorrect == NO) {
// The source image UUIDs don't match, so the image data should be deleted for this entity.
[self deleteEntryForEntityUUID:entityUUID];
entryData = nil;
}
}
[_lock unlock];
imageExists = entryData != nil;
return imageExists;
}
#pragma mark - Working with Entries
- (NSInteger)_maximumCount {
return MAX([_imageFormat maximumCount], _entriesPerChunk);
}
- (void)_setEntryCount:(NSInteger)entryCount {
if (entryCount != _entryCount) {
off_t fileLength = entryCount * _entryLength;
int result = ftruncate(_fileDescriptor, fileLength);
if (result != 0) {
NSString *message = [NSString stringWithFormat:@"*** FIC Error: %s ftruncate returned %d, error = %d, fd = %d, filePath = %@, length = %lld", __PRETTY_FUNCTION__, result, errno, _fileDescriptor, _filePath, fileLength];
[self.imageCache _logMessage:message];
} else {
_fileLength = fileLength;
_entryCount = entryCount;
_chunkCount = _entriesPerChunk > 0 ? ((_entryCount + _entriesPerChunk - 1) / _entriesPerChunk) : 0;
NSDictionary *chunkDictionary = [_chunkDictionary copy];
for (FICImageTableChunk *chunk in [chunkDictionary allValues]) {
if ([chunk length] != _chunkLength) {
// Issue 31: https://github.com/path/FastImageCache/issues/31
// Somehow, we have a partial chunk whose length needs to be adjusted
// since we changed our file length.
[self _setChunk:nil index:[chunk index]];
}
}
}
}
}
// There's inherently a race condition between when you ask whether the data is
// accessible and when you try to use that data. Sidestep this issue altogether
// by using NSFileProtectionNone
- (BOOL)canAccessEntryData {
if ([_fileDataProtectionMode isEqualToString:NSFileProtectionNone])
return YES;
if ([_fileDataProtectionMode isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication] && _canAccessData)
return YES;
// -[UIApplication isProtectedDataAvailable] checks whether the keybag is locked or not
UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
if (application) {
_canAccessData = [application isProtectedDataAvailable];
}
// We have to fallback to a direct check on the file if either:
// - The application doesn't exist (happens in some extensions)
// - The keybag is locked, but the file might still be accessible because the mode is "until first user authentication"
if (!application || (!_canAccessData && [_fileDataProtectionMode isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication])) {
int fd;
_canAccessData = ((fd = open([_filePath fileSystemRepresentation], O_RDONLY)) != -1);
if (_canAccessData)
close(fd);
}
return _canAccessData;
}
- (FICImageTableEntry *)_entryDataAtIndex:(NSInteger)index {
FICImageTableEntry *entryData = nil;
[_lock lock];
BOOL canAccessData = [self canAccessEntryData];
if (index < _entryCount && canAccessData) {
off_t entryOffset = index * _entryLength;
size_t chunkIndex = (size_t)(entryOffset / _chunkLength);
FICImageTableChunk *chunk = [self _chunkAtIndex:chunkIndex];
if (chunk != nil) {
off_t chunkOffset = chunkIndex * _chunkLength;
off_t entryOffsetInChunk = entryOffset - chunkOffset;
void *mappedChunkAddress = [chunk bytes];
void *mappedEntryAddress = mappedChunkAddress + entryOffsetInChunk;
entryData = [[FICImageTableEntry alloc] initWithImageTableChunk:chunk bytes:mappedEntryAddress length:_entryLength];
if (entryData) {
[entryData setImageCache:self.imageCache];
[entryData setIndex:index];
[_chunkSet addObject:chunk];
__weak FICImageTable *weakSelf = self;
[entryData executeBlockOnDealloc:^{
[weakSelf _entryWasDeallocatedFromChunk:chunk];
}];
}
}
}
[_lock unlock];
if (!entryData) {
NSString *message = nil;
if (canAccessData) {
message = [NSString stringWithFormat:@"*** FIC Error: %s failed to get entry for index %ld.", __PRETTY_FUNCTION__, (long)index];
} else {
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__];
}
[self.imageCache _logMessage:message];
}
return entryData;
}
- (void)_entryWasDeallocatedFromChunk:(FICImageTableChunk *)chunk {
[_lock lock];
[_chunkSet removeObject:chunk];
if ([_chunkSet countForObject:chunk] == 0) {
[self _setChunk:nil index:[chunk index]];
}
[_lock unlock];
}
- (NSInteger)_nextEntryIndex {
NSMutableIndexSet *unoccupiedIndexes = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, _entryCount)];
[unoccupiedIndexes removeIndexes:_occupiedIndexes];
NSInteger index = [unoccupiedIndexes firstIndex];
if (index == NSNotFound) {
index = _entryCount;
}
if (index >= [self _maximumCount] && [_MRUEntries count]) {
// Evict the oldest/least-recently accessed entry here
NSString *oldestEvictableEntityUUID = [self oldestEvictableEntityUUID];
if (oldestEvictableEntityUUID) {
[self deleteEntryForEntityUUID:oldestEvictableEntityUUID];
index = [self _nextEntryIndex];
}
}
if (index >= [self _maximumCount]) {
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]];
[self.imageCache _logMessage:message];
}
return index;
}
- (NSString *)oldestEvictableEntityUUID {
NSString *uuid = nil;
for (NSInteger i = [_MRUEntries count] - 1; i >= 0; i--) {
NSString *candidateUUID = [_MRUEntries objectAtIndex:i];
if (![_inUseEntries containsObject:candidateUUID]) {
uuid = candidateUUID;
break;
}
}
return uuid;
}
- (NSInteger)_indexOfEntryForEntityUUID:(NSString *)entityUUID {
NSInteger index = NSNotFound;
if (_indexMap != nil && entityUUID != nil) {
NSNumber *indexNumber = [_indexMap objectForKey:entityUUID];
index = indexNumber ? [indexNumber integerValue] : NSNotFound;
if (index != NSNotFound && index >= _entryCount) {
[_indexMap removeObjectForKey:entityUUID];
[_occupiedIndexes removeIndex:index];
[_sourceImageMap removeObjectForKey:entityUUID];
index = NSNotFound;
}
}
return index;
}
- (FICImageTableEntry *)_entryDataForEntityUUID:(NSString *)entityUUID {
FICImageTableEntry *entryData = nil;
NSInteger index = [self _indexOfEntryForEntityUUID:entityUUID];
if (index != NSNotFound) {
entryData = [self _entryDataAtIndex:index];
}
return entryData;
}
- (void)_entryWasAccessedWithEntityUUID:(NSString *)entityUUID {
// Update MRU array
NSInteger index = [_MRUEntries indexOfObject:entityUUID];
if (index == NSNotFound) {
[_MRUEntries insertObject:entityUUID atIndex:0];
} else if (index != 0) {
[_MRUEntries removeObjectAtIndex:index];
[_MRUEntries insertObject:entityUUID atIndex:0];
}
}
// Unchanging pointer value for a given entry index to synchronize on
- (NSNumber *)_numberForEntryAtIndex:(NSInteger)index {
NSNumber *resultNumber = (__bridge id)CFDictionaryGetValue(_indexNumbers, (const void *)index);
if (!resultNumber) {
resultNumber = [NSNumber numberWithInteger:index];
CFDictionarySetValue(_indexNumbers, (const void *)index, (__bridge void *)resultNumber);
}
return resultNumber;
}
#pragma mark - Working with Metadata
- (void)saveMetadata {
@autoreleasepool {
[_lock lock];
NSDictionary *metadataDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
[_indexMap copy], FICImageTableIndexMapKey,
[_sourceImageMap copy], FICImageTableContextMapKey,
[[_MRUEntries array] copy], FICImageTableMRUArrayKey,
[_imageFormatDictionary copy], FICImageTableFormatKey, nil];
__block int32_t metadataVersion = OSAtomicIncrement32(&_metadataVersion);
[_lock unlock];
static dispatch_queue_t __metadataQueue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__metadataQueue = dispatch_queue_create("com.path.FastImageCache.ImageTableMetadataQueue", NULL);
});
dispatch_async(__metadataQueue, ^{
// Cancel serialization if a new metadata version is queued to be saved
if (metadataVersion != _metadataVersion) {
return;
}
@autoreleasepool {
NSData *data = [NSJSONSerialization dataWithJSONObject:metadataDictionary options:kNilOptions error:NULL];
// Cancel disk writing if a new metadata version is queued to be saved
if (metadataVersion != _metadataVersion) {
return;
}
BOOL fileWriteResult = [data writeToFile:[self metadataFilePath] atomically:NO];
if (fileWriteResult == NO) {
NSString *message = [NSString stringWithFormat:@"*** FIC Error: %s couldn't write metadata for format %@", __PRETTY_FUNCTION__, [_imageFormat name]];
[self.imageCache _logMessage:message];
}
}
});
}
}
- (void)_loadMetadata {
NSString *metadataFilePath = [self metadataFilePath];
NSData *metadataData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:metadataFilePath] options:NSDataReadingMappedAlways error:NULL];
if (metadataData != nil) {
NSDictionary *metadataDictionary = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:metadataData options:kNilOptions error:NULL];
if (!metadataDictionary) {
// The image table was likely previously stored as a .plist
// We'll read it into memory as a .plist and later store it (during -saveMetadata) using NSJSONSerialization for performance reasons
metadataDictionary = (NSDictionary *)[NSPropertyListSerialization propertyListWithData:metadataData options:0 format:NULL error:NULL];
}
NSDictionary *formatDictionary = [metadataDictionary objectForKey:FICImageTableFormatKey];
if ([formatDictionary isEqualToDictionary:_imageFormatDictionary] == NO) {
// Something about this image format has changed, so the existing metadata is no longer valid. The image table file
// must be deleted and recreated.
[[NSFileManager defaultManager] removeItemAtPath:_filePath error:NULL];
[[NSFileManager defaultManager] removeItemAtPath:metadataFilePath error:NULL];
metadataDictionary = nil;
NSString *message = [NSString stringWithFormat:@"*** FIC Notice: Image format %@ has changed; deleting data and starting over.", [_imageFormat name]];
[self.imageCache _logMessage:message];
}
[_indexMap setDictionary:[metadataDictionary objectForKey:FICImageTableIndexMapKey]];
for (NSNumber *index in [_indexMap allValues]) {
[_occupiedIndexes addIndex:[index intValue]];
}
[_sourceImageMap setDictionary:[metadataDictionary objectForKey:FICImageTableContextMapKey]];
[_MRUEntries removeAllObjects];
NSArray *mruArray = [metadataDictionary objectForKey:FICImageTableMRUArrayKey];
if (mruArray) {
[_MRUEntries addObjectsFromArray:mruArray];
}
}
}
#pragma mark - Resetting the Image Table
- (void)reset {
[_lock lock];
[_indexMap removeAllObjects];
[_occupiedIndexes removeAllIndexes];
[_inUseEntries removeAllObjects];
[_MRUEntries removeAllObjects];
[_sourceImageMap removeAllObjects];
[_chunkDictionary removeAllObjects];
[_chunkSet removeAllObjects];
[self _setEntryCount:0];
[self saveMetadata];
[_lock unlock];
}
@end
================================================
FILE: FastImageCache/FastImageCache/FastImageCache/FICImageTableChunk.h
================================================
//
// FICImageTableChunk.h
// FastImageCache
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICImports.h"
NS_ASSUME_NONNULL_BEGIN
@class FICImageTable;
/**
`FICImageTableChunk` represents a contiguous portion of image table file data.
*/
@interface FICImageTableChunk : NSObject
///-----------------------------------
/// @name Image Table Chunk Properties
///-----------------------------------
/**
The bytes of file data contained in the chunk.
@discussion `FICImageTableChunk` maps file data directly to `bytes`, so no memory copy occurs.
*/
@property (nonatomic, assign, readonly) void *bytes;
/**
The index of the chunk in the image table file.
*/
@property (nonatomic, assign, readonly) NSInteger index;
/**
The offset in the image table file where the chunk begins.
*/
@property (nonatomic, assign, readonly) off_t fileOffset;
/**
The length, in bytes, of the chunk.
*/
@property (nonatomic, assign, readonly) size_t length;
///----------------------------------------
/// @name Initializing an Image Table Chunk
///----------------------------------------
/**
Initializes a new image table chunk.
@param fileDescriptor The image table's file descriptor to map from.
@param index The index of the chunk.
@param length The length, in bytes, of the chunk.
@return A new image table chunk.
*/
- (nullable instancetype)initWithFileDescriptor:(int)fileDescriptor index:(NSInteger)index length:(size_t)length;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: FastImageCache/FastImageCache/FastImageCache/FICImageTableChunk.m
================================================
//
// FICImageTableChunk.m
// FastImageCache
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICImageTableChunk.h"
#import <sys/mman.h>
#pragma mark - Class Extension
@interface FICImageTableChunk () {
NSInteger _index;
void *_bytes;
size_t _length;
off_t _fileOffset;
}
@end
#pragma mark
@implementation FICImageTableChunk
@synthesize bytes = _bytes;
@synthesize fileOffset = _fileOffset;
@synthesize length = _length;
#pragma mark - Object Lifecycle
- (instancetype)initWithFileDescriptor:(int)fileDescriptor index:(NSInteger)index length:(size_t)length {
self = [super init];
if (self != nil) {
_index = index;
_length = length;
_fileOffset = _index * _length;
_bytes = mmap(NULL, _length, (PROT_READ|PROT_WRITE), (MAP_FILE|MAP_SHARED), fileDescriptor, _fileOffset);
if (_bytes == MAP_FAILED) {
NSLog(@"Failed to map chunk. errno=%d", errno);
_bytes = NULL;
self = nil;
}
}
return self;
}
- (void)dealloc {
if (_bytes != NULL) {
munmap(_bytes, _length);
}
}
@end
================================================
FILE: FastImageCache/FastImageCache/FastImageCache/FICImageTableEntry.h
================================================
//
// FICImageTableEntry.h
// FastImageCache
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICImports.h"
NS_ASSUME_NONNULL_BEGIN
@class FICImageTableChunk;
@class FICImageCache;
typedef struct {
CFUUIDBytes _entityUUIDBytes;
CFUUIDBytes _sourceImageUUIDBytes;
} FICImageTableEntryMetadata;
/**
`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
`<FICImageTableChunk>`.
*/
@interface FICImageTableEntry : NSObject
///---------------------------------------------
/// @name Accessing Image Table Entry Properties
///---------------------------------------------
/**
The length, in bytes, of the entry data.
@discussion Entries begin with the image data, followed by the metadata struct.
*/
@property (nonatomic, assign, readonly) size_t length;
/**
The length, in bytes, of just the image data.
*/
@property (nonatomic, assign, readonly) size_t imageLength;
/**
The bytes that represent the entry data.
*/
@property (nonatomic, assign, readonly) void *bytes;
/**
The entity UUID, in byte form, associated with the entry.
*/
@property (nonatomic, assign) CFUUIDBytes entityUUIDBytes;
/**
The source image UUID, in byte form, associated with the entry.
*/
@property (nonatomic, assign) CFUUIDBytes sourceImageUUIDBytes;
/**
The image table chunk that contains this entry.
*/
@property (nonatomic, readonly) FICImageTableChunk *imageTableChunk;
/**
A weak reference to the image cache that contains the image table chunk that contains this entry.
*/
@property (nonatomic, weak) FICImageCache *imageCache;
/**
The index where this entry exists in the image table.
*/
@property (nonatomic, assign) NSInteger index;
///----------------------------------
/// @name Image Table Entry Lifecycle
///----------------------------------
/**
Initializes a new image table entry from an image table chunk.
@param imageTableChunk The image table chunk that contains the entry data.
@param bytes The bytes from the chunk that contain the entry data.
@param length The length, in bytes, of the entry data.
@return A new image table entry.
*/
- (nullable instancetype)initWithImageTableChunk:(FICImageTableChunk *)imageTableChunk bytes:(void *)bytes length:(size_t)length;
/**
Adds a block to be executed when this image table entry is deallocated.
@param block A block that will be called when this image table entry is deallocated.
@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.
*/
- (void)executeBlockOnDealloc:(dispatch_block_t)block;
/**
Forces the kernel to page in the memory-mapped, on-disk data backing this entry right away.
*/
- (void)preheat;
///--------------------------------------------
/// @name Flushing a Modified Image Table Entry
///--------------------------------------------
/**
Writes a modified image table entry back to disk.
*/
- (void)flush;
///--------------------------------------------
/// @name Versioning Image Table Entry Metadata
///--------------------------------------------
/**
Returns the current metadata version for image table entries.
@return The integer version number of the current metadata version.
@discussion Whenever the `<FICImageTableEntryMetadata>` struct is changed in any way, the metadata version must be changed.
*/
+ (NSInteger)metadataVersion;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: FastImageCache/FastImageCache/FastImageCache/FICImageTableEntry.m
================================================
//
// FICImageTableEntry.m
// FastImageCache
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICImageTableEntry.h"
#import "FICImageTable.h"
#import "FICImageTableChunk.h"
#import "FICImageCache.h"
#import "FICImageCache+FICErrorLogging.h"
#import <sys/mman.h>
#pragma mark Class Extension
@interface FICImageTableEntry () {
FICImageTableChunk *_imageTableChunk;
void *_bytes;
size_t _length;
NSMutableArray *_deallocBlocks;
NSInteger _index;
}
@end
#pragma mark
@implementation FICImageTableEntry
@synthesize bytes = _bytes;
@synthesize length = _length;
@synthesize imageTableChunk = _imageTableChunk;
@synthesize index = _index;
@synthesize imageCache;
#pragma mark - Property Accessors
- (size_t)imageLength {
return _length - sizeof(FICImageTableEntryMetadata);
}
- (CFUUIDBytes)entityUUIDBytes {
return [self _metadata]->_entityUUIDBytes;
}
- (void)setEntityUUIDBytes:(CFUUIDBytes)entityUUIDBytes {
[self _metadata]->_entityUUIDBytes = entityUUIDBytes;
}
- (CFUUIDBytes)sourceImageUUIDBytes {
return [self _metadata]->_sourceImageUUIDBytes;
}
- (void)setSourceImageUUIDBytes:(CFUUIDBytes)sourceImageUUIDBytes {
[self _metadata]->_sourceImageUUIDBytes = sourceImageUUIDBytes;
}
#pragma mark - Object Lifecycle
- (id)initWithImageTableChunk:(FICImageTableChunk *)imageTableChunk bytes:(void *)bytes length:(size_t)length {
self = [super init];
if (self != nil) {
// Safety check
void *entryMax = bytes + length;
void *chunkMax = [imageTableChunk bytes] + [imageTableChunk length];
if (entryMax > chunkMax) {
self = nil;
} else {
_imageTableChunk = imageTableChunk;
_bytes = bytes;
_length = length;
_deallocBlocks = [[NSMutableArray alloc] init];
}
}
return self;
}
- (void)executeBlockOnDealloc:(dispatch_block_t)block {
[_deallocBlocks addObject:[block copy]];
}
- (void)dealloc {
for (dispatch_block_t block in _deallocBlocks) {
dispatch_async([FICImageCache dispatchQueue], block);
}
}
#pragma mark - Other Accessors
+ (NSInteger)metadataVersion {
return 8;
}
- (FICImageTableEntryMetadata *)_metadata {
return (FICImageTableEntryMetadata *)(_bytes + [self imageLength]);
}
#pragma mark - Flushing a Modified Image Table Entry
- (void)flush {
int pageSize = [FICImageTable pageSize];
void *address = _bytes;
size_t pageIndex = (size_t)address / pageSize;
void *pageAlignedAddress = (void *)(pageIndex * pageSize);
size_t bytesBeforeData = address - pageAlignedAddress;
size_t bytesToFlush = (bytesBeforeData + _length);
int result = msync(pageAlignedAddress, bytesToFlush, MS_SYNC);
if (result) {
NSString *message = [NSString stringWithFormat:@"*** FIC Error: %s msync(%p, %ld) returned %d errno=%d", __PRETTY_FUNCTION__, pageAlignedAddress, bytesToFlush, result, errno];
[self.imageCache _logMessage:message];
}
}
- (void)preheat {
int pageSize = [FICImageTable pageSize];
void *bytes = [self bytes];
NSUInteger length = [self length];
// Read a byte off of each VM page to force the kernel to page in the data
for (NSUInteger i = 0; i < length; i += pageSize) {
*((volatile uint8_t *)bytes + i);
}
}
@end
================================================
FILE: FastImageCache/FastImageCache/FastImageCache/FICImports.h
================================================
//
// FICImports.h
// FastImageCache
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import <UIKit/UIKit.h>
================================================
FILE: FastImageCache/FastImageCache/FastImageCache/FICUtilities.h
================================================
//
// FICUtilities.h
// FastImageCache
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICImports.h"
size_t FICByteAlign(size_t bytesPerRow, size_t alignment);
size_t FICByteAlignForCoreAnimation(size_t bytesPerRow);
NSString * _Nullable FICStringWithUUIDBytes(CFUUIDBytes UUIDBytes);
CFUUIDBytes FICUUIDBytesWithString(NSString * _Nonnull string);
CFUUIDBytes FICUUIDBytesFromMD5HashOfString(NSString * _Nonnull MD5Hash); // Useful for computing an entity's UUID from a URL, for example
================================================
FILE: FastImageCache/FastImageCache/FastImageCache/FICUtilities.m
================================================
//
// FICUtilities.m
// FastImageCache
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICUtilities.h"
#import <CommonCrypto/CommonDigest.h>
#pragma mark Internal Definitions
// Core Animation will make a copy of any image that a client application provides whose backing store isn't properly byte-aligned.
// This copy operation can be prohibitively expensive, so we want to avoid this by properly aligning any UIImages we're working with.
// 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.
#pragma mark - Byte Alignment
inline size_t FICByteAlign(size_t width, size_t alignment) {
return ((width + (alignment - 1)) / alignment) * alignment;
}
inline size_t FICByteAlignForCoreAnimation(size_t bytesPerRow) {
return FICByteAlign(bytesPerRow, 64);
}
#pragma mark - Strings and UUIDs
NSString * FICStringWithUUIDBytes(CFUUIDBytes UUIDBytes) {
NSString *UUIDString = nil;
CFUUIDRef UUIDRef = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, UUIDBytes);
if (UUIDRef != NULL) {
UUIDString = (__bridge_transfer NSString *)CFUUIDCreateString(kCFAllocatorDefault, UUIDRef);
CFRelease(UUIDRef);
}
return UUIDString;
}
CFUUIDBytes FICUUIDBytesWithString(NSString *string) {
CFUUIDBytes UUIDBytes;
CFUUIDRef UUIDRef = CFUUIDCreateFromString(kCFAllocatorDefault, (CFStringRef)string);
if (UUIDRef != NULL) {
UUIDBytes = CFUUIDGetUUIDBytes(UUIDRef);
CFRelease(UUIDRef);
}
return UUIDBytes;
}
CFUUIDBytes FICUUIDBytesFromMD5HashOfString(NSString *MD5Hash) {
const char *UTF8String = [MD5Hash UTF8String];
CFUUIDBytes UUIDBytes;
CC_MD5(UTF8String, (CC_LONG)strlen(UTF8String), (unsigned char*)&UUIDBytes);
return UUIDBytes;
}
================================================
FILE: FastImageCache/FastImageCache/FastImageCache.h
================================================
//
// FastImageCache.h
// FastImageCache
//
// Created by Rui Peres on 17/06/2015.
// Copyright (c) 2015 Path. All rights reserved.
//
#import <UIKit/UIKit.h>
//! Project version number for FastImageCache.
FOUNDATION_EXPORT double FastImageCacheVersionNumber;
//! Project version string for FastImageCache.
FOUNDATION_EXPORT const unsigned char FastImageCacheVersionString[];
#import <FastImageCache/FICImageCache.h>
#import <FastImageCache/FICEntity.h>
#import <FastImageCache/FICUtilities.h>
================================================
FILE: FastImageCache/FastImageCache/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
================================================
FILE: FastImageCache/FastImageCache.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
B2E5676E1B316D5800906840 /* FastImageCache.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E5676D1B316D5800906840 /* FastImageCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
B2E567741B316D5800906840 /* FastImageCache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2E567681B316D5800906840 /* FastImageCache.framework */; };
B2E5677B1B316D5800906840 /* FastImageCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5677A1B316D5800906840 /* FastImageCacheTests.m */; };
B2E567941B316D9600906840 /* FICEntity.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E567851B316D9600906840 /* FICEntity.h */; settings = {ATTRIBUTES = (Public, ); }; };
B2E567951B316D9600906840 /* FICImageCache+FICErrorLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E567861B316D9600906840 /* FICImageCache+FICErrorLogging.h */; };
B2E567961B316D9600906840 /* FICImageCache.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E567871B316D9600906840 /* FICImageCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
B2E567971B316D9600906840 /* FICImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567881B316D9600906840 /* FICImageCache.m */; };
B2E567981B316D9600906840 /* FICImageFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E567891B316D9600906840 /* FICImageFormat.h */; settings = {ATTRIBUTES = (Public, ); }; };
B2E567991B316D9600906840 /* FICImageFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5678A1B316D9600906840 /* FICImageFormat.m */; };
B2E5679A1B316D9600906840 /* FICImageTable.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E5678B1B316D9600906840 /* FICImageTable.h */; };
B2E5679B1B316D9600906840 /* FICImageTable.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5678C1B316D9600906840 /* FICImageTable.m */; };
B2E5679C1B316D9600906840 /* FICImageTableChunk.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E5678D1B316D9600906840 /* FICImageTableChunk.h */; };
B2E5679D1B316D9600906840 /* FICImageTableChunk.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5678E1B316D9600906840 /* FICImageTableChunk.m */; };
B2E5679E1B316D9600906840 /* FICImageTableEntry.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E5678F1B316D9600906840 /* FICImageTableEntry.h */; };
B2E5679F1B316D9600906840 /* FICImageTableEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567901B316D9600906840 /* FICImageTableEntry.m */; };
B2E567A01B316D9600906840 /* FICImports.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E567911B316D9600906840 /* FICImports.h */; settings = {ATTRIBUTES = (Public, ); }; };
B2E567A11B316D9600906840 /* FICUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E567921B316D9600906840 /* FICUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; };
B2E567A21B316D9600906840 /* FICUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567931B316D9600906840 /* FICUtilities.m */; };
B2E567AC1B316DCA00906840 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567AB1B316DCA00906840 /* main.m */; };
B2E567DA1B316E1000906840 /* FICDAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567CF1B316E1000906840 /* FICDAppDelegate.m */; };
B2E567DB1B316E1000906840 /* FICDFullscreenPhotoDisplayController.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567D11B316E1000906840 /* FICDFullscreenPhotoDisplayController.m */; };
B2E567DC1B316E1000906840 /* FICDPhoto.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567D31B316E1000906840 /* FICDPhoto.m */; };
B2E567DD1B316E1000906840 /* FICDPhotosTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567D51B316E1000906840 /* FICDPhotosTableViewCell.m */; };
B2E567DE1B316E1000906840 /* FICDTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567D71B316E1000906840 /* FICDTableView.m */; };
B2E567DF1B316E1000906840 /* FICDViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567D91B316E1000906840 /* FICDViewController.m */; };
B2E567E41B316E2200906840 /* fetch_demo_images.sh in Resources */ = {isa = PBXBuildFile; fileRef = B2E567E31B316E2200906840 /* fetch_demo_images.sh */; };
B2E567E61B316E3700906840 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B2E567E51B316E3700906840 /* Assets.xcassets */; };
B2E567E71B316E5F00906840 /* FICUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567931B316D9600906840 /* FICUtilities.m */; };
B2E567E81B316E6600906840 /* FICImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567881B316D9600906840 /* FICImageCache.m */; };
B2E567E91B316E6600906840 /* FICImageFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5678A1B316D9600906840 /* FICImageFormat.m */; };
B2E567EA1B316E6600906840 /* FICImageTable.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5678C1B316D9600906840 /* FICImageTable.m */; };
B2E567EB1B316E6600906840 /* FICImageTableChunk.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5678E1B316D9600906840 /* FICImageTableChunk.m */; };
B2E567EC1B316E6600906840 /* FICImageTableEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E567901B316D9600906840 /* FICImageTableEntry.m */; };
BFD6BFFB1B68FD5D005292DC /* Demo Images in Resources */ = {isa = PBXBuildFile; fileRef = BFD6BFFA1B68FD5D005292DC /* Demo Images */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
B2E567751B316D5800906840 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = B2E5675F1B316D5800906840 /* Project object */;
proxyType = 1;
remoteGlobalIDString = B2E567671B316D5800906840;
remoteInfo = FastImageCache;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
B2E567681B316D5800906840 /* FastImageCache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FastImageCache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B2E5676C1B316D5800906840 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B2E5676D1B316D5800906840 /* FastImageCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FastImageCache.h; sourceTree = "<group>"; };
B2E567731B316D5800906840 /* FastImageCacheTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FastImageCacheTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
B2E567791B316D5800906840 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B2E5677A1B316D5800906840 /* FastImageCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FastImageCacheTests.m; sourceTree = "<group>"; };
B2E567851B316D9600906840 /* FICEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICEntity.h; sourceTree = "<group>"; };
B2E567861B316D9600906840 /* FICImageCache+FICErrorLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FICImageCache+FICErrorLogging.h"; sourceTree = "<group>"; };
B2E567871B316D9600906840 /* FICImageCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICImageCache.h; sourceTree = "<group>"; };
B2E567881B316D9600906840 /* FICImageCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICImageCache.m; sourceTree = "<group>"; };
B2E567891B316D9600906840 /* FICImageFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICImageFormat.h; sourceTree = "<group>"; };
B2E5678A1B316D9600906840 /* FICImageFormat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICImageFormat.m; sourceTree = "<group>"; };
B2E5678B1B316D9600906840 /* FICImageTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICImageTable.h; sourceTree = "<group>"; };
B2E5678C1B316D9600906840 /* FICImageTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICImageTable.m; sourceTree = "<group>"; };
B2E5678D1B316D9600906840 /* FICImageTableChunk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICImageTableChunk.h; sourceTree = "<group>"; };
B2E5678E1B316D9600906840 /* FICImageTableChunk.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICImageTableChunk.m; sourceTree = "<group>"; };
B2E5678F1B316D9600906840 /* FICImageTableEntry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICImageTableEntry.h; sourceTree = "<group>"; };
B2E567901B316D9600906840 /* FICImageTableEntry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICImageTableEntry.m; sourceTree = "<group>"; };
B2E567911B316D9600906840 /* FICImports.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICImports.h; sourceTree = "<group>"; };
B2E567921B316D9600906840 /* FICUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICUtilities.h; sourceTree = "<group>"; };
B2E567931B316D9600906840 /* FICUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICUtilities.m; sourceTree = "<group>"; };
B2E567A71B316DCA00906840 /* FastImageCacheDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FastImageCacheDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
B2E567AA1B316DCA00906840 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B2E567AB1B316DCA00906840 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
B2E567CE1B316E1000906840 /* FICDAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICDAppDelegate.h; sourceTree = "<group>"; };
B2E567CF1B316E1000906840 /* FICDAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICDAppDelegate.m; sourceTree = "<group>"; };
B2E567D01B316E1000906840 /* FICDFullscreenPhotoDisplayController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICDFullscreenPhotoDisplayController.h; sourceTree = "<group>"; };
B2E567D11B316E1000906840 /* FICDFullscreenPhotoDisplayController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICDFullscreenPhotoDisplayController.m; sourceTree = "<group>"; };
B2E567D21B316E1000906840 /* FICDPhoto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICDPhoto.h; sourceTree = "<group>"; };
B2E567D31B316E1000906840 /* FICDPhoto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICDPhoto.m; sourceTree = "<group>"; };
B2E567D41B316E1000906840 /* FICDPhotosTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICDPhotosTableViewCell.h; sourceTree = "<group>"; };
B2E567D51B316E1000906840 /* FICDPhotosTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICDPhotosTableViewCell.m; sourceTree = "<group>"; };
B2E567D61B316E1000906840 /* FICDTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICDTableView.h; sourceTree = "<group>"; };
B2E567D71B316E1000906840 /* FICDTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICDTableView.m; sourceTree = "<group>"; };
B2E567D81B316E1000906840 /* FICDViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FICDViewController.h; sourceTree = "<group>"; };
B2E567D91B316E1000906840 /* FICDViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FICDViewController.m; sourceTree = "<group>"; };
B2E567E31B316E2200906840 /* fetch_demo_images.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = fetch_demo_images.sh; sourceTree = "<group>"; };
B2E567E51B316E3700906840 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
B2E567ED1B316EBF00906840 /* FastImageCacheDemo-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FastImageCacheDemo-Prefix.pch"; sourceTree = "<group>"; };
BFD6BFFA1B68FD5D005292DC /* Demo Images */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Demo Images"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
B2E567641B316D5800906840 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
B2E567701B316D5800906840 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B2E567741B316D5800906840 /* FastImageCache.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
B2E567A41B316DCA00906840 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
B2E5675E1B316D5800906840 = {
isa = PBXGroup;
children = (
B2E5676A1B316D5800906840 /* FastImageCache */,
B2E567771B316D5800906840 /* FastImageCacheTests */,
B2E567A81B316DCA00906840 /* FastImageCacheDemo */,
B2E567691B316D5800906840 /* Products */,
);
sourceTree = "<group>";
};
B2E567691B316D5800906840 /* Products */ = {
isa = PBXGroup;
children = (
B2E567681B316D5800906840 /* FastImageCache.framework */,
B2E567731B316D5800906840 /* FastImageCacheTests.xctest */,
B2E567A71B316DCA00906840 /* FastImageCacheDemo.app */,
);
name = Products;
sourceTree = "<group>";
};
B2E5676A1B316D5800906840 /* FastImageCache */ = {
isa = PBXGroup;
children = (
B2E567841B316D9600906840 /* FastImageCache */,
B2E5676D1B316D5800906840 /* FastImageCache.h */,
B2E5676B1B316D5800906840 /* Supporting Files */,
);
path = FastImageCache;
sourceTree = "<group>";
};
B2E5676B1B316D5800906840 /* Supporting Files */ = {
isa = PBXGroup;
children = (
B2E5676C1B316D5800906840 /* Info.plist */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
B2E567771B316D5800906840 /* FastImageCacheTests */ = {
isa = PBXGroup;
children = (
B2E5677A1B316D5800906840 /* FastImageCacheTests.m */,
B2E567781B316D5800906840 /* Supporting Files */,
);
path = FastImageCacheTests;
sourceTree = "<group>";
};
B2E567781B316D5800906840 /* Supporting Files */ = {
isa = PBXGroup;
children = (
B2E567791B316D5800906840 /* Info.plist */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
B2E567841B316D9600906840 /* FastImageCache */ = {
isa = PBXGroup;
children = (
B2E567851B316D9600906840 /* FICEntity.h */,
B2E567861B316D9600906840 /* FICImageCache+FICErrorLogging.h */,
B2E567871B316D9600906840 /* FICImageCache.h */,
B2E567881B316D9600906840 /* FICImageCache.m */,
B2E567891B316D9600906840 /* FICImageFormat.h */,
B2E5678A1B316D9600906840 /* FICImageFormat.m */,
B2E5678B1B316D9600906840 /* FICImageTable.h */,
B2E5678C1B316D9600906840 /* FICImageTable.m */,
B2E5678D1B316D9600906840 /* FICImageTableChunk.h */,
B2E5678E1B316D9600906840 /* FICImageTableChunk.m */,
B2E5678F1B316D9600906840 /* FICImageTableEntry.h */,
B2E567901B316D9600906840 /* FICImageTableEntry.m */,
B2E567911B316D9600906840 /* FICImports.h */,
B2E567921B316D9600906840 /* FICUtilities.h */,
B2E567931B316D9600906840 /* FICUtilities.m */,
);
path = FastImageCache;
sourceTree = "<group>";
};
B2E567A81B316DCA00906840 /* FastImageCacheDemo */ = {
isa = PBXGroup;
children = (
B2E567E31B316E2200906840 /* fetch_demo_images.sh */,
BFD6BFFA1B68FD5D005292DC /* Demo Images */,
B2E567CD1B316E1000906840 /* Classes */,
B2E567A91B316DCA00906840 /* Supporting Files */,
);
path = FastImageCacheDemo;
sourceTree = "<group>";
};
B2E567A91B316DCA00906840 /* Supporting Files */ = {
isa = PBXGroup;
children = (
B2E567ED1B316EBF00906840 /* FastImageCacheDemo-Prefix.pch */,
B2E567E51B316E3700906840 /* Assets.xcassets */,
B2E567AA1B316DCA00906840 /* Info.plist */,
B2E567AB1B316DCA00906840 /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
B2E567CD1B316E1000906840 /* Classes */ = {
isa = PBXGroup;
children = (
B2E567CE1B316E1000906840 /* FICDAppDelegate.h */,
B2E567CF1B316E1000906840 /* FICDAppDelegate.m */,
B2E567D01B316E1000906840 /* FICDFullscreenPhotoDisplayController.h */,
B2E567D11B316E1000906840 /* FICDFullscreenPhotoDisplayController.m */,
B2E567D21B316E1000906840 /* FICDPhoto.h */,
B2E567D31B316E1000906840 /* FICDPhoto.m */,
B2E567D41B316E1000906840 /* FICDPhotosTableViewCell.h */,
B2E567D51B316E1000906840 /* FICDPhotosTableViewCell.m */,
B2E567D61B316E1000906840 /* FICDTableView.h */,
B2E567D71B316E1000906840 /* FICDTableView.m */,
B2E567D81B316E1000906840 /* FICDViewController.h */,
B2E567D91B316E1000906840 /* FICDViewController.m */,
);
path = Classes;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
B2E567651B316D5800906840 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
B2E5679A1B316D9600906840 /* FICImageTable.h in Headers */,
B2E5679E1B316D9600906840 /* FICImageTableEntry.h in Headers */,
B2E567941B316D9600906840 /* FICEntity.h in Headers */,
B2E567A01B316D9600906840 /* FICImports.h in Headers */,
B2E567981B316D9600906840 /* FICImageFormat.h in Headers */,
B2E567A11B316D9600906840 /* FICUtilities.h in Headers */,
B2E5679C1B316D9600906840 /* FICImageTableChunk.h in Headers */,
B2E567951B316D9600906840 /* FICImageCache+FICErrorLogging.h in Headers */,
B2E567961B316D9600906840 /* FICImageCache.h in Headers */,
B2E5676E1B316D5800906840 /* FastImageCache.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
B2E567671B316D5800906840 /* FastImageCache */ = {
isa = PBXNativeTarget;
buildConfigurationList = B2E5677E1B316D5800906840 /* Build configuration list for PBXNativeTarget "FastImageCache" */;
buildPhases = (
B2E567631B316D5800906840 /* Sources */,
B2E567641B316D5800906840 /* Frameworks */,
B2E567651B316D5800906840 /* Headers */,
B2E567661B316D5800906840 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = FastImageCache;
productName = FastImageCache;
productReference = B2E567681B316D5800906840 /* FastImageCache.framework */;
productType = "com.apple.product-type.framework";
};
B2E567721B316D5800906840 /* FastImageCacheTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = B2E567811B316D5800906840 /* Build configuration list for PBXNativeTarget "FastImageCacheTests" */;
buildPhases = (
B2E5676F1B316D5800906840 /* Sources */,
B2E567701B316D5800906840 /* Frameworks */,
B2E567711B316D5800906840 /* Resources */,
);
buildRules = (
);
dependencies = (
B2E567761B316D5800906840 /* PBXTargetDependency */,
);
name = FastImageCacheTests;
productName = FastImageCacheTests;
productReference = B2E567731B316D5800906840 /* FastImageCacheTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
B2E567A61B316DCA00906840 /* FastImageCacheDemo */ = {
isa = PBXNativeTarget;
buildConfigurationList = B2E567C71B316DCB00906840 /* Build configuration list for PBXNativeTarget "FastImageCacheDemo" */;
buildPhases = (
B2E567A31B316DCA00906840 /* Sources */,
B2E567A41B316DCA00906840 /* Frameworks */,
B2E567A51B316DCA00906840 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = FastImageCacheDemo;
productName = FastImageCacheDemo;
productReference = B2E567A71B316DCA00906840 /* FastImageCacheDemo.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
B2E5675F1B316D5800906840 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0720;
ORGANIZATIONNAME = Path;
TargetAttributes = {
B2E567671B316D5800906840 = {
CreatedOnToolsVersion = 6.3.2;
};
B2E567721B316D5800906840 = {
CreatedOnToolsVersion = 6.3.2;
};
B2E567A61B316DCA00906840 = {
CreatedOnToolsVersion = 6.3.2;
};
};
};
buildConfigurationList = B2E567621B316D5800906840 /* Build configuration list for PBXProject "FastImageCache" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = B2E5675E1B316D5800906840;
productRefGroup = B2E567691B316D5800906840 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
B2E567671B316D5800906840 /* FastImageCache */,
B2E567721B316D5800906840 /* FastImageCacheTests */,
B2E567A61B316DCA00906840 /* FastImageCacheDemo */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
B2E567661B316D5800906840 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
B2E567711B316D5800906840 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
B2E567A51B316DCA00906840 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B2E567E41B316E2200906840 /* fetch_demo_images.sh in Resources */,
BFD6BFFB1B68FD5D005292DC /* Demo Images in Resources */,
B2E567E61B316E3700906840 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
B2E567631B316D5800906840 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B2E5679B1B316D9600906840 /* FICImageTable.m in Sources */,
B2E5679D1B316D9600906840 /* FICImageTableChunk.m in Sources */,
B2E567971B316D9600906840 /* FICImageCache.m in Sources */,
B2E567A21B316D9600906840 /* FICUtilities.m in Sources */,
B2E5679F1B316D9600906840 /* FICImageTableEntry.m in Sources */,
B2E567991B316D9600906840 /* FICImageFormat.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
B2E5676F1B316D5800906840 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B2E5677B1B316D5800906840 /* FastImageCacheTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
B2E567A31B316DCA00906840 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B2E567DA1B316E1000906840 /* FICDAppDelegate.m in Sources */,
B2E567EB1B316E6600906840 /* FICImageTableChunk.m in Sources */,
B2E567E91B316E6600906840 /* FICImageFormat.m in Sources */,
B2E567AC1B316DCA00906840 /* main.m in Sources */,
B2E567EA1B316E6600906840 /* FICImageTable.m in Sources */,
B2E567DD1B316E1000906840 /* FICDPhotosTableViewCell.m in Sources */,
B2E567E81B316E6600906840 /* FICImageCache.m in Sources */,
B2E567EC1B316E6600906840 /* FICImageTableEntry.m in Sources */,
B2E567DF1B316E1000906840 /* FICDViewController.m in Sources */,
B2E567DC1B316E1000906840 /* FICDPhoto.m in Sources */,
B2E567DB1B316E1000906840 /* FICDFullscreenPhotoDisplayController.m in Sources */,
B2E567DE1B316E1000906840 /* FICDTableView.m in Sources */,
B2E567E71B316E5F00906840 /* FICUtilities.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
B2E567761B316D5800906840 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = B2E567671B316D5800906840 /* FastImageCache */;
targetProxy = B2E567751B316D5800906840 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
B2E5677C1B316D5800906840 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
B2E5677D1B316D5800906840 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
B2E5677F1B316D5800906840 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = FastImageCache/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.path.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Debug;
};
B2E567801B316D5800906840 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = FastImageCache/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.path.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
};
B2E567821B316D5800906840 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
);
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = FastImageCacheTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.path.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
B2E567831B316D5800906840 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
);
INFOPLIST_FILE = FastImageCacheTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.path.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
B2E567C81B316DCB00906840 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = Icon;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = FastImageCacheDemo/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.path.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
B2E567C91B316DCB00906840 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = Icon;
INFOPLIST_FILE = FastImageCacheDemo/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.path.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
B2E567621B316D5800906840 /* Build configuration list for PBXProject "FastImageCache" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B2E5677C1B316D5800906840 /* Debug */,
B2E5677D1B316D5800906840 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
B2E5677E1B316D5800906840 /* Build configuration list for PBXNativeTarget "FastImageCache" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B2E5677F1B316D5800906840 /* Debug */,
B2E567801B316D5800906840 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
B2E567811B316D5800906840 /* Build configuration list for PBXNativeTarget "FastImageCacheTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B2E567821B316D5800906840 /* Debug */,
B2E567831B316D5800906840 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
B2E567C71B316DCB00906840 /* Build configuration list for PBXNativeTarget "FastImageCacheDemo" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B2E567C81B316DCB00906840 /* Debug */,
B2E567C91B316DCB00906840 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = B2E5675F1B316D5800906840 /* Project object */;
}
================================================
FILE: FastImageCache/FastImageCache.xcodeproj/xcshareddata/xcschemes/FastImageCache.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0720"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B2E567671B316D5800906840"
BuildableName = "FastImageCache.framework"
BlueprintName = "FastImageCache"
ReferencedContainer = "container:FastImageCache.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B2E567721B316D5800906840"
BuildableName = "FastImageCacheTests.xctest"
BlueprintName = "FastImageCacheTests"
ReferencedContainer = "container:FastImageCache.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B2E567721B316D5800906840"
BuildableName = "FastImageCacheTests.xctest"
BlueprintName = "FastImageCacheTests"
ReferencedContainer = "container:FastImageCache.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B2E567671B316D5800906840"
BuildableName = "FastImageCache.framework"
BlueprintName = "FastImageCache"
ReferencedContainer = "container:FastImageCache.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B2E567671B316D5800906840"
BuildableName = "FastImageCache.framework"
BlueprintName = "FastImageCache"
ReferencedContainer = "container:FastImageCache.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B2E567671B316D5800906840"
BuildableName = "FastImageCache.framework"
BlueprintName = "FastImageCache"
ReferencedContainer = "container:FastImageCache.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: FastImageCache/FastImageCacheDemo/Assets.xcassets/Icon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"size" : "57x57",
"idiom" : "iphone",
"filename" : "iPhone-App.png",
"scale" : "1x"
},
{
"size" : "57x57",
"idiom" : "iphone",
"filename" : "iPhone-App@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "iPhone-App-iOS7@2x.png",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "50x50",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "50x50",
"scale" : "2x"
},
{
"size" : "72x72",
"idiom" : "ipad",
"filename" : "iPad-App.png",
"scale" : "1x"
},
{
"size" : "72x72",
"idiom" : "ipad",
"filename" : "iPad-App@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "iPad-App-iOS7.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "iPad-App-iOS7@2x.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"pre-rendered" : true
}
}
================================================
FILE: FastImageCache/FastImageCacheDemo/Assets.xcassets/Launch Image.launchimage/Contents.json
================================================
{
"images" : [
{
"orientation" : "portrait",
"idiom" : "iphone",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"filename" : "iPhone-Portrait-iOS7@2x.png",
"scale" : "2x"
},
{
"extent" : "full-screen",
"idiom" : "iphone",
"subtype" : "retina4",
"filename" : "iPhone-Portrait-R4@2x.png",
"minimum-system-version" : "7.0",
"orientation" : "portrait",
"scale" : "2x"
},
{
"orientation" : "portrait",
"idiom" : "ipad",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"filename" : "iPad-Portrait-iOS7.png",
"scale" : "1x"
},
{
"orientation" : "portrait",
"idiom" : "ipad",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"filename" : "iPad-Portrait-iOS7@2x.png",
"scale" : "2x"
},
{
"orientation" : "portrait",
"idiom" : "iphone",
"extent" : "full-screen",
"filename" : "iPhone-Portrait.png",
"scale" : "1x"
},
{
"orientation" : "portrait",
"idiom" : "iphone",
"extent" : "full-screen",
"filename" : "iPhone-Portrait@2x.png",
"scale" : "2x"
},
{
"orientation" : "portrait",
"idiom" : "iphone",
"extent" : "full-screen",
"filename" : "iPhone-Portrait-R4@2x-1.png",
"subtype" : "retina4",
"scale" : "2x"
},
{
"orientation" : "portrait",
"idiom" : "ipad",
"extent" : "to-status-bar",
"filename" : "iPad-Portrait-1004h.png",
"scale" : "1x"
},
{
"orientation" : "portrait",
"idiom" : "ipad",
"extent" : "to-status-bar",
"filename" : "iPad-Portrait-1004h@2x.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: FastImageCache/FastImageCacheDemo/Classes/FICDAppDelegate.h
================================================
//
// FICDAppDelegate.h
// FastImageCacheDemo
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import <UIKit/UIKit.h>
@class FICDViewController;
@interface FICDAppDelegate : UIResponder <UIApplicationDelegate>
@property (nonatomic, strong) UIWindow *window;
@property (nonatomic, strong) FICDViewController *viewController;
@end
================================================
FILE: FastImageCache/FastImageCacheDemo/Classes/FICDAppDelegate.m
================================================
//
// FICDAppDelegate.m
// FastImageCacheDemo
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICDAppDelegate.h"
#import "FICImageCache.h"
#import "FICDViewController.h"
#import "FICDPhoto.h"
#pragma mark Class Extension
@interface FICDAppDelegate () <FICImageCacheDelegate>
@end
#pragma mark
@implementation FICDAppDelegate
#pragma mark - Application Lifecycle
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSMutableArray *mutableImageFormats = [NSMutableArray array];
// Square image formats...
NSInteger squareImageFormatMaximumCount = 400;
FICImageFormatDevices squareImageFormatDevices = FICImageFormatDevicePhone | FICImageFormatDevicePad;
// ...32-bit BGR
FICImageFormat *squareImageFormat32BitBGRA = [FICImageFormat formatWithName:FICDPhotoSquareImage32BitBGRAFormatName family:FICDPhotoImageFormatFamily imageSize:FICDPhotoSquareImageSize style:FICImageFormatStyle32BitBGRA
maximumCount:squareImageFormatMaximumCount devices:squareImageFormatDevices protectionMode:FICImageFormatProtectionModeNone];
[mutableImageFormats addObject:squareImageFormat32BitBGRA];
// ...32-bit BGR
FICImageFormat *squareImageFormat32BitBGR = [FICImageFormat formatWithName:FICDPhotoSquareImage32BitBGRFormatName family:FICDPhotoImageFormatFamily imageSize:FICDPhotoSquareImageSize style:FICImageFormatStyle32BitBGR
maximumCount:squareImageFormatMaximumCount devices:squareImageFormatDevices protectionMode:FICImageFormatProtectionModeNone];
[mutableImageFormats addObject:squareImageFormat32BitBGR];
// ...16-bit BGR
FICImageFormat *squareImageFormat16BitBGR = [FICImageFormat formatWithName:FICDPhotoSquareImage16BitBGRFormatName family:FICDPhotoImageFormatFamily imageSize:FICDPhotoSquareImageSize style:FICImageFormatStyle16BitBGR
maximumCount:squareImageFormatMaximumCount devices:squareImageFormatDevices protectionMode:FICImageFormatProtectionModeNone];
[mutableImageFormats addObject:squareImageFormat16BitBGR];
// ...8-bit Grayscale
FICImageFormat *squareImageFormat8BitGrayscale = [FICImageFormat formatWithName:FICDPhotoSquareImage8BitGrayscaleFormatName family:FICDPhotoImageFormatFamily imageSize:FICDPhotoSquareImageSize style:FICImageFormatStyle8BitGrayscale
maximumCount:squareImageFormatMaximumCount devices:squareImageFormatDevices protectionMode:FICImageFormatProtectionModeNone];
[mutableImageFormats addObject:squareImageFormat8BitGrayscale];
if ([UIViewController instancesRespondToSelector:@selector(preferredStatusBarStyle)]) {
// Pixel image format
NSInteger pixelImageFormatMaximumCount = 1000;
FICImageFormatDevices pixelImageFormatDevices = FICImageFormatDevicePhone | FICImageFormatDevicePad;
FICImageFormat *pixelImageFormat = [FICImageFormat formatWithName:FICDPhotoPixelImageFormatName family:FICDPhotoImageFormatFamily imageSize:FICDPhotoPixelImageSize style:FICImageFormatStyle32BitBGR
maximumCount:pixelImageFormatMaximumCount devices:pixelImageFormatDevices protectionMode:FICImageFormatProtectionModeNone];
[mutableImageFormats addObject:pixelImageFormat];
}
// Configure the image cache
FICImageCache *sharedImageCache = [FICImageCache sharedImageCache];
[sharedImageCache setDelegate:self];
[sharedImageCache setFormats:mutableImageFormats];
// Configure the window
CGRect windowFrame = [[UIScreen mainScreen] bounds];
UIWindow *window = [[UIWindow alloc] initWithFrame:windowFrame];
[self setWindow:window];
UIViewController *rootViewController = [[FICDViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
[[self window] setRootViewController:navigationController];
[[self window] makeKeyAndVisible];
return YES;
}
#pragma mark - Protocol Implementations
#pragma mark - FICImageCacheDelegate
- (void)imageCache:(FICImageCache *)imageCache wantsSourceImageForEntity:(id<FICEntity>)entity withFormatName:(NSString *)formatName completionBlock:(FICImageRequestCompletionBlock)completionBlock {
// 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.
// For the purposes of this demo app, we'll just access images stored locally on disk.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *sourceImage = [(FICDPhoto *)entity sourceImage];
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(sourceImage);
});
});
}
- (BOOL)imageCache:(FICImageCache *)imageCache shouldProcessAllFormatsInFamily:(NSString *)formatFamily forEntity:(id<FICEntity>)entity {
return NO;
}
- (void)imageCache:(FICImageCache *)imageCache errorDidOccurWithMessage:(NSString *)errorMessage {
NSLog(@"%@", errorMessage);
}
@end
================================================
FILE: FastImageCache/FastImageCacheDemo/Classes/FICDFullscreenPhotoDisplayController.h
================================================
//
// FICDFullscreenPhotoDisplayController.h
// FastImageCacheDemo
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class FICDPhoto;
@protocol FICDFullscreenPhotoDisplayControllerDelegate;
@interface FICDFullscreenPhotoDisplayController : NSObject
@property (nonatomic, weak) id <FICDFullscreenPhotoDisplayControllerDelegate> delegate;
+ (instancetype)sharedDisplayController;
@property (nonatomic, assign, readonly, getter = isDisplayingPhoto) BOOL displayingPhoto;
- (void)showFullscreenPhoto:(FICDPhoto *)photo forImageFormatName:(NSString *)imageFormatName withThumbnailImageView:(UIImageView *)thumbnailImageView;
- (void)hideFullscreenPhoto;
@end
@protocol FICDFullscreenPhotoDisplayControllerDelegate <NSObject>
@optional
- (void)photoDisplayController:(FICDFullscreenPhotoDisplayController *)photoDisplayController willShowSourceImage:(UIImage *)sourceImage forPhoto:(FICDPhoto *)photo withThumbnailImageView:(UIImageView *)thumbnailImageView;
- (void)photoDisplayController:(FICDFullscreenPhotoDisplayController *)photoDisplayController didShowSourceImage:(UIImage *)sourceImage forPhoto:(FICDPhoto *)photo withThumbnailImageView:(UIImageView *)thumbnailImageView;
- (void)photoDisplayController:(FICDFullscreenPhotoDisplayController *)photoDisplayController willHideSourceImage:(UIImage *)sourceImage forPhoto:(FICDPhoto *)photo withThumbnailImageView:(UIImageView *)thumbnailImageView;
- (void)photoDisplayController:(FICDFullscreenPhotoDisplayController *)photoDisplayController didHideSourceImage:(UIImage *)sourceImage forPhoto:(FICDPhoto *)photo withThumbnailImageView:(UIImageView *)thumbnailImageView;
@end
================================================
FILE: FastImageCache/FastImageCacheDemo/Classes/FICDFullscreenPhotoDisplayController.m
================================================
//
// FICDFullscreenPhotoDisplayController.m
// FastImageCacheDemo
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICDFullscreenPhotoDisplayController.h"
#import "FICDPhoto.h"
#import <CoreImage/CoreImage.h>
#pragma mark Class Extension
@interface FICDFullscreenPhotoDisplayController () <UIGestureRecognizerDelegate> {
__weak id <FICDFullscreenPhotoDisplayControllerDelegate> _delegate;
BOOL _delegateImplementsWillShowSourceImageForPhotoWithThumbnailImageView;
BOOL _delegateImplementsDidShowSourceImageForPhotoWithThumbnailImageView;
BOOL _delegateImplementsWillHideSourceImageForPhotoWithThumbnailImageView;
BOOL _delegateImplementsDidHideSourceImageForPhotoWithThumbnailImageView;
UIView *_fullscreenView;
UIView *_backgroundView;
UIImageView *_thumbnailImageView;
CGRect _originalThumbnailImageViewFrame;
NSUInteger _originalThumbnailImageViewSubviewIndex;
UIView *_originalThumbnailImageViewSuperview;
UIImageView *_sourceImageView;
FICDPhoto *_photo;
UITapGestureRecognizer *_tapGestureRecognizer;
CIContext* _CoreImageContext;
}
@end
#pragma mark
@implementation FICDFullscreenPhotoDisplayController
@synthesize delegate = _delegate;
#pragma mark - Property Accessors
- (void)setDelegate:(id<FICDFullscreenPhotoDisplayControllerDelegate>)delegate {
_delegate = delegate;
_delegateImplementsWillShowSourceImageForPhotoWithThumbnailImageView = [_delegate respondsToSelector:@selector(photoDisplayController:willShowSourceImage:forPhoto:withThumbnailImageView:)];
_delegateImplementsDidShowSourceImageForPhotoWithThumbnailImageView = [_delegate respondsToSelector:@selector(photoDisplayController:didShowSourceImage:forPhoto:withThumbnailImageView:)];
_delegateImplementsWillHideSourceImageForPhotoWithThumbnailImageView = [_delegate respondsToSelector:@selector(photoDisplayController:willHideSourceImage:forPhoto:withThumbnailImageView:)];
_delegateImplementsDidHideSourceImageForPhotoWithThumbnailImageView = [_delegate respondsToSelector:@selector(photoDisplayController:didHideSourceImage:forPhoto:withThumbnailImageView:)];
}
- (BOOL)isDisplayingPhoto {
return _photo != nil;
}
#pragma mark - Object Lifecycle
+ (instancetype)sharedDisplayController {
static FICDFullscreenPhotoDisplayController *__sharedDisplayController = nil;
if (__sharedDisplayController == nil) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__sharedDisplayController = [[[self class] alloc] init];
});
}
return __sharedDisplayController;
}
- (id)init {
self = [super init];
if (self != nil) {
UIViewAutoresizing autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
_fullscreenView = [[UIView alloc] init];
[_fullscreenView setAutoresizingMask:autoresizingMask];
_backgroundView = [[UIView alloc] init];
[_backgroundView setAutoresizingMask:autoresizingMask];
[_backgroundView setBackgroundColor:[UIColor colorWithWhite:0 alpha:0.8]];
_sourceImageView = [[UIImageView alloc] init];
[_sourceImageView setAutoresizingMask:autoresizingMask];
[_sourceImageView setContentMode:UIViewContentModeScaleAspectFill];
[_sourceImageView setClipsToBounds:YES];
_tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_tapGestureRecognizerStateDidChange)];
[_fullscreenView addGestureRecognizer:_tapGestureRecognizer];
_CoreImageContext = [CIContext contextWithOptions:nil];
}
return self;
}
- (void)dealloc {
[_tapGestureRecognizer setDelegate:nil];
}
#pragma mark - Showing and Hiding a Fullscreen Photo
- (void)showFullscreenPhoto:(FICDPhoto *)photo forImageFormatName:(NSString *)imageFormatName withThumbnailImageView:(UIImageView *)thumbnailImageView {
// Stash away the photo
_photo = photo;
// Stash away original thumbnail image view information
_thumbnailImageView = thumbnailImageView;
_originalThumbnailImageViewSuperview = [thumbnailImageView superview];
_originalThumbnailImageViewFrame = [thumbnailImageView frame];
_originalThumbnailImageViewSubviewIndex = [[[thumbnailImageView superview] subviews] indexOfObject:thumbnailImageView];
// Configure the fullscreen view
UIView *rootViewControllerView = [[[[UIApplication sharedApplication] keyWindow] rootViewController] view];
[_fullscreenView setFrame:[rootViewControllerView bounds]];
[rootViewControllerView addSubview:_fullscreenView];
// Configure the background view
[_backgroundView setFrame:[_fullscreenView bounds]];
[_backgroundView setAlpha:0];
[_fullscreenView addSubview:_backgroundView];
// Configure the thumbnail image view
CGRect convertedThumbnailImageViewFrame = [_originalThumbnailImageViewSuperview convertRect:_originalThumbnailImageViewFrame toView:_fullscreenView];
[_thumbnailImageView setFrame:convertedThumbnailImageViewFrame];
[_fullscreenView addSubview:_thumbnailImageView];
// Configure the source image view
UIImage *sourceImage = [photo sourceImage];
// Desaturate the source image with Core Image if the image format name is FICDPhotoSquareImage8BitGrayscaleFormatName
if ([imageFormatName isEqualToString:FICDPhotoSquareImage8BitGrayscaleFormatName]) {
CIFilter *colorControlsFilter = [CIFilter filterWithName:@"CIColorControls"];
[colorControlsFilter setValue:[CIImage imageWithCGImage:[sourceImage CGImage]] forKey:kCIInputImageKey];
[colorControlsFilter setValue:[NSNumber numberWithFloat:0] forKey:@"inputSaturation"];
CIImage *outputCIImage = [colorControlsFilter outputImage];
CGImageRef outputImageRef = [_CoreImageContext createCGImage:outputCIImage fromRect:[outputCIImage extent]];
sourceImage = [UIImage imageWithCGImage:outputImageRef];
CGImageRelease(outputImageRef);
}
[_sourceImageView setImage:sourceImage];
[_sourceImageView setFrame:convertedThumbnailImageViewFrame];
[_sourceImageView setAlpha:0];
[_fullscreenView addSubview:_sourceImageView];
// Inform the delegate that we're about to show a fullscreen photo
if (_delegateImplementsWillShowSourceImageForPhotoWithThumbnailImageView) {
[_delegate photoDisplayController:self willShowSourceImage:sourceImage forPhoto:_photo withThumbnailImageView:_thumbnailImageView];
}
// Animate fullscreen photo appearance
[UIView animateWithDuration:0.3 animations:^{
[_backgroundView setAlpha:1];
[_thumbnailImageView setFrame:[_fullscreenView bounds]];
[_sourceImageView setAlpha:1];
[_sourceImageView setFrame:[_fullscreenView bounds]];
} completion:^(BOOL finished) {
// Inform the delegate that we just showed a fullscreen photo
if (_delegateImplementsDidShowSourceImageForPhotoWithThumbnailImageView) {
[_delegate photoDisplayController:self didShowSourceImage:sourceImage forPhoto:_photo withThumbnailImageView:_thumbnailImageView];
}
}];
}
- (void)hideFullscreenPhoto {
UIImage *sourceImage = [_sourceImageView image];
// Inform the delegate that we're about to hide a fullscreen photo
if (_delegateImplementsWillHideSourceImageForPhotoWithThumbnailImageView) {
[_delegate photoDisplayController:self willHideSourceImage:sourceImage forPhoto:_photo withThumbnailImageView:_thumbnailImageView];
}
CGRect convertedThumbnailImageViewFrame = [_originalThumbnailImageViewSuperview convertRect:_originalThumbnailImageViewFrame toView:_fullscreenView];
// Animate fullscreen photo appearance
[UIView animateWithDuration:0.3 animations:^{
[_backgroundView setAlpha:0];
[_thumbnailImageView setFrame:convertedThumbnailImageViewFrame];
[_sourceImageView setAlpha:0];
[_sourceImageView setFrame:convertedThumbnailImageViewFrame];
} completion:^(BOOL finished) {
[_thumbnailImageView setFrame:_originalThumbnailImageViewFrame];
[_originalThumbnailImageViewSuperview insertSubview:_thumbnailImageView atIndex:_originalThumbnailImageViewSubviewIndex];
[_fullscreenView removeFromSuperview];
// Inform the delegate that we just hide a fullscreen photo
if (_delegateImplementsDidHideSourceImageForPhotoWithThumbnailImageView) {
[_delegate photoDisplayController:self didHideSourceImage:sourceImage forPhoto:_photo withThumbnailImageView:_thumbnailImageView];
}
// Clean up
_photo = nil;
_thumbnailImageView = nil;
_originalThumbnailImageViewSuperview = nil;
_originalThumbnailImageViewFrame = CGRectZero;
_originalThumbnailImageViewSubviewIndex = 0;
_sourceImageView.image = nil;
}];
}
- (void)_tapGestureRecognizerStateDidChange {
if ([_tapGestureRecognizer state] == UIGestureRecognizerStateEnded) {
[self hideFullscreenPhoto];
}
}
@end
================================================
FILE: FastImageCache/FastImageCacheDemo/Classes/FICDPhoto.h
================================================
//
// FICDPhoto.h
// FastImageCacheDemo
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICEntity.h"
extern NSString *const FICDPhotoImageFormatFamily;
extern NSString *const FICDPhotoSquareImage32BitBGRAFormatName;
extern NSString *const FICDPhotoSquareImage32BitBGRFormatName;
extern NSString *const FICDPhotoSquareImage16BitBGRFormatName;
extern NSString *const FICDPhotoSquareImage8BitGrayscaleFormatName;
extern NSString *const FICDPhotoPixelImageFormatName;
extern CGSize const FICDPhotoSquareImageSize;
extern CGSize const FICDPhotoPixelImageSize;
@interface FICDPhoto : NSObject <FICEntity>
@property (nonatomic, copy) NSURL *sourceImageURL;
@property (nonatomic, strong, readonly) UIImage *sourceImage;
@property (nonatomic, strong, readonly) UIImage *thumbnailImage;
@property (nonatomic, assign, readonly) BOOL thumbnailImageExists;
// Methods for demonstrating more conventional caching techniques
- (void)generateThumbnail;
- (void)deleteThumbnail;
@end
================================================
FILE: FastImageCache/FastImageCacheDemo/Classes/FICDPhoto.m
================================================
//
// FICDPhoto.m
// FastImageCacheDemo
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICDPhoto.h"
#import "FICUtilities.h"
#pragma mark External Definitions
NSString *const FICDPhotoImageFormatFamily = @"FICDPhotoImageFormatFamily";
NSString *const FICDPhotoSquareImage32BitBGRAFormatName = @"com.path.FastImageCacheDemo.FICDPhotoSquareImage32BitBGRAFormatName";
NSString *const FICDPhotoSquareImage32BitBGRFormatName = @"com.path.FastImageCacheDemo.FICDPhotoSquareImage32BitBGRFormatName";
NSString *const FICDPhotoSquareImage16BitBGRFormatName = @"com.path.FastImageCacheDemo.FICDPhotoSquareImage16BitBGRFormatName";
NSString *const FICDPhotoSquareImage8BitGrayscaleFormatName = @"com.path.FastImageCacheDemo.FICDPhotoSquareImage8BitGrayscaleFormatName";
NSString *const FICDPhotoPixelImageFormatName = @"com.path.FastImageCacheDemo.FICDPhotoPixelImageFormatName";
CGSize const FICDPhotoSquareImageSize = {75, 75};
CGSize const FICDPhotoPixelImageSize = {1, 1};
#pragma mark - Class Extension
@interface FICDPhoto () {
NSURL *_sourceImageURL;
NSString *_UUID;
NSString *_thumbnailFilePath;
BOOL _thumbnailFileExists;
BOOL _didCheckForThumbnailFile;
}
@end
#pragma mark
@implementation FICDPhoto
@synthesize sourceImageURL = _sourceImageURL;
#pragma mark - Property Accessors
- (UIImage *)sourceImage {
UIImage *sourceImage = [UIImage imageWithContentsOfFile:[_sourceImageURL path]];
return sourceImage;
}
- (UIImage *)thumbnailImage {
UIImage *thumbnailImage = [UIImage imageWithContentsOfFile:[self _thumbnailFilePath]];
return thumbnailImage;
}
- (BOOL)thumbnailImageExists {
BOOL thumbnailImageExists = [[NSFileManager defaultManager] fileExistsAtPath:[self _thumbnailFilePath]];
return thumbnailImageExists;
}
#pragma mark - Image Helper Functions
static CGMutablePathRef _FICDCreateRoundedRectPath(CGRect rect, CGFloat cornerRadius) {
CGMutablePathRef path = CGPathCreateMutable();
CGFloat minX = CGRectGetMinX(rect);
CGFloat midX = CGRectGetMidX(rect);
CGFloat maxX = CGRectGetMaxX(rect);
CGFloat minY = CGRectGetMinY(rect);
CGFloat midY = CGRectGetMidY(rect);
CGFloat maxY = CGRectGetMaxY(rect);
CGPathMoveToPoint(path, NULL, minX, midY);
CGPathAddArcToPoint(path, NULL, minX, maxY, midX, maxY, cornerRadius);
CGPathAddArcToPoint(path, NULL, maxX, maxY, maxX, midY, cornerRadius);
CGPathAddArcToPoint(path, NULL, maxX, minY, midX, minY, cornerRadius);
CGPathAddArcToPoint(path, NULL, minX, minY, minX, midY, cornerRadius);
return path;
}
static UIImage * _FICDSquareImageFromImage(UIImage *image) {
UIImage *squareImage = nil;
CGSize imageSize = [image size];
if (imageSize.width == imageSize.height) {
squareImage = image;
} else {
// Compute square crop rect
CGFloat smallerDimension = MIN(imageSize.width, imageSize.height);
CGRect cropRect = CGRectMake(0, 0, smallerDimension, smallerDimension);
// Center the crop rect either vertically or horizontally, depending on which dimension is smaller
if (imageSize.width <= imageSize.height) {
cropRect.origin = CGPointMake(0, rintf((imageSize.height - smallerDimension) / 2.0));
} else {
cropRect.origin = CGPointMake(rintf((imageSize.width - smallerDimension) / 2.0), 0);
}
CGImageRef croppedImageRef = CGImageCreateWithImageInRect([image CGImage], cropRect);
squareImage = [UIImage imageWithCGImage:croppedImageRef];
CGImageRelease(croppedImageRef);
}
return squareImage;
}
static UIImage * _FICDStatusBarImageFromImage(UIImage *image) {
CGSize imageSize = [image size];
CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size;
CGRect cropRect = CGRectMake(0, 0, imageSize.width, statusBarSize.height);
CGImageRef croppedImageRef = CGImageCreateWithImageInRect([image CGImage], cropRect);
UIImage *statusBarImage = [UIImage imageWithCGImage:croppedImageRef];
CGImageRelease(croppedImageRef);
return statusBarImage;
}
#pragma mark - Conventional Image Caching Techniques
- (NSString *)_thumbnailFilePath {
if (!_thumbnailFilePath) {
NSURL *photoURL = [self sourceImageURL];
_thumbnailFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[photoURL absoluteString] lastPathComponent]];
}
return _thumbnailFilePath;
}
- (void)generateThumbnail {
NSString *thumbnailFilePath = [self _thumbnailFilePath];
if (!_didCheckForThumbnailFile) {
_didCheckForThumbnailFile = YES;
_thumbnailFileExists = [[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath];
}
if (_thumbnailFileExists == NO) {
CGFloat screenScale = [[UIScreen mainScreen] scale];
CGRect contextBounds = CGRectZero;
contextBounds.size = CGSizeMake(FICDPhotoSquareImageSize.width * screenScale, FICDPhotoSquareImageSize.height * screenScale);
UIImage *sourceImage = [self sourceImage];
UIImage *squareImage = _FICDSquareImageFromImage(sourceImage);
UIGraphicsBeginImageContext(contextBounds.size);
[squareImage drawInRect:contextBounds];
UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
NSData *scaledImageJPEGRepresentation = UIImageJPEGRepresentation(scaledImage, 0.8);
[scaledImageJPEGRepresentation writeToFile:thumbnailFilePath atomically:NO];
UIGraphicsEndImageContext();
_thumbnailFileExists = YES;
}
}
- (void)deleteThumbnail {
[[NSFileManager defaultManager] removeItemAtPath:[self _thumbnailFilePath] error:NULL];
_thumbnailFileExists = NO;
}
#pragma mark - Protocol Implementations
#pragma mark - FICImageCacheEntity
- (NSString *)fic_UUID {
if (_UUID == nil) {
// MD5 hashing is expensive enough that we only want to do it once
NSString *imageName = [_sourceImageURL lastPathComponent];
CFUUIDBytes UUIDBytes = FICUUIDBytesFromMD5HashOfString(imageName);
_UUID = FICStringWithUUIDBytes(UUIDBytes);
}
return _UUID;
}
- (NSString *)fic_sourceImageUUID {
return [self fic_UUID];
}
- (NSURL *)fic_sourceImageURLWithFormatName:(NSString *)formatName {
return _sourceImageURL;
}
- (FICEntityImageDrawingBlock)fic_drawingBlockForImage:(UIImage *)image withFormatName:(NSString *)formatName {
FICEntityImageDrawingBlock drawingBlock = ^(CGContextRef contextRef, CGSize contextSize) {
CGRect contextBounds = CGRectZero;
contextBounds.size = contextSize;
CGContextClearRect(contextRef, contextBounds);
if ([formatName isEqualToString:FICDPhotoPixelImageFormatName]) {
UIImage *statusBarImage = _FICDStatusBarImageFromImage(image);
CGContextSetInterpolationQuality(contextRef, kCGInterpolationMedium);
UIGraphicsPushContext(contextRef);
[statusBarImage drawInRect:contextBounds];
UIGraphicsPopContext();
} else {
if ([formatName isEqualToString:FICDPhotoSquareImage32BitBGRAFormatName] == NO) {
// Fill with white for image formats that are opaque
CGContextSetFillColorWithColor(contextRef, [[UIColor whiteColor] CGColor]);
CGContextFillRect(contextRef, contextBounds);
}
UIImage *squareImage = _FICDSquareImageFromImage(image);
// Clip to a rounded rect
CGPathRef path = _FICDCreateRoundedRectPath(contextBounds, 12);
CGContextAddPath(contextRef, path);
CFRelease(path);
CGContextEOClip(contextRef);
UIGraphicsPushContext(contextRef);
[squareImage drawInRect:contextBounds];
UIGraphicsPopContext();
}
};
return drawingBlock;
}
@end
================================================
FILE: FastImageCache/FastImageCacheDemo/Classes/FICDPhotosTableViewCell.h
================================================
//
// FICDPhotosTableViewCell.h
// FastImageCacheDemo
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import <UIKit/UIKit.h>
@class FICDPhoto;
@protocol FICDPhotosTableViewCellDelegate;
@interface FICDPhotosTableViewCell : UITableViewCell
@property (nonatomic, weak) id <FICDPhotosTableViewCellDelegate> delegate;
@property (nonatomic, assign) BOOL usesImageTable;
@property (nonatomic, copy) NSArray *photos;
@property (nonatomic, copy) NSString *imageFormatName;
+ (NSString *)reuseIdentifier;
+ (NSInteger)photosPerRow;
+ (CGFloat)outerPadding;
+ (CGFloat)rowHeight;
@end
@protocol FICDPhotosTableViewCellDelegate <NSObject>
@required
- (void)photosTableViewCell:(FICDPhotosTableViewCell *)photosTableViewCell didSelectPhoto:(FICDPhoto *)photo withImageView:(UIImageView *)imageView;
@end
================================================
FILE: FastImageCache/FastImageCacheDemo/Classes/FICDPhotosTableViewCell.m
================================================
//
// FICDPhotosTableViewCell.m
// FastImageCacheDemo
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICDPhotosTableViewCell.h"
#import "FICDPhoto.h"
#import "FICImageCache.h"
#import "FICDAppDelegate.h"
#pragma mark Class Extension
@interface FICDPhotosTableViewCell () <UIGestureRecognizerDelegate> {
__weak id <FICDPhotosTableViewCellDelegate> _delegate;
BOOL _usesImageTable;
NSArray *_photos;
NSString *_imageFormatName;
NSArray *_imageViews;
UITapGestureRecognizer *_tapGestureRecognizer;
}
@end
#pragma mark
@implementation FICDPhotosTableViewCell
@synthesize delegate = _delegate;
@synthesize usesImageTable = _usesImageTable;
@synthesize photos = _photos;
@synthesize imageFormatName = _imageFormatName;
#pragma mark - Property Accessors
- (void)setPhotos:(NSArray *)photos {
if (photos != _photos) {
_photos = [photos copy];
for (NSInteger i = 0; i < [_imageViews count]; i++) {
UIImageView *imageView = [_imageViews objectAtIndex:i];
if (i < [_photos count]) {
FICDPhoto *photo = [_photos objectAtIndex:i];
if (_usesImageTable) {
[[FICImageCache sharedImageCache] retrieveImageForEntity:photo withFormatName:_imageFormatName completionBlock:^(id<FICEntity> entity, NSString *formatName, UIImage *image) {
// 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.
if (photos == [self photos]) {
[imageView setImage:image];
}
}];
} else {
[imageView setImage:[photo thumbnailImage]];
}
} else {
// Last row might not be full
[imageView setImage:nil];
}
}
}
}
#pragma mark - Class-Level Definitions
+ (NSString *)reuseIdentifier {
static NSString *__reuseIdentifier = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__reuseIdentifier = NSStringFromClass([FICDPhotosTableViewCell class]);
});
return __reuseIdentifier;
}
+ (NSInteger)photosPerRow {
NSInteger photosPerRow = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? 9 : 4;
return photosPerRow;
}
+ (CGFloat)outerPadding {
CGFloat outerPadding = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? 10 : 4;
return outerPadding;
}
+ (CGFloat)rowHeight {
CGFloat rowHeight = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? 84 : 79;
return rowHeight;
}
#pragma mark - Object Lifecycle
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self != nil) {
_tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_tapGestureRecognizerStateDidChange)];
[self addGestureRecognizer:_tapGestureRecognizer];
NSInteger photosPerRow = [[self class] photosPerRow];
NSMutableArray *imageViews = [[NSMutableArray alloc] initWithCapacity:photosPerRow];
for (NSInteger i = 0; i < photosPerRow; i++) {
UIImageView *imageView = [[UIImageView alloc] init];
[imageView setContentMode:UIViewContentModeScaleAspectFill];
[imageViews addObject:imageView];
[self.contentView addSubview:imageView];
}
_imageViews = [imageViews copy];
}
return self;
}
- (id)init {
return [self initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
}
- (void)dealloc {
[_tapGestureRecognizer setDelegate:nil];
}
#pragma mark - Configuring the View Hierarchy
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat innerPadding = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? 9 : 4;
CGFloat outerPadding = [[self class] outerPadding];
CGRect imageViewFrame = CGRectMake(outerPadding, outerPadding, FICDPhotoSquareImageSize.width, FICDPhotoSquareImageSize.height);
NSInteger count = [_photos count];
for (NSInteger i = 0; i < count; i++) {
UIImageView *imageView = [_imageViews objectAtIndex:i];
[imageView setFrame:imageViewFrame];
imageViewFrame.origin.x += imageViewFrame.size.width + innerPadding;
}
}
#pragma mark - Responding to User Interaction Events
- (void)_tapGestureRecognizerStateDidChange {
if ([_tapGestureRecognizer state] == UIGestureRecognizerStateEnded) {
CGPoint tapLocationInSelf = [_tapGestureRecognizer locationInView:self];
UIImageView *selectedImageView = nil;
for (UIImageView *imageView in _imageViews) {
CGRect imageViewFrame = [imageView frame];
BOOL frameContainsTapLocation = CGRectContainsPoint(imageViewFrame, tapLocationInSelf);
if (frameContainsTapLocation) {
selectedImageView = imageView;
}
}
if (selectedImageView != nil) {
NSUInteger imageViewIndex = [_imageViews indexOfObject:selectedImageView];
FICDPhoto *selectedPhoto = [_photos objectAtIndex:imageViewIndex];
[_delegate photosTableViewCell:self didSelectPhoto:selectedPhoto withImageView:selectedImageView];
}
}
}
@end
================================================
FILE: FastImageCache/FastImageCacheDemo/Classes/FICDTableView.h
================================================
//
// FICDTableView.h
// FastImageCacheDemo
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import <UIKit/UIKit.h>
@interface FICDTableView : UITableView
@property (nonatomic, assign, readonly) CGFloat averageFPS;
- (void)resetScrollingPerformanceCounters;
@end
================================================
FILE: FastImageCache/FastImageCacheDemo/Classes/FICDTableView.m
================================================
//
// FICDTableView.m
// FastImageCacheDemo
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICDTableView.h"
#pragma mark Class Extension
@interface FICDTableView () {
CADisplayLink *_displayLink;
NSInteger _framesInLastInterval;
CFAbsoluteTime _lastLogTime;
NSInteger _totalFrames;
NSTimeInterval _scrollingTime;
CGFloat _averageFPS;
}
@property (nonatomic, assign, readwrite) CGFloat averageFPS;
@end
#pragma mark
@implementation FICDTableView
@synthesize averageFPS = _averageFPS;
#pragma mark - Object Lifecycle
- (void)dealloc {
[_displayLink invalidate];
}
- (void)didMoveToWindow {
if ([self window] != nil) {
[self _scrollingStatusDidChange];
} else {
[_displayLink invalidate];
_displayLink = nil;
}
}
#pragma mark - Monitoring Scrolling Performance
- (void)resetScrollingPerformanceCounters {
_framesInLastInterval = 0;
_lastLogTime = CFAbsoluteTimeGetCurrent();
_scrollingTime = 0;
_totalFrames = 0;
}
- (void)_scrollingStatusDidChange {
NSString *currentRunLoopMode = [[NSRunLoop currentRunLoop] currentMode];
BOOL isScrolling = [currentRunLoopMode isEqualToString:UITrackingRunLoopMode];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_scrollingStatusDidChange) object:nil];
if (isScrolling) {
if (_displayLink == nil) {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_screenDidUpdateWhileScrolling:)];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:UITrackingRunLoopMode];
}
_framesInLastInterval = 0;
_lastLogTime = CFAbsoluteTimeGetCurrent();
[_displayLink setPaused:NO];
// Let us know when scrolling has stopped
[self performSelector:@selector(_scrollingStatusDidChange) withObject:nil afterDelay:0 inModes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];
} else {
[_displayLink setPaused:YES];
// Let us know when scrolling begins
[self performSelector:@selector(_scrollingStatusDidChange) withObject:nil afterDelay:0 inModes:[NSArray arrayWithObject:UITrackingRunLoopMode]];
}
}
- (void)_screenDidUpdateWhileScrolling:(CADisplayLink *)displayLink {
CFAbsoluteTime currentTime = CFAbsoluteTimeGetCurrent();
if (!_lastLogTime) {
_lastLogTime = currentTime;
}
CGFloat delta = currentTime - _lastLogTime;
if (delta >= 1) {
_scrollingTime += delta;
_totalFrames += _framesInLastInterval;
NSInteger lastFPS = (NSInteger)rintf((CGFloat)_framesInLastInterval / delta);
CGFloat averageFPS = (CGFloat)(_totalFrames / _scrollingTime);
[self setAverageFPS:averageFPS];
static dispatch_queue_t __dispatchQueue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__dispatchQueue = dispatch_queue_create("com.path.FastImageCacheDemo.ScrollingPerformanceMeasurement", 0);
});
// We don't want the logging of scrolling performance to be able to impact the scrolling performance,
// so move both the logging and the string formatting onto a GCD serial queue.
dispatch_async(__dispatchQueue, ^{
NSLog(@"*** FIC Demo: Last FPS = %ld, Average FPS = %.2f", (long)lastFPS, averageFPS);
});
_framesInLastInterval = 0;
_lastLogTime = currentTime;
} else {
_framesInLastInterval++;
}
}
@end
================================================
FILE: FastImageCache/FastImageCacheDemo/Classes/FICDViewController.h
================================================
//
// FICDViewController.h
// FastImageCacheDemo
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import <UIKit/UIKit.h>
@class FICDTableView;
@interface FICDViewController : UIViewController
@end
================================================
FILE: FastImageCache/FastImageCacheDemo/Classes/FICDViewController.m
================================================
//
// FICDViewController.m
// FastImageCacheDemo
//
// Copyright (c) 2013 Path, Inc.
// See LICENSE for full license agreement.
//
#import "FICDViewController.h"
#import "FICImageCache.h"
#import "FICDTableView.h"
#import "FICDAppDelegate.h"
#import "FICDPhoto.h"
#import "FICDFullscreenPhotoDisplayController.h"
#import "FICDPhotosTableViewCell.h"
#pragma mark Class Extension
@interface FICDViewController () <UITableViewDataSource, UITableViewDelegate, UIAlertViewDelegate, FICDPhotosTableViewCellDelegate, FICDFullscreenPhotoDisplayControllerDelegate> {
FICDTableView *_tableView;
NSArray *_photos;
NSString *_imageFormatName;
NSArray *_imageFormatStyleToolbarItems;
BOOL _usesImageTable;
BOOL _shouldReloadTableViewAfterScrollingAnimationEnds;
BOOL _shouldResetData;
NSInteger _selectedMethodSegmentControlIndex;
NSInteger _callbackCount;
UIAlertView *_noImagesAlertView;
UILabel *_averageFPSLabel;
}
@end
#pragma mark
@implementation FICDViewController
#pragma mark - Object Lifecycle
- (id)init {
self = [super init];
if (self != nil) {
NSBundle *mainBundle = [NSBundle mainBundle];
NSArray *imageURLs = [mainBundle URLsForResourcesWithExtension:@"jpg" subdirectory:@"Demo Images"];
if ([imageURLs count] > 0) {
NSMutableArray *photos = [[NSMutableArray alloc] init];
for (NSURL *imageURL in imageURLs) {
FICDPhoto *photo = [[FICDPhoto alloc] init];
[photo setSourceImageURL:imageURL];
[photos addObject:photo];
}
while ([photos count] < 5000) {
[photos addObjectsFromArray:photos]; // Create lots of photos to scroll through
}
_photos = photos;
} else {
NSString *title = @"No Source Images";
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.";
_noImagesAlertView = [[UIAlertView alloc] initWithTitle:title message:message delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
[_noImagesAlertView show];
}
}
return self;
}
- (void)dealloc {
[_tableView setDelegate:nil];
[_tableView setDataSource:nil];
[_noImagesAlertView setDelegate:nil];
}
#pragma mark - View Controller Lifecycle
- (void)loadView {
CGRect viewFrame = [[UIScreen mainScreen] bounds];
UIView *view = [[UIView alloc] initWithFrame:viewFrame];
[view setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
[view setBackgroundColor:[UIColor whiteColor]];
[self setView:view];
// Configure the table view
if (_tableView == nil) {
_tableView = [[FICDTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
[_tableView setDataSource:self];
[_tableView setDelegate:self];
[_tableView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
[_tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
[_tableView registerClass:[FICDPhotosTableViewCell class] forCellReuseIdentifier:[FICDPhotosTableViewCell reuseIdentifier]];
CGFloat tableViewCellOuterPadding = [FICDPhotosTableViewCell outerPadding];
[_tableView setContentInset:UIEdgeInsetsMake(0, 0, tableViewCellOuterPadding, 0)];
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
[_tableView setScrollIndicatorInsets:UIEdgeInsetsMake(7, 0, 7, 1)];
}
}
[_tableView setFrame:[view bounds]];
[view addSubview:_tableView];
// Configure the navigation item
UINavigationItem *navigationItem = [self navigationItem];
UIBarButtonItem *resetBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Reset" style:UIBarButtonItemStylePlain target:self action:@selector(_reset)];
[navigationItem setLeftBarButtonItem:resetBarButtonItem];
UISegmentedControl *met
gitextract_frhwcarf/ ├── .gitignore ├── FastImageCache/ │ ├── FastImageCache/ │ │ ├── FastImageCache/ │ │ │ ├── FICEntity.h │ │ │ ├── FICImageCache+FICErrorLogging.h │ │ │ ├── FICImageCache.h │ │ │ ├── FICImageCache.m │ │ │ ├── FICImageFormat.h │ │ │ ├── FICImageFormat.m │ │ │ ├── FICImageTable.h │ │ │ ├── FICImageTable.m │ │ │ ├── FICImageTableChunk.h │ │ │ ├── FICImageTableChunk.m │ │ │ ├── FICImageTableEntry.h │ │ │ ├── FICImageTableEntry.m │ │ │ ├── FICImports.h │ │ │ ├── FICUtilities.h │ │ │ └── FICUtilities.m │ │ ├── FastImageCache.h │ │ └── Info.plist │ ├── FastImageCache.xcodeproj/ │ │ ├── project.pbxproj │ │ └── xcshareddata/ │ │ └── xcschemes/ │ │ └── FastImageCache.xcscheme │ ├── FastImageCacheDemo/ │ │ ├── Assets.xcassets/ │ │ │ ├── Icon.appiconset/ │ │ │ │ └── Contents.json │ │ │ └── Launch Image.launchimage/ │ │ │ └── Contents.json │ │ ├── Classes/ │ │ │ ├── FICDAppDelegate.h │ │ │ ├── FICDAppDelegate.m │ │ │ ├── FICDFullscreenPhotoDisplayController.h │ │ │ ├── FICDFullscreenPhotoDisplayController.m │ │ │ ├── FICDPhoto.h │ │ │ ├── FICDPhoto.m │ │ │ ├── FICDPhotosTableViewCell.h │ │ │ ├── FICDPhotosTableViewCell.m │ │ │ ├── FICDTableView.h │ │ │ ├── FICDTableView.m │ │ │ ├── FICDViewController.h │ │ │ └── FICDViewController.m │ │ ├── Demo Images/ │ │ │ └── README │ │ ├── FastImageCacheDemo-Prefix.pch │ │ ├── Info.plist │ │ ├── fetch_demo_images.sh │ │ └── main.m │ └── FastImageCacheTests/ │ ├── FastImageCacheTests.m │ └── Info.plist ├── LICENSE └── README.md
SYMBOL INDEX (3 symbols across 2 files)
FILE: FastImageCache/FastImageCache/FastImageCache/FICImageFormat.h
type FICImageFormatStyle32BitBGRA (line 18) | typedef NS_ENUM(NSUInteger, FICImageFormatStyle) {
type FICImageFormatProtectionModeNone (line 25) | typedef NS_ENUM(NSUInteger, FICImageFormatProtectionMode) {
FILE: FastImageCache/FastImageCache/FastImageCache/FICImageTableEntry.h
type FICImageTableEntryMetadata (line 16) | typedef struct {
Condensed preview — 43 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (270K chars).
[
{
"path": ".gitignore",
"chars": 321,
"preview": "# 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.mo"
},
{
"path": "FastImageCache/FastImageCache/FastImageCache/FICEntity.h",
"chars": 4349,
"preview": "//\n// FICEntity.h\n// FastImageCache\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agreement.\n/"
},
{
"path": "FastImageCache/FastImageCache/FastImageCache/FICImageCache+FICErrorLogging.h",
"chars": 870,
"preview": "//\n// FICImageCache+FICErrorLogging.h\n// FastImageCache\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full "
},
{
"path": "FastImageCache/FastImageCache/FastImageCache/FICImageCache.h",
"chars": 15496,
"preview": "//\n// FICImageCache.h\n// FastImageCache\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agreemen"
},
{
"path": "FastImageCache/FastImageCache/FastImageCache/FICImageCache.m",
"chars": 21897,
"preview": "//\n// FICImageCache.m\n// FastImageCache\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agreemen"
},
{
"path": "FastImageCache/FastImageCache/FastImageCache/FICImageFormat.h",
"chars": 9583,
"preview": "//\n// FICImageFormat.h\n// FastImageCache\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agreeme"
},
{
"path": "FastImageCache/FastImageCache/FastImageCache/FICImageFormat.m",
"chars": 6911,
"preview": "//\n// FICImageFormat.m\n// FastImageCache\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agreeme"
},
{
"path": "FastImageCache/FastImageCache/FastImageCache/FICImageTable.h",
"chars": 7159,
"preview": "//\n// FICImageTable.h\n// FastImageCache\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agreemen"
},
{
"path": "FastImageCache/FastImageCache/FastImageCache/FICImageTable.m",
"chars": 32326,
"preview": "//\n// FICImageTable.m\n// FastImageCache\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agreemen"
},
{
"path": "FastImageCache/FastImageCache/FastImageCache/FICImageTableChunk.h",
"chars": 1525,
"preview": "//\n// FICImageTableChunk.h\n// FastImageCache\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agr"
},
{
"path": "FastImageCache/FastImageCache/FastImageCache/FICImageTableChunk.m",
"chars": 1168,
"preview": "//\n// FICImageTableChunk.m\n// FastImageCache\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agr"
},
{
"path": "FastImageCache/FastImageCache/FastImageCache/FICImageTableEntry.h",
"chars": 3631,
"preview": "//\n// FICImageTableEntry.h\n// FastImageCache\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agr"
},
{
"path": "FastImageCache/FastImageCache/FastImageCache/FICImageTableEntry.m",
"chars": 3385,
"preview": "//\n// FICImageTableEntry.m\n// FastImageCache\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agr"
},
{
"path": "FastImageCache/FastImageCache/FastImageCache/FICImports.h",
"chars": 220,
"preview": "//\n// FICImports.h\n// FastImageCache\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agreement.\n"
},
{
"path": "FastImageCache/FastImageCache/FastImageCache/FICUtilities.h",
"chars": 539,
"preview": "//\n// FICUtilities.h\n// FastImageCache\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agreement"
},
{
"path": "FastImageCache/FastImageCache/FastImageCache/FICUtilities.m",
"chars": 1871,
"preview": "//\n// FICUtilities.m\n// FastImageCache\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agreement"
},
{
"path": "FastImageCache/FastImageCache/FastImageCache.h",
"chars": 501,
"preview": "//\n// FastImageCache.h\n// FastImageCache\n//\n// Created by Rui Peres on 17/06/2015.\n// Copyright (c) 2015 Path. All r"
},
{
"path": "FastImageCache/FastImageCache/Info.plist",
"chars": 806,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "FastImageCache/FastImageCache.xcodeproj/project.pbxproj",
"chars": 33215,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "FastImageCache/FastImageCache.xcodeproj/xcshareddata/xcschemes/FastImageCache.xcscheme",
"chars": 4354,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"0720\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "FastImageCache/FastImageCacheDemo/Assets.xcassets/Icon.appiconset/Contents.json",
"chars": 1817,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"iphone\",\n \"size\" : \"29x29\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\""
},
{
"path": "FastImageCache/FastImageCacheDemo/Assets.xcassets/Launch Image.launchimage/Contents.json",
"chars": 1852,
"preview": "{\n \"images\" : [\n {\n \"orientation\" : \"portrait\",\n \"idiom\" : \"iphone\",\n \"extent\" : \"full-screen\",\n "
},
{
"path": "FastImageCache/FastImageCacheDemo/Classes/FICDAppDelegate.h",
"chars": 372,
"preview": "//\n// FICDAppDelegate.h\n// FastImageCacheDemo\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license ag"
},
{
"path": "FastImageCache/FastImageCacheDemo/Classes/FICDAppDelegate.m",
"chars": 5165,
"preview": "//\n// FICDAppDelegate.m\n// FastImageCacheDemo\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license ag"
},
{
"path": "FastImageCache/FastImageCacheDemo/Classes/FICDFullscreenPhotoDisplayController.h",
"chars": 1732,
"preview": "//\n// FICDFullscreenPhotoDisplayController.h\n// FastImageCacheDemo\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENS"
},
{
"path": "FastImageCache/FastImageCacheDemo/Classes/FICDFullscreenPhotoDisplayController.m",
"chars": 9269,
"preview": "//\n// FICDFullscreenPhotoDisplayController.m\n// FastImageCacheDemo\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENS"
},
{
"path": "FastImageCache/FastImageCacheDemo/Classes/FICDPhoto.h",
"chars": 1022,
"preview": "//\n// FICDPhoto.h\n// FastImageCacheDemo\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agreemen"
},
{
"path": "FastImageCache/FastImageCacheDemo/Classes/FICDPhoto.m",
"chars": 8061,
"preview": "//\n// FICDPhoto.m\n// FastImageCacheDemo\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agreemen"
},
{
"path": "FastImageCache/FastImageCacheDemo/Classes/FICDPhotosTableViewCell.h",
"chars": 841,
"preview": "//\n// FICDPhotosTableViewCell.h\n// FastImageCacheDemo\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full li"
},
{
"path": "FastImageCache/FastImageCacheDemo/Classes/FICDPhotosTableViewCell.m",
"chars": 5661,
"preview": "//\n// FICDPhotosTableViewCell.m\n// FastImageCacheDemo\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full li"
},
{
"path": "FastImageCache/FastImageCacheDemo/Classes/FICDTableView.h",
"chars": 306,
"preview": "//\n// FICDTableView.h\n// FastImageCacheDemo\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agre"
},
{
"path": "FastImageCache/FastImageCacheDemo/Classes/FICDTableView.m",
"chars": 3598,
"preview": "//\n// FICDTableView.m\n// FastImageCacheDemo\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license agre"
},
{
"path": "FastImageCache/FastImageCacheDemo/Classes/FICDViewController.h",
"chars": 239,
"preview": "//\n// FICDViewController.h\n// FastImageCacheDemo\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license"
},
{
"path": "FastImageCache/FastImageCacheDemo/Classes/FICDViewController.m",
"chars": 24954,
"preview": "//\n// FICDViewController.m\n// FastImageCacheDemo\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for full license"
},
{
"path": "FastImageCache/FastImageCacheDemo/Demo Images/README",
"chars": 127,
"preview": "Either place your own .jpg files in this directory, or run the fetch_demo_images.sh script in the FastImageCacheDemo dir"
},
{
"path": "FastImageCache/FastImageCacheDemo/FastImageCacheDemo-Prefix.pch",
"chars": 479,
"preview": "//\n// FastImageCacheDemo-Prefix.pch\n// FastImageCacheDemo\n//\n// Copyright (c) 2013 Path, Inc.\n// See LICENSE for ful"
},
{
"path": "FastImageCache/FastImageCacheDemo/Info.plist",
"chars": 1439,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "FastImageCache/FastImageCacheDemo/fetch_demo_images.sh",
"chars": 193,
"preview": "#!/bin/bash\n\necho \"Fetching demo images...\"\n`curl \"https://s3.amazonaws.com/fast-image-cache/demo-images/FICDDemoImage[0"
},
{
"path": "FastImageCache/FastImageCacheDemo/main.m",
"chars": 349,
"preview": "//\n// main.m\n// FastImageCacheDemo\n//\n// Created by Rui Peres on 17/06/2015.\n// Copyright (c) 2015 Path. All rights "
},
{
"path": "FastImageCache/FastImageCacheTests/FastImageCacheTests.m",
"chars": 883,
"preview": "//\n// FastImageCacheTests.m\n// FastImageCacheTests\n//\n// Created by Rui Peres on 17/06/2015.\n// Copyright (c) 2015 P"
},
{
"path": "FastImageCache/FastImageCacheTests/Info.plist",
"chars": 733,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "LICENSE",
"chars": 1076,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2013 Path, Inc.\n\nPermission is hereby granted, free of charge, to any person obtain"
},
{
"path": "README.md",
"chars": 36974,
"preview": "\n\n---\n[![Carthage compatibl"
}
]
About this extraction
This page contains the full source code of the path/FastImageCache GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 43 files (251.2 KB), approximately 63.7k tokens, and a symbol index with 3 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.