iOS上传大文件(切片上传)

在开发项目中,我们有可能会遇到上传大文件,比如几百兆,甚至于一个G,因此我们不能直接拿到文件的Path直接转化成NSData文件上传,这样的话我们会在项目中引用这个大的Data对象,可能直接会导致项目的运行内存暴涨,程序被强退.既然我们不能使用以前的方法,那么我们可以使用拿到文件所在的路径,然后将文件划分成数个小文件上传,这个其实也就是我们平时所说的分片上传,在这里我使用的是简单的上传方法,也就是一般项目中所用的,退出app不会保存上传记录的,废话不多说,直接上代码吧.
FileStreamOperation.h文件

#import #define FileFragmentMaxSize1024 *1024 // 1MB@class FileFragment; /** * 文件流操作类 */ @interface FileStreamOperation : NSObject @property (nonatomic, readonly, copy) NSString *fileName; //包括文件后缀名的文件名 @property (nonatomic, readonly, assign) NSUInteger fileSize; //文件大小 @property (nonatomic, readonly, copy) NSString *filePath; //文件所在的文件目录 @property (nonatomic, readonly, strong) NSArray *fileFragments; //文件分片数组+ (instancetype)sharedOperation; //若为读取文件数据,打开一个已存在的文件。 //若为写入文件数据,如果文件不存在,会创建的新的空文件。(创建FileStreamer对象就可以直接使用fragments(分片数组)属性) - (instancetype)initFileOperationAtPath:(NSString*)path forReadOperation:(BOOL)isReadOperation ; //获取当前偏移量 - (NSUInteger)offsetInFile; //设置偏移量, 仅对读取设置 - (void)seekToFileOffset:(NSUInteger)offset; //将偏移量定位到文件的末尾 - (NSUInteger)seekToEndOfFile; //关闭文件 - (void)closeFile; #pragma mark - 读操作 //通过分片信息读取对应的片数据 - (NSData*)readDateOfFragment:(FileFragment*)fragment; //从当前文件偏移量开始 - (NSData*)readDataOfLength:(NSUInteger)bytes; //从当前文件偏移量开始 - (NSData*)readDataToEndOfFile; #pragma mark - 写操作 //写入文件数据 - (void)writeData:(NSData *)data; @endtypedef NS_ENUM(NSInteger, FileUpState) { FileUpStateWaiting = 0,//加入到数组 FileUpStateLoading = 1,//正在上传 FileUpStateSuccess = 2//上传成功 }; //上传文件片 @interface FileFragment : NSObject @property (nonatomic,copy)NSString*fragmentId; //片的唯一标识 @property (nonatomic,assign)NSUIntegerfragmentSize; //片的大小 @property (nonatomic,assign)NSUIntegerfragementOffset; //片的偏移量 @property (nonatomic,assign)FileUpStatefragmentStatus; //上传状态 @end

FileStreamOperation.m
#import "FileStreamOperation.h" #import //// 把FileStreamOpenration类保存到UserDefault中 //static NSString *const UserDefaultFileInfo = @"UserDefaultFileInfo"; #pragma mark - FileStreamOperation@interface FileStreamOperation () @property (nonatomic, copy) NSString*fileName; @property (nonatomic, assign) NSUIntegerfileSize; @property (nonatomic, copy) NSString*filePath; @property (nonatomic, strong) NSArray*fileFragments; @property (nonatomic, strong) NSFileHandle*readFileHandle; @property (nonatomic, strong) NSFileHandle*writeFileHandle; @property (nonatomic, assign) BOOLisReadOperation; @end@implementation FileStreamOperation+ (instancetype)sharedOperation { static id instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[[self class] alloc] init]; }); return instance; }+ (NSString *)fileKey {CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); CFStringRef cfstring = CFUUIDCreateString(kCFAllocatorDefault, uuid); const char *cStr = CFStringGetCStringPtr(cfstring,CFStringGetFastestEncoding(cfstring)); unsigned char result[16]; CC_MD5( cStr, (unsigned int)strlen(cStr), result ); CFRelease(uuid); return [NSString stringWithFormat: @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%08lx", result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7], result[8], result[9], result[10], result[11], result[12], result[13], result[14], result[15], (unsigned long)(arc4random() % NSUIntegerMax)]; }- (void)encodeWithCoder:(NSCoder *)aCoder {[aCoder encodeObject:[self fileName] forKey:@"fileName"]; [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fileSize]] forKey:@"fileSize"]; [aCoder encodeObject:[self filePath] forKey:@"filePath"]; [aCoder encodeObject:[self fileFragments] forKey:@"fileFragments"]; }- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if (self != nil) { [self setFileName:[aDecoder decodeObjectForKey:@"fileName"]]; [self setFileSize:[[aDecoder decodeObjectForKey:@"fileSize"] unsignedIntegerValue]]; [self setFilePath:[aDecoder decodeObjectForKey:@"filePath"]]; [self setFileFragments:[aDecoder decodeObjectForKey:@"fileFragments"]]; }return self; }- (BOOL)getFileInfoAtPath:(NSString*)path {NSFileManager *fileMgr = [NSFileManager defaultManager]; if (![fileMgr fileExistsAtPath:path]) { NSLog(@"文件不存在:%@",path); return NO; }self.filePath = path; NSDictionary *attr =[fileMgr attributesOfItemAtPath:path error:nil]; self.fileSize = attr.fileSize; NSString *fileName = [path lastPathComponent]; self.fileName = fileName; return YES; }// 若为读取文件数据,打开一个已存在的文件。 // 若为写入文件数据,如果文件不存在,会创建的新的空文件。 - (instancetype)initFileOperationAtPath:(NSString*)path forReadOperation:(BOOL)isReadOperation {if (self = [super init]) { self.isReadOperation = isReadOperation; if (_isReadOperation) { if (![self getFileInfoAtPath:path]) { return nil; } self.readFileHandle = [NSFileHandle fileHandleForReadingAtPath:path]; [self cutFileForFragments]; } else { NSFileManager *fileMgr = [NSFileManager defaultManager]; if (![fileMgr fileExistsAtPath:path]) { [fileMgr createFileAtPath:path contents:nil attributes:nil]; }if (![self getFileInfoAtPath:path]) { return nil; }self.writeFileHandle = [NSFileHandle fileHandleForWritingAtPath:path]; } }return self; }#pragma mark - 读操作 // 切分文件片段 - (void)cutFileForFragments {NSUInteger offset = FileFragmentMaxSize; // 块数 NSUInteger chunks = (_fileSize%offset==0)?(_fileSize/offset):(_fileSize/(offset) + 1); NSMutableArray *fragments = [[NSMutableArray alloc] initWithCapacity:0]; for (NSUInteger i = 0; i < chunks; i ++) {FileFragment *fFragment = [[FileFragment alloc] init]; fFragment.fragmentStatus = FileUpStateWaiting; fFragment.fragmentId = [[self class] fileKey]; fFragment.fragementOffset = i * offset; if (i != chunks - 1) { fFragment.fragmentSize = offset; } else { fFragment.fragmentSize = _fileSize - fFragment.fragementOffset; }[fragments addObject:fFragment]; }self.fileFragments = fragments; }// 通过分片信息读取对应的片数据 - (NSData*)readDateOfFragment:(FileFragment*)fragment {if (fragment) { [self seekToFileOffset:fragment.fragementOffset]; return [_readFileHandle readDataOfLength:fragment.fragmentSize]; }return nil; }- (NSData*)readDataOfLength:(NSUInteger)bytes { return [_readFileHandle readDataOfLength:bytes]; }- (NSData*)readDataToEndOfFile { return [_readFileHandle readDataToEndOfFile]; }#pragma mark - 写操作// 写入文件数据 - (void)writeData:(NSData *)data { [_writeFileHandle writeData:data]; }#pragma mark - common // 获取当前偏移量 - (NSUInteger)offsetInFile{ if (_isReadOperation) { return [_readFileHandle offsetInFile]; }return [_writeFileHandle offsetInFile]; }// 设置偏移量, 仅对读取设置 - (void)seekToFileOffset:(NSUInteger)offset { [_readFileHandle seekToFileOffset:offset]; }// 将偏移量定位到文件的末尾 - (NSUInteger)seekToEndOfFile{ if (_isReadOperation) { return [_readFileHandle seekToEndOfFile]; }return [_writeFileHandle seekToEndOfFile]; }// 关闭文件 - (void)closeFile { if (_isReadOperation) { [_readFileHandle closeFile]; } else { [_writeFileHandle closeFile]; } }@end#pragma mark - FileFragment@implementation FileFragment- (void)encodeWithCoder:(NSCoder *)aCoder {[aCoder encodeObject:[self fragmentId] forKey:@"fragmentId"]; [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fragmentSize]] forKey:@"fragmentSize"]; [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fragementOffset]] forKey:@"fragementOffset"]; [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fragmentStatus]] forKey:@"fragmentStatus"]; }- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if (self != nil) { [self setFragmentId:[aDecoder decodeObjectForKey:@"fragmentId"]]; [self setFragmentSize:[[aDecoder decodeObjectForKey:@"fragmentSize"] unsignedIntegerValue]]; [self setFragementOffset:[[aDecoder decodeObjectForKey:@"fragementOffset"] unsignedIntegerValue]]; [self setFragmentStatus:[[aDecoder decodeObjectForKey:@"fragmentStatus"] integerValue]]; }return self; }@end

