Viewer 源码阅读心得1——存储

版权所有, 转载请著明出处,保留链接。
Viewer 源码阅读心得1
Viewer 是一个IOS平台上的PDF阅读器,其源码地址:https://github.com/vfr/Viewer

##存储概括
Viewer存储涉及到的类有ReaderThumbRequest, ReaderThumbCache, ReaderThumbFetch, ReaderThumbRender, ReaderThumbQueue,对外的接口是ReaderThumbCache,是个单例,而ReaderThumbFetch, ReaderThumbRendder, ReaderThumbQueue对外是不可见的。对外主要接口如下:

- (id)thumbRequest:(ReaderThumbRequest *)request priority:(BOOL)priority

其内部,大体是根据request在内存查找Thumb, ReaderThumbCache职责,如果没有找到,则交给ReaderThumbFetch, 从磁盘上加载thumb, 如果磁盘上不存在,则交给ReaderThumbRender, 从PDF文档中渲染出Thumb,保存到内存,保存到磁盘。其中从磁盘上加载,PDF中渲染,耗时较长,采用任务队列,异步加载、渲染图片,任务Operation由ReaderThumbQueue管理,而ReaderThumbFetch和ReaderThumbRender都是Operation的子类。其过程如下:

ReaderThumbCahce--->ReaderThumbFetch--->ReaderThumbRender-->ReaderThumbQueue

其中ReaderThumbRequest是把他们串起来的对象,在他们间流动,是一个输入输出参数,输入指的是请求Thumb的信息,输出指的是最终获取的thumb(UIImage)。

###ReaderThumbRequest类
ReaderThumbRequest类是对获取Thumb(图标)请求信息的封装,就像一个HTTP请求一样,封装了获取Thumb所需要的信息,与http请求获取网络上的HTML不同的是,这次请求获取的Thumb位于磁盘或内存中,由于涉及到了IO操作,比较费时,因此,也采取了异步Operation的方式来获取Thumb.ReaderThumbRequest类的头文件如下:

@class ReaderThumbView;

@interface ReaderThumbRequest : NSObject <NSObject>

@property (nonatomic, strong, readonly) NSURL *fileURL;
@property (nonatomic, strong, readonly) NSString *guid;
@property (nonatomic, strong, readonly) NSString *password;
@property (nonatomic, strong, readonly) NSString *cacheKey;
@property (nonatomic, strong, readonly) NSString *thumbName;
@property (nonatomic, strong, readwrite) ReaderThumbView *thumbView;
@property (nonatomic, assign, readonly) NSUInteger targetTag;
@property (nonatomic, assign, readonly) NSInteger thumbPage;
@property (nonatomic, assign, readonly) CGSize thumbSize;
@property (nonatomic, assign, readonly) CGFloat scale;

+ (instancetype)newForView:(ReaderThumbView *)view fileURL:(NSURL *)url password:    (NSString *)phrase guid:(NSString *)guid page:(NSInteger)page size:(CGSize)size;

@end

其中fileURL:PDF在磁盘上的位置,guid PDF文档的唯一标识码,password PDF文档访问的密码,cacheKey 此thumb的缓存键值(thumbName+guid), thumbName:此thumb的文件名(page+宽+高), thumbView:thumb依附的视图,targetTag:cacheKey的hash值,scale 像素空间到点空间的放大缩小倍数,thumbPage 从PDF文档的第thumbPage页产生thumb.

对外接口是一个类方法:+(instancetype)newForView….,它做的事情是获取内存,调用初始化方法,初始化上述字段。

###ReaderThumbCache类
ReaderThumbCache类负责thumb的存储,包括内存上的缓存和磁盘上的文件存储。职责单一,分工明确,它是一个单例的类,有一个对外的接口类方法,获取单例:

+ (ReaderThumbCache *)sharedInstance;

在内部,其通过NSCache对Thumb在内存上进行缓存,NSCache就如一个字典,thumb的缓存的key是cacheKey, 值是thumb(UIImage),而在磁盘上的存放路径是:Library/Caches/GUIID/thumb.png

Documents
    ----1.pdf
    ----2.pdf
    .........
Library
    Application Support
        ---Reader.sqlite
    Caches
        ---1PDF文档的GUIID
            ---thumb1.png(page+宽+高)
            ---thumb2.png
            .......
        ---2PDF文档的GUIID
        ......
    Preferences
tmp

ReaderThumbCache的其他对外接口如下,分为类方法和实例方法,其中类方法主要处理Cache文件夹,实例方法主要处理缓存。

+ (void)touchThumbCacheWithGUID:(NSString *)guid;//创建guid对应pdf文档的存储位置(文件夹)

