FastImageCache核心分析

司马奇希
2023-12-01

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

 

 

转载于:https://www.cnblogs.com/liuguoming/p/3424842.html

 类似资料: