iOS - NSINputStream输入流的两种解决方案(适用大文件上传读取)

众所周知 , 移动端有时候挺受内存限制 , 特别是前几年还是512M时 , 如果读取一个几百M的视频 , 那么手机就直接崩溃了.. 近两年随着内存不断升级 , 情况已经好很多 , 大部分时候开发者已经不用考虑内存的问题 , 但是对于比较小众的需求 , 比如大文件上传下载 , 还是需要考虑内存的问题 , 所以需要考虑读取文件时分步读入或者以流的方式读出 . 而后台服务器也会经常要求你进行分段式上传 , 所以了解输入流和逐步读入文件必不可少 (下载输出流下篇文章写)

解决方案一:

NSInputStream输入流

举个反面栗子: 直接一次性将一个300M的大文件加载进内存

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{

    NSString *path = [[NSBundle mainBundle]pathForResource:@"03-FMDB的简单使用.mp4" ofType:nil];

    //一次性全部转换
    NSData *data = [NSData dataWithContentsOfFile:path];

}

结果就是这样..内存一下暴涨
iOS - NSINputStream输入流的两种解决方案(适用大文件上传读取)_第1张图片


使用NSInputStream输入流逐步读入 , 代码如下

1. //遵循代理协议
@interface ViewController ()<NSStreamDelegate>

@end
2.//创建NSInputStream对象 , 配置路径 , 加入运行循环等..
    NSString *path = [[NSBundle mainBundle]pathForResource:@"03-FMDB的简单使用.mp4" ofType:nil];

    //[NSInputStream inputStreamWithURL:<#(nonnull NSURL *)#>]
    //[NSInputStream inputStreamWithData:<#(nonnull NSData *)#>]

//根据路径创建输入流 , 创建输入流的方法有很多,如上
    NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:path];

    //设置代理
    inputStream.delegate = self;

    //加入运行循环
    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

    //打开输入流
    [inputStream open];
//3.实现代理方法
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{

//    NSStreamEventOpenCompleted = 1UL << 0,     // 输入流打开完成
//    NSStreamEventHasBytesAvailable = 1UL << 1,  //获取到字节数
//    NSStreamEventHasSpaceAvailable = 1UL << 2, //有可用的空间,不知道怎么翻译..
//    NSStreamEventErrorOccurred = 1UL << 3,     // 发生错误
//    NSStreamEventEndEncountered = 1UL << 4     //输入完成

    NSInputStream *inputStream = (NSInputStream *)aStream;

    switch (eventCode) {

            //开始输入
        case NSStreamEventHasBytesAvailable:

        {

            //定义一个数组
            uint8_t streamData[1000000];

           //返回输入长度
            NSUInteger length = [inputStream read:streamData maxLength:1000000];

            if (length) {

                //转换为data
                NSData *data = [NSData dataWithBytes:streamData length:length];

                NSLog(@"%lu",(unsigned long)data.length);

            }else{


                NSLog(@"没有数据");
            }
        }
            break;

            //异常处理
            case NSStreamEventErrorOccurred:

            NSLog(@"进行异常处理");

            break;

            //输入完成
            case NSStreamEventEndEncountered:
        {
            //输入流关闭处理
            [inputStream close];
            //从运行循环中移除
            [inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
            //置为空
            inputStream = nil;

        }
            break;
        default:
            break;
    }
}

解决方案二

操作NSFileHandle文件句柄

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{

    //文件路径
    NSString *path = [[NSBundle mainBundle]pathForResource:@"03-FMDB的简单使用.mp4" ofType:nil];

    //计算文件大小
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSDictionary *attriDict = [fileManager attributesOfItemAtPath:path error:nil];

    //文件属性列表
    //    (lldb) po attriDict
//    {
//        NSFileCreationDate = "2016-11-08 13:54:58 +0000";
//        NSFileExtensionHidden = 0;
//        NSFileGroupOwnerAccountID = 20;
//        NSFileGroupOwnerAccountName = staff;
//        NSFileModificationDate = "2016-11-08 13:54:58 +0000";
//        NSFileOwnerAccountID = 501;
//        NSFilePosixPermissions = 420;
//        NSFileReferenceCount = 1;
//        NSFileSize = 335670302;
//        NSFileSystemFileNumber = 12755195;
//        NSFileSystemNumber = 16777218;
//        NSFileType = NSFileTypeRegular;
//    }

//获取文件大小
    NSString * fileSize = attriDict[NSFileSize];

     NSLog(@"-------总大小%lld",fileSize.longLongValue);

    //循环次数
    NSInteger times = fileSize.longLongValue/1000000;

    //开始循环读取
    for (NSInteger index = 0; index < times ; index ++) {
       //创建句柄
    self.fileHandle = [NSFileHandle fileHandleForReadingAtPath:path];

//设置每次句柄偏移量
    [self.fileHandle seekToFileOffset:1000000 *(index + 1)];
        //获取每次读入data
    NSData *data = [self.fileHandle readDataOfLength:1000000];

    NSLog(@"-----%lu",(unsigned long)data.length);
                if (index == times - 1) {

    //关闭句柄
    [self.fileHandle closeFile];

        }
    }
}

总结 : 对于大文件的操作 , 两种方法都可以 , 并且各有利弊 . 第一种方法相对复杂 , 但是不用考虑循环次数 , 个人感觉效率更高一些 . 第二种写法相较简单 , 但是需要自己计算循环次数和句柄偏移量 , 看各自喜好 . 但是有一点 , 第二种方法 , 是在Stack Overflow上的说法 , 但是据我观察 , 并不能解决内存暴涨问题 . 所以建议第一种方法 .

你可能感兴趣的:(iOS,Objective-c)