+ (void)createThumbCacheWithGUID:(NSString *)guid;//更新PDF存储文件夹的最后修改时间

+ (void)removeThumbCacheWithGUID:(NSString *)guid;//删除PDF存储文件夹,异步删除,在队列中删除

+ (void)purgeThumbCachesOlderThan:(NSTimeInterval)age;//清理Caches中的PDF文件夹,age之前的删除,异步删除

+ (NSString *)thumbCachePathForGUID:(NSString *)guid;//guid对应的pdf文件夹全名

- (id)thumbRequest:(ReaderThumbRequest *)request priority:(BOOL)priority;

- (void)setObject:(UIImage *)image forKey:(NSString *)key; //依据key在内存中缓存

- (void)removeObjectForKey:(NSString *)key;//依据key删除内存中的thumb缓存

- (void)removeNullForKey:(NSString *)key;//依据key删除内存中对应的是NULL的对象

- (void)removeAllObjects;//删除内存中的所有缓存

其中主要方法是:- (id)thumbRequest:(ReaderThumbRequest *)request priority:(BOOL)priority,现在来讲讲它,它的主要动因是提供接口给外层向ReaderThumbCache请求Thumb, 关于要获取什么样的thumb, 其信息已经封装在ReaderThumbRequest中了,至于返回的Thumb呢?输出到哪边,也输出到ReaderThumbRequest中(thumbView),其实可以看出ReaderThumbRequest是一个输入输出参数。该方法的逻辑过程是现在内存中找,如果内存中,实例化个获取Operation(Fetch Operation),放入队列,让它异步的去加载。

  1. 首先根据request中的cacheKey在内存中查找有无该thumb,如果存在,则直接返回,如果不存在,请走2
  2. 在thumbCache内存中给该cacheKey填一个Null占位符;
  3. 以request实例化一个ReaderThumbFetch对象;
  4. 根据优先级参数,设置ReaderThumbFetch实例的队列的优先级;
  5. 把对ReaderThumbFetch实例的引用赋值给request的thumbView的operation字段(传出参数);
  6. 根据优先级参数,设置ReaderThumbFetch实例的线程优先级
  7. 把ReaderThumbFetch实例(操作)加入加载队列中,由ReaderThumbQueue管理

###ReaderThumbFetch类
ReaderThumbFetch类的主要功能是从磁盘上获取Thumb,如果Thumb存在磁盘上,因为涉及到了IO操作,所以用了任务队列(loadQueue),异步加载,如果Thumb没有存在在磁盘上,则需要从PDF文档上渲染出来,这也比较费时,也用了任务队列(workQueue)。

ReaderThumbFetch类继承自ReaderThumbOperation,而ReaderThumbOperation又继承自NSOperation,显然它是一个任务(Operation), 而ReaderThumbOperation的头文件如下,其中guid是标识PDF文档的,一个PDF文档与一个唯一的guid对应。

@interface ReaderThumbOperation : NSOperation

@property (nonatomic, strong, readonly) NSString *guid;

- (instancetype)initWithGUID:(NSString *)guid;

@end

ReaderThumbFetch类的头文件如下,就只有一个初始化方法,以ReaderThumbRequest为参数,而ReaderThumbRequest封装了请求一个Thumb所用到的所用信息。既然其是一个operation(non-concurrent),来看看其main和cancel方法。

@class ReaderThumbRequest;

@interface ReaderThumbFetch : ReaderThumbOperation

- (instancetype)initWithRequest:(ReaderThumbRequest *)options;

@end

