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
推荐阅读
- 我要做大厨
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量
- 知识
- 三十年后的广场舞大爷
- 奔向你的城市
- 村里的故事|村里的故事 --赵大头
- 期刊|期刊 | 国内核心期刊之(北大核心)
- 华为旁!大社区、地铁新盘,佳兆业城市广场五期!
- 2020-04-07vue中Axios的封装和API接口的管理
- 汇讲-勇于突破