下面用到的才是最直接方便的,是我们所用到的上传工具类.
JYUpdataTool.h文件
#import @interface JYUpdataTool : NSObject/** 根据路径上传本地文件 @param path 文件所在的本地路径 */ -(void)upDataWithPath:(NSString *)path; @end

JYUpdataTool.m文件
#import "JYUpdataTool.h" #import "FileStreamOperation.h"@interface JYUpdataTool()@property(strong,nonatomic) FileStreamOperation *fileStreamer; @property(assign,nonatomic) NSInteger currentIndex; @property(nonatomic,strong)NSThread *thread1; @property(nonatomic,strong)NSThread *thread2; @property(nonatomic,strong)NSThread *thread3; @property(strong,nonatomic) NSDate *date1; @end@implementation JYUpdataTool-(void)upDataWithPath:(NSString *)path{FileStreamOperation *fileStreamer = [[FileStreamOperation alloc] initFileOperationAtPath:path forReadOperation:YES]; self.fileStreamer = fileStreamer; [self toUpData]; }#pragma mark懒加载 -(NSThread *)thread1{ if (!_thread1) { _thread1=[[NSThread alloc]initWithTarget:self selector:@selector(upOne) object:nil]; } return _thread1; } -(NSThread *)thread2{ if (!_thread2) { _thread2=[[NSThread alloc]initWithTarget:self selector:@selector(upOne) object:nil]; } return _thread2; } -(NSThread *)thread3{ if (!_thread3) { _thread3=[[NSThread alloc]initWithTarget:self selector:@selector(upOne) object:nil]; } return _thread3; }#pragma mark方法-(void)toUpData{ self.date1 = [NSDate date]; [self.thread1 start]; [self.thread2 start]; [self.thread3 start]; }-(void)upOne{ while (1) { //线程安全,防止多次上传同一块区间 //@synchronized (self) { @autoreleasepool { if (self.currentIndex < self.fileStreamer.fileFragments.count) { if (self.fileStreamer.fileFragments[self.currentIndex].fragmentStatus == FileUpStateWaiting) { self.fileStreamer.fileFragments[self.currentIndex].fragmentStatus = FileUpStateLoading; NSData *data = https://www.it610.com/article/[self.fileStreamer readDateOfFragment:self.fileStreamer.fileFragments[self.currentIndex]]; //在这里执行上传的操作 [NSThread sleepForTimeInterval:0.2]; NSLog(@"这是第%zd个上传----%@",self.currentIndex,[NSThread currentThread]); self.currentIndex++; }} else { NSLog(@"时间间隔是%zd",(int)[[NSDate date] timeIntervalSinceDate:self.date1]); [NSThread exit]; } } //} }}@end

【iOS上传大文件(切片上传)】就这样一个简单的大文件上传就搞定了,但是需要注意的是我们还要和后端那边协商下,因为我们穿的data是一个分段的,也就是切片的,所以需要后端那边进行合并下,因此我们是要在上传的时候在哪里设置标识让后端进行区分,也是可以和后台那边进行协商的.就是这么简单任性...
demo地址:https://github.com/LUJYM/OC-Demo.git

    推荐阅读