FastImageCache之所以能够加快Image的显示,主要是由于:
1. 缓存了解码之后的rawdata到文件中。为之后的加载节省了decode的时间
2. 将文件中的rawdata直接映射到虚拟内存空间,利用缺页中断加载rawdata页面到RAM中。节省了创建buffer并填充buffer的时间
3. 从rawdata创建UIImage时注意了字节对齐。节省了animation时为了字节对齐而执行copy_image操作的时间
这三点加快了UIImageView加载UIImage到屏幕显示的时间。 针对这这些操作的关键代码进行分析。
1.缓存了解码之后的rawdata到文件中
核心代码在FICImageTable.m中的如下函数中
- (void)setEntryForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID imageDrawingBlock:(FICEntityImageDrawingBlock)imageDrawingBlock { ... // 创建一个FICImageTableEntry用来存储被画到context之上的raw data // 这里用mmap把文件映射到内存buffer中,从而使用这段内存buffer,之后详述见a FICImageTableEntry *entryData = [self _entryDataAtIndex:newEntryIndex]; // 准备好用来存放bitmap即raw data的context,内存空间由[entryData bytes]提供 CGContextRef context = CGBitmapContextCreate([entryData bytes], pixelSize.width, pixelSize.height, bitsPerComponent, _imageRowLength, colorSpace, bitmapInfo); CGColorSpaceRelease(colorSpace); CGContextTranslateCTM(context, 0, pixelSize.height); CGContextScaleCTM(context, _screenScale, -_screenScale); // 调用FICEntity提供的回调block来将original的image绘制到context上 imageDrawingBlock(context, [_imageFormat imageSize]); CGContextRelease(context); ... // 将context中已经画好的buffer回刷到文件中。之后详述见b [entryData flush]; }
a)创建FICImageTableEntry
- (FICImageTableEntry *)_entryDataAtIndex:(NSInteger)index { ... off_t entryOffset = index * _entryLength; size_t chunkIndex = (size_t)(entryOffset / _chunkLength); FICImageTableChunk *chunk = [self _chunkAtIndex:chunkIndex]; if (chunk != nil) { ... // entryData主要是通过FICImageTableChunk来初始化其使用的内存空间的 entryData = [[FICImageTableEntry alloc] initWithImageTableChunk:chunk bytes:mappedEntryAddress length:_entryLength]; ... }
再来看看FICImageTableChunk是如何创建的
- (FICImageTableChunk *)_chunkAtIndex:(NSInteger)index { ... // 通过_fileDescriptor、index和chunkLength来初始一个FICImageTableChunk实例 chunk = [[FICImageTableChunk alloc] initWithFileDescriptor:_fileDescriptor index:index length:chunkLength]; [self _setChunk:chunk index:index]; ... return chunk; } - (instancetype)initWithFileDescriptor:(int)fileDescriptor index:(NSInteger)index length:(size_t)length { self = [super init]; if (self != nil) { _index = index; _length = length; _fileOffset = _index * _length; // 这里mmap把fileDescriptor对应文件中的一段空间映射到虚拟内存空间,index和length可以算出其偏移 _bytes = mmap(NULL, _length, (PROT_READ|PROT_WRITE), (MAP_FILE|MAP_SHARED), fileDescriptor, _fileOffset); if (_bytes == MAP_FAILED) { _bytes = NULL; } } return self; }
b)回刷到文件中.
详见FICImageTableEntry.m的如下函数
- (void)flush { // 回刷的内容要按页面大小对齐,首先要取得pagesize 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); // 通过调用msync()函数来实现磁盘文件内容与共享内存区中的内容一致, MS_SYNC表示同步 int result = msync(pageAlignedAddress, bytesToFlush, MS_SYNC); ... }
2. 将文件中的rawdata直接映射到虚拟内存空间,从rawdata创建UIImage时注意了字节对齐
在FICImageTable.m中,由如下函数来提供已经缓存的image
- (UIImage *)newImageForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *)sourceImageUUID { // 在创建entryData时,将文件缓存的rawdata映射到了虚拟内存空间[entryData bytes] FICImageTableEntry *entryData = [self _entryDataForEntityUUID:entityUUID]; // 利用[entryData bytes]创建dataProvider CGDataProviderRef dataProvider = CGDataProviderCreateWithData((__bridge_retained void *)entryData, [entryData bytes], [entryData imageLength], _FICReleaseImageData); CGSize pixelSize = [_imageFormat pixelSize]; CGBitmapInfo bitmapInfo = [_imageFormat bitmapInfo]; // 保证按imageFormat提供的信息保证字节对齐,从而animation时防止copy_image的操作 NSInteger bitsPerComponent = [_imageFormat bitsPerComponent]; NSInteger bitsPerPixel = [_imageFormat bytesPerPixel] * 8; CGColorSpaceRef colorSpace = [_imageFormat isGrayscale] ? CGColorSpaceCreateDeviceGray() : CGColorSpaceCreateDeviceRGB(); // 创建CGImageRef CGImageRef imageRef = CGImageCreate(pixelSize.width, pixelSize.height, bitsPerComponent, bitsPerPixel, _imageRowLength, colorSpace, bitmapInfo, dataProvider, NULL, false, (CGColorRenderingIntent)0); CGDataProviderRelease(dataProvider); CGColorSpaceRelease(colorSpace); if (imageRef != NULL) { // 通过CGImageRef创建UIImage image = [[UIImage alloc] initWithCGImage:imageRef scale:_screenScale orientation:UIImageOrientationUp]; CGImageRelease(imageRef); } }