在开发项目中,我们有可能会遇到上传大文件,比如几百兆,甚至于一个G,因此我们不能直接拿到文件的Path直接转化成NSData文件上传,这样的话我们会在项目中引用这个大的Data对象,可能直接会导致项目的运行内存暴涨,程序被强退.既然我们不能使用以前的方法,那么我们可以使用拿到文件所在的路径,然后将文件划分成数个小文件上传,这个其实也就是我们平时所说的分片上传,在这里我使用的是简单的上传方法,也就是一般项目中所用的,退出app不会保存上传记录的,废话不多说,直接上代码吧.
FileStreamOperation.h文件
#import
#define FileFragmentMaxSize 1024 *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;
@end
typedef NS_ENUM(NSInteger, FileUpState)
{
FileUpStateWaiting = 0,//加入到数组
FileUpStateLoading = 1,//正在上传
FileUpStateSuccess = 2//上传成功
};
//上传文件片
@interface FileFragment : NSObject
@property (nonatomic,copy)NSString *fragmentId; //片的唯一标识
@property (nonatomic,assign)NSUInteger fragmentSize; //片的大小
@property (nonatomic,assign)NSUInteger fragementOffset;//片的偏移量
@property (nonatomic,assign)FileUpState fragmentStatus; //上传状态
@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) NSUInteger fileSize;
@property (nonatomic, copy) NSString *filePath;
@property (nonatomic, strong) NSArray *fileFragments;
@property (nonatomic, strong) NSFileHandle *readFileHandle;
@property (nonatomic, strong) NSFileHandle *writeFileHandle;
@property (nonatomic, assign) BOOL isReadOperation;
@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 = [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
就这样一个简单的大文件上传就搞定了,但是需要注意的是我们还要和后端那边协商下,因为我们穿的data是一个分段的,也就是切片的,所以需要后端那边进行合并下,因此我们是要在上传的时候在哪里设置标识让后端进行区分,也是可以和后台那边进行协商的.就是这么简单任性...
demo地址:https://github.com/LUJYM/OC-Demo.git