实现一个简单沙盒文件浏览器(二)

1,643 阅读6分钟

往期文章

寻找IOS相册中相似图片
NSNotification与类对象,实例对象
CocoaPods私有源搭建
雷达扩散效果搜索设备
实现一个简单沙盒文件浏览器(一)

在上一节中我们实现了简单沙盒文件浏览器的导入数据功能,文件类型判断功能。在今天这一节中,我们主要实现沙盒文件浏览器文件的展示。

目录数据列表获取

获取目录下文件夹和文件的路径

在这里我们沙盒浏览器展示的根目录是Documents,所以我们首先需要获取Document文件夹下,所有的文件以及文件夹

- (nullable NSArray<NSString *> *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error

使用NSFileManager提供的方法,我们可以成功获取的该目录下所有文件以及文件夹的名称。

注意 该方法对当前目录进行浅搜索,所谓浅搜索也就是不会遍历任何符号链接(软链接)。不会搜索该目录下任何子目录的内容,也不会返回当前目录(.) 以及当前目录的父目录(..)。但是该方法会返回当前目录中的隐藏文件以及隐藏目录比如(.Trash目录)

返回的形式是一个文件名数组

@[@"Folder1",@"Folder2",@"p1.jpg".@"m1.mov"]

获取文件名和文件夹的元数据(metadata)

为了获取文件的metadata,同样我们需要使用NSFileManager的提供的方法

- (nullable NSDictionary<NSFileAttributeKey, id> *)attributesOfItemAtPath:(NSString *)path error:(NSError **)error

这里需要传入的path是文件夹和文件的绝对路径,而上一步我们获取的path是相对于父目录(Documents)的相对路径,所以我们需要将父目录(Documents)的path后面拼接上我们的文件名。从而得到绝对路径。

返回的元数据如下

{

NSFileCreationDate = "2021-09-12 00:37:31 +0000";

NSFileExtensionHidden = 0;

NSFileGroupOwnerAccountID = 501;

NSFileGroupOwnerAccountName = mobile;

NSFileModificationDate = "2021-09-12 00:37:31 +0000";

NSFileOwnerAccountID = 501;

NSFileOwnerAccountName = mobile;

NSFilePosixPermissions = 420;

NSFileProtectionKey = NSFileProtectionCompleteUntilFirstUserAuthentication;

NSFileReferenceCount = 1;

NSFileSize = 2070195;

NSFileSystemFileNumber = 2911611;

NSFileSystemNumber = 16777222;

NSFileType = NSFileTypeRegular;

}

文件缩略图获取

经过上面的步骤之后,我们已经成功拿到的需要展示在沙盒文件浏览器中的数据。并且在上一节中我们知道了可以通过UTType来区分不同类型的文件,像文档,目录这样的文件,我们可以使用固定的图片来展示其icon,但是像图片和视频之类的文件就需要我们手动去生成缩略图。

图片获取缩略图

首先如果我们不使用缩略图的话,直接读取沙盒文件中的原图。会造成两个问题

第一如果沙盒里面图片过多,就会读取大量的图片数据UIImage到内存中,从而造成内存暴涨。

第二,每次都读取原图的会使用大量的IO影响我们程序的性能

所以我们需要去对沙盒里面的图片进行缩略图生成,这里我们使用ImageIO给我们提供的缩略图生成方法

+ (UIImage *)getThumbnailImageWithPath:(NSString *)path

{
    CGImageSourceRef imageSource;
    imageSource = CGImageSourceCreateWithURL((CFURLRef)[NSURL fileURLWithPath:path], NULL);
    if (imageSource == nil) {
        return nil;
    }

    // 图片宽高
    int imageSize = 100*2;
    // 缩略图尺寸
    CFNumberRef thumbSize = CFNumberCreate(NULL, kCFNumberIntType, &imageSize);
    CFTypeRef imageValues[3];
    CFStringRef imageKeys[3];

    imageKeys[0] = kCGImageSourceCreateThumbnailWithTransform;
    imageValues[0] = (CFTypeRef)kCFBooleanTrue;

    imageKeys[1] = kCGImageSourceCreateThumbnailFromImageIfAbsent;
    imageValues[1] = (CFTypeRef)kCFBooleanTrue;

    //缩放键值对

    imageKeys[2] = kCGImageSourceThumbnailMaxPixelSize;
    imageValues[2] = (CFTypeRef)thumbSize;

    CFDictionaryRef imageOption = CFDictionaryCreate(NULL, (const void **) imageKeys,

                                      (const void **) imageValues, 3,

                                      &kCFTypeDictionaryKeyCallBacks,

                                      &kCFTypeDictionaryValueCallBacks);
    //获取缩略图

    CGImageRef thumbImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, imageOption);

    CFRelease(imageOption);
    CFRelease(imageSource);
    CFRelease(thumbSize);

    UIImage* thumbnailImage = [UIImage imageWithCGImage:thumbImage];
    //显示缩略图
    return thumbnailImage;

}