main方法的主要方法逻辑如下:

  1. 根据request中的信息构建thumb在磁盘上的路径信息,根据路径,创建加载路径,加载路径存在,则读取thumb的图片数据:

    CGImageRef imageRef = NULL; NSURL *thumbURL = [self thumbFileURL];
    
    CGImageSourceRef loadRef = CGImageSourceCreateWithURL((__bridge CFURLRef)thumbURL, NULL);
    
    if (loadRef != NULL) // Load the existing thumb image
    {
        imageRef = CGImageSourceCreateImageAtIndex(loadRef, 0, NULL); // Load it
    
        CFRelease(loadRef); // Release CGImageSource reference
    }
    
  2. 如果加载路径不存在,则创建ReaderThumbRender thumb渲染实例(从PDF文档中的某页,渲染到Context, 再保存成图片),根据ReaderThumbFetch操作的队列优先级设置ReaderThumbRender实例的队列优先级,根据ReaderThumbFetch线程的优先级设置ReaderThumbRender实例的线程优先级。如果ReaderThumbFetch的任务操作没有被取消,则更新request的thumbView的operation为ReaderThumbRender实例,并把渲染任务操作加入到工作队列中,由ReaderThumbQueue管理:

    ReaderThumbRender *thumbRender = [[ReaderThumbRender alloc] initWithRequest:request]; // Create a thumb render operation
    
    [thumbRender setQueuePriority:self.queuePriority]; [thumbRender setThreadPriority:(self.threadPriority - 0.1)]; // Priority
    
    if (self.isCancelled == NO) // We're not cancelled - so update things and add the render operation to the work queue
    {
        request.thumbView.operation = thumbRender; // Update the thumb view operation property to the new operation
    
        [[ReaderThumbQueue sharedInstance] addWorkOperation:thumbRender]; return; // Queue the operation
    }
    
  3. 如果成功读到了图片数据,则根据图片数据创建UIImage对象,并把它画在内存中,存储在ReaderThumbCache的缓存中,如果ReaderThumbFetch的任务没有被取消,则调度在主队列中在thumbView显示图片,代码如下:

    if (imageRef != NULL) // Create a UIImage from a CGImage and show it
    {
        UIImage *image = [UIImage imageWithCGImage:imageRef scale:request.scale orientation:UIImageOrientationUp];
    
        CGImageRelease(imageRef); // Release the CGImage reference from the above thumb load code
    
        UIGraphicsBeginImageContextWithOptions(image.size, YES, request.scale); // Graphics context
    
        [image drawAtPoint:CGPointZero]; // Decode and draw the image on this background thread
    
        UIImage *decoded = UIGraphicsGetImageFromCurrentImageContext(); // Newly decoded image
    
        UIGraphicsEndImageContext(); // Cleanup after the bitmap-based graphics drawing context
    
        [[ReaderThumbCache sharedInstance] setObject:decoded forKey:request.cacheKey]; // Cache it
    
        if (self.isCancelled == NO) // Show the image in the target thumb view on the main thread
        {
            ReaderThumbView *thumbView = request.thumbView; // Target thumb view for image show
    
            NSUInteger targetTag = request.targetTag; // Target reference tag for image show
    
            dispatch_async(dispatch_get_main_queue(), // Queue image show on main thread
            ^{
                if (thumbView.targetTag == targetTag) [thumbView showImage:decoded];
            });
        }
    }
    

###ReaderThumbRender类
ReaderThumbReader也继承于ReaderThumbOperation,其头文件如下,其主要任务是从PDF文档中渲染出Thumb,并保存到磁盘。

@class ReaderThumbRequest;

@interface ReaderThumbRender : ReaderThumbOperation

- (instancetype)initWithRequest:(ReaderThumbRequest *)options;

@end 

其main方法逻辑过程如下:

  1. 依据request中的信息fileURL, password, 创建对PDF文档的引用:

    CGPDFDocumentRef thePDFDocRef = CGPDFDocumentCreateUsingUrl(fileURL, password);
    
  2. 从PDF文档的引用中获取PDF页面

    CGPDFPageRef thePDFPageRef = CGPDFDocumentGetPage(thePDFDocRef, page);
    
  3. 从request中获取thumb的宽和长:thumb_w, thumb_h,从PDF页面引用中获取页面的宽和长:page_w,page_h,计算从page缩放到thumb的缩放因子target_w, target_h,在计算目标thumb的target_w, target_h,以目标区域在context创建绘画空间,再把该PDF页面画上去,然后依据context获取图片

    CGContextDrawPDFPage(context, thePDFPageRef); // Render the PDF page into the custom CGBitmap context
    imageRef = CGBitmapContextCreateImage(context); // Create CGImage from custom CGBitmap context
    
  4. 如果从context获取图片数据成功,则根据imageRef创建UIImage,把UIImage保存到ReaderThumbCache的缓存中;如果ReaderThumbRender实例任务操作没有被取消,则调度主队列在thumbView中显示图片;保存UIImage到磁盘上。

  5. 如果从context获取图片失败,则在ReaderThumbCache中删除cacheKey对应的Null值的占位对象

###ReaderThumbQueue类
ReaderThumbQueue类是对任务队列的封装,其头文件接口如下,显然它是一个单例,封装了loadQueue(加载队列,存放ReaderThumbFetch), workQueue(工作队列,封装了ReaderThumbRender),队列都不是并行的,最大时只有一个任务。

@interface ReaderThumbQueue : NSObject <NSObject>

+ (ReaderThumbQueue *)sharedInstance;

- (void)addLoadOperation:(NSOperation *)operation; //加入loadQueue

- (void)addWorkOperation:(NSOperation *)operation; //加入workQuueue

- (void)cancelOperationsWithGUID:(NSString *)guid; //撤销guid标识的任务

- (void)cancelAllOperations; //撤销所有任务

@end

版权所有, 转载请著明出处,保留链接。