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

<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>

作者鏈接:http://www.reibang.com/p/567968830431

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坝冕,一起剝皮案震驚了整個(gè)濱河市徒探,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌喂窟,老刑警劉巖测暗,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件央串,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡碗啄,警方通過查閱死者的電腦和手機(jī)质和,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挫掏,“玉大人侦另,你說我怎么就攤上這事秩命∥竟玻” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵弃锐,是天一觀的道長袄友。 經(jīng)常有香客問我,道長霹菊,這世上最難降的妖魔是什么剧蚣? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮旋廷,結(jié)果婚禮上鸠按,老公的妹妹穿的比我還像新娘。我一直安慰自己饶碘,他們只是感情好目尖,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著扎运,像睡著了一般瑟曲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上豪治,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天洞拨,我揣著相機(jī)與錄音,去河邊找鬼负拟。 笑死烦衣,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掩浙。 我是一名探鬼主播花吟,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼涣脚!你這毒婦竟也來了示辈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤遣蚀,失蹤者是張志新(化名)和其女友劉穎矾麻,沒想到半個(gè)月后纱耻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡险耀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年弄喘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甩牺。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蘑志,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贬派,到底是詐尸還是另有隱情急但,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布搞乏,位于F島的核電站波桩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏请敦。R本人自食惡果不足惜镐躲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望侍筛。 院中可真熱鬧萤皂,春花似錦、人聲如沸匣椰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窝爪。三九已至弛车,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蒲每,已是汗流浹背纷跛。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邀杏,地道東北人贫奠。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像望蜡,于是被迫代替她去往敵國和親唤崭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容