<article class="_2rhmJa">
在開發(fā)項(xiàng)目中,我們有可能會(huì)遇到上傳大文件,比如幾百兆,甚至于一個(gè)G,因此我們不能直接拿到文件的Path直接轉(zhuǎn)化成NSData文件上傳,這樣的話我們會(huì)在項(xiàng)目中引用這個(gè)大的Data對(duì)象,可能直接會(huì)導(dǎo)致項(xiàng)目的運(yùn)行內(nèi)存暴漲,程序被強(qiáng)退.既然我們不能使用以前的方法,那么我們可以使用拿到文件所在的路徑,然后將文件劃分成數(shù)個(gè)小文件上傳,這個(gè)其實(shí)也就是我們平時(shí)所說的分片上傳,在這里我使用的是簡單的上傳方法,也就是一般項(xiàng)目中所用的,退出app不會(huì)保存上傳記錄的,廢話不多說,直接上代碼吧.
FileStreamOperation.h文件
#import <Foundation/Foundation.h>
#define FileFragmentMaxSize 1024 *1024 // 1MB
@class FileFragment;
/**
* 文件流操作類
*/
@interface FileStreamOperation : NSObject<NSCoding>
@property (nonatomic, readonly, copy) NSString *fileName;//包括文件后綴名的文件名
@property (nonatomic, readonly, assign) NSUInteger fileSize;//文件大小
@property (nonatomic, readonly, copy) NSString *filePath;//文件所在的文件目錄
@property (nonatomic, readonly, strong) NSArray<FileFragment*> *fileFragments;//文件分片數(shù)組
+ (instancetype)sharedOperation;
//若為讀取文件數(shù)據(jù)凛驮,打開一個(gè)已存在的文件奋构。
//若為寫入文件數(shù)據(jù),如果文件不存在糠涛,會(huì)創(chuàng)建的新的空文件。(創(chuàng)建FileStreamer對(duì)象就可以直接使用fragments(分片數(shù)組)屬性)
- (instancetype)initFileOperationAtPath:(NSString*)path forReadOperation:(BOOL)isReadOperation ;
//獲取當(dāng)前偏移量
- (NSUInteger)offsetInFile;
//設(shè)置偏移量, 僅對(duì)讀取設(shè)置
- (void)seekToFileOffset:(NSUInteger)offset;
//將偏移量定位到文件的末尾
- (NSUInteger)seekToEndOfFile;
//關(guān)閉文件
- (void)closeFile;
#pragma mark - 讀操作
//通過分片信息讀取對(duì)應(yīng)的片數(shù)據(jù)
- (NSData*)readDateOfFragment:(FileFragment*)fragment;
//從當(dāng)前文件偏移量開始
- (NSData*)readDataOfLength:(NSUInteger)bytes;
//從當(dāng)前文件偏移量開始
- (NSData*)readDataToEndOfFile;
#pragma mark - 寫操作
//寫入文件數(shù)據(jù)
- (void)writeData:(NSData *)data;
@end
typedef NS_ENUM(NSInteger, FileUpState)
{
FileUpStateWaiting = 0,//加入到數(shù)組
FileUpStateLoading = 1,//正在上傳
FileUpStateSuccess = 2//上傳成功
};
//上傳文件片
@interface FileFragment : NSObject<NSCoding>
@property (nonatomic,copy)NSString *fragmentId; //片的唯一標(biāo)識(shí)
@property (nonatomic,assign)NSUInteger fragmentSize; //片的大小
@property (nonatomic,assign)NSUInteger fragementOffset;//片的偏移量
@property (nonatomic,assign)FileUpState fragmentStatus; //上傳狀態(tài)
@end
FileStreamOperation.m
#import "FileStreamOperation.h"
#import <CommonCrypto/CommonDigest.h>
//// 把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<FileFragment*> *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;
}
// 若為讀取文件數(shù)據(jù)喳篇,打開一個(gè)已存在的文件呆细。
// 若為寫入文件數(shù)據(jù),如果文件不存在燕差,會(huì)創(chuàng)建的新的空文件。
- (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;
// 塊數(shù)
NSUInteger chunks = (_fileSize%offset==0)?(_fileSize/offset):(_fileSize/(offset) + 1);
NSMutableArray<FileFragment *> *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;
}
// 通過分片信息讀取對(duì)應(yīng)的片數(shù)據(jù)
- (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 - 寫操作
// 寫入文件數(shù)據(jù)
- (void)writeData:(NSData *)data {
[_writeFileHandle writeData:data];
}
#pragma mark - common
// 獲取當(dāng)前偏移量
- (NSUInteger)offsetInFile{
if (_isReadOperation) {
return [_readFileHandle offsetInFile];
}
return [_writeFileHandle offsetInFile];
}
// 設(shè)置偏移量, 僅對(duì)讀取設(shè)置
- (void)seekToFileOffset:(NSUInteger)offset {
[_readFileHandle seekToFileOffset:offset];
}
// 將偏移量定位到文件的末尾
- (NSUInteger)seekToEndOfFile{
if (_isReadOperation) {
return [_readFileHandle seekToEndOfFile];
}
return [_writeFileHandle seekToEndOfFile];
}
// 關(guān)閉文件
- (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 <Foundation/Foundation.h>
@interface JYUpdataTool : NSObject
/**
根據(jù)路徑上傳本地文件
@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) {
// 線程安全,防止多次上傳同一塊區(qū)間
// @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]];
// 在這里執(zhí)行上傳的操作
[NSThread sleepForTimeInterval:0.2];
NSLog(@"這是第%zd個(gè)上傳----%@",self.currentIndex,[NSThread currentThread]);
self.currentIndex++;
}
} else {
NSLog(@"時(shí)間間隔是%zd",(int)[[NSDate date] timeIntervalSinceDate:self.date1]);
[NSThread exit];
}
}
// }
}
}
@end
就這樣一個(gè)簡單的大文件上傳就搞定了,但是需要注意的是我們還要和后端那邊協(xié)商下,因?yàn)槲覀兇┑膁ata是一個(gè)分段的,也就是切片的,所以需要后端那邊進(jìn)行合并下,因此我們是要在上傳的時(shí)候在哪里設(shè)置標(biāo)識(shí)讓后端進(jìn)行區(qū)分,也是可以和后臺(tái)那邊進(jìn)行協(xié)商的.就是這么簡單任性...
demo地址:https://github.com/LUJYM/OC-Demo.git
</article>