注意这里使用的是CoreFoundation,不支持ARC,所以记得使用CFRelease释放申请的对象,不然会造成内存泄漏

视频缩略图获取

使用AVAssetImageGenerator来获取视频的某一帧作为图片。

+ (UIImage *)getImageForVideoWithURLStr:(NSString *)urlStr
{
    if (!urlStr.length) return nil;
    UIImage *shotImage;
    AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:urlStr] options:nil];
    AVAssetImageGenerator *gen = [[AVAssetImageGenerator alloc] initWithAsset:asset];
    gen.appliesPreferredTrackTransform = YES;
    CMTime time = CMTimeMakeWithSeconds(0.0, 600);
    NSError *error = nil;
    CMTime actualTime;
    CGImageRef image = [gen copyCGImageAtTime:time actualTime:&actualTime error:&error];
    shotImage = [[UIImage alloc] initWithCGImage:image];
    CGImageRelease(image);
    return shotImage;
}

我们根据沙盒中视频的路径,生成了一个AVURLAsset对象,然后通过这个AVURLAsset对象构造出一个AVAssetImageGenerator。

初始化AVAssetImageGenerator的参数 并设置CMTime time = CMTimeMakeWithSeconds(0.0, 600);

CMTime CMTimeMakeWithSeconds(
     Float64 seconds,   //第几秒的截图,是当前视频播放到的帧数的具体时间
     int32_t preferredTimeScale //首选的时间尺度 "每秒的帧数"
 );

第一个参数seconds代表截取视频的第几秒作为截图

第二个参数代表每秒的帧数

这里我们希望获取第一帧图片,所以第一个参数为0代表第0秒,第二个参数尽可能大

接下来我们在传入一个actualTime,用来表示AVAssetImageGenerator生成视频截图的确切时间。

将两个CMTime传入生成截图

[gen copyCGImageAtTime:time actualTime:&actualTime error:&error];

缩略图的缓存

参考SDWebImage每次下载完图片之后,都会进行缓存从而避免多次下载相同的资源。所里这里我们也参看SDWebImage的原理,每次生成沙盒视频和图片的缩略图之后,将生成的好的图片缓存在沙盒的一个目录中 如下图所示,我在tmp文件夹下创建了一个ImageCache的目录用来保存生成好的缩略图 image.png

然后将生成好的缩略图按照原图的绝对路径的hash值,作为缩略图的文件名存入我们生成好的缓存目录中。

在沙盒文件浏览器展示沙盒文件的读取图片缩略图的时候,先去缓存文件夹中查找,如果有就立刻返回对于的缩略图,如果没有在进行缩略图获取。在成功获取到缩略图之后。立刻缓存获取的缩略图都缓存文件夹中。

实现代码如下:

- (UIImage*) getSanboxThumbnailAtPath:(AMSanboxFileModel*) model {

    //尝试去缓存路面取image
    UIImage* image = [self getCacheSanboxImage:model.fileName];

    if (image) {
        //如果获取到缓存文件则立刻返回缓存文件
        return image;
    }

    else {
        //如果没有获取到缓存文件,则调用各自缩略图请求器进行请求缩略图
        if (model.type == AMSanboxImage) {
            image = [AMImageManager getThumbnailImageWithPath:model.path];
        }

        else if (model.type == AMSanboxViedo){
            image = [AMAVAssetManager getImageForVideoWithURLStr:model.path];
        }
        //写入缓存
        [self cacheSanboxImage:image  forKey:model.fileName];
        return image;
    }
    return nil;
}

/// 根据key获取缓存图片
/// **@param** key 图片名称
- (UIImage*) getCacheSanboxImage:(NSString*) key {

    NSString* cachePath = [self createFolderWithPath:[self defaultDiskCacheDirectory]];

    NSString* fileName = [cachePath stringByAppendingPathComponent:key];

    NSData* imageData = [NSData dataWithContentsOfFile:fileName];

    return imageData ? [UIImage imageWithData:imageData] : nil;

}

///缓存UIImage
- (void) cacheSanboxImage:(UIImage*) image forKey:(NSString*) key{

    //创建保存目录,如果存在就直接返回目录,如果不存在就创建目录

    NSString* cachePath = [self createFolderWithPath:[self defaultDiskCacheDirectory]];

    NSData* imageData = UIImagePNGRepresentation(image);

    NSString* fileName = [cachePath stringByAppendingPathComponent:key];

    bool writeError = NO;


    writeError = [imageData writeToFile:fileName atomically:NO];

    if (!writeError) {
        NSLog(@"写入缓存成功");
    }

    else {
        NSLog(@"写入缓存失败");
    }
}

这样一来我们的沙盒浏览器就能成功的显示沙盒文件中的缩略图了

B2139C5F-0BCA-463D-A467-589A24FA804F_1_105_c.jpeg