番号rd295,点墨中文网,姓名算命婚姻
项目开发中,有时候我们需要将本地的文件上传到服务器,简单的几张图片还好,但是针对iphone里面的视频文件进行上传,为了用户体验,我们有必要实现断点上传。其实也不是真的断点,这里我们只是模仿断点机制。
需求
既然需要上传文件,那最好要有一个上传列表界面,方面用户对上传中的文件进行实时管理。这里我简单搭建了一个上传列表界面,如下图:
该界面实现的功能:左滑删除,单击暂停、取消,清空列表。退出该界面可后台上传,暂停再次开始或则app被kill掉依旧支持续传。上传完成、删除正在上传文件、清空上传列表都会将本地缓存的文件删除。
实现方法
客户端把大文件切片,服务器接收完所有片后拼接成一个完整文件。
1.缓存文件
录制视频或者选择系统相册中的视频后需要写入文件到沙盒。因为如果不缓存,只是通过路径来获取视频,手机中的视频可能被删除。如果是选择系统自带压缩的话,文件只是存在了系统的某个cache文件夹下,系统可能会清理该文件件,那么下次再次根据路径获取视频的时候,就找不到了。
缓存文件就不再细说,在/library/caches 目录下面新建一个文件夹video用来缓存视频文件。之前看到用的文章存到了documents文件夹下,我是不建议的,之所以在这个目录下面,是因为系统不会清理这个文件夹,而且在进行icloud备份时也不会备份该文件夹下的内容。如果把一个很大的视频文件放到documents文件夹下,必然给用户带来不便。还有一点需要注意,正如上面所描述,上传完成、删除正在上传文件、清空上传列表都必须将本地缓存的文件删除。不然会导致app占用系统太多的空间,用户看到后直接把你的app卸载了。
为了防止重名,我在文件名中拼上了时间戳。
#pragma mark- write cache file - (nsstring *)writetocachevideo:(nsdata *)data appendnamestring:(nsstring *)name { nsstring *cachesdirectory = nssearchpathfordirectoriesindomains(nscachesdirectory, nsuserdomainmask, yes).firstobject; nsstring *createpath = [cachesdirectory stringbyappendingpathcomponent:@"video"]; nsfilemanager *filemanager = [[nsfilemanager alloc] init]; [filemanager createdirectoryatpath:createpath withintermediatedirectories:yes attributes:nil error:nil]; nsstring *path = [cachesdirectory stringbyappendingpathcomponent:[nsstring stringwithformat:@"/video/%.0f%@",[nsdate date].timeintervalsince1970,name]]; [data writetofile:path atomically:no]; return path; }
这里随便说下沙盒目录下几个文件夹的作用。
2.切片
切片主要用到nsfilehandle这个类,其实就是通过移动文件指针来读取某段内容。
// model.filepath 文件路径 nsfilehandle *handle = [nsfilehandle filehandleforreadingatpath:model.filepath]; // 移动文件指针 // ksuperuploadblocksize 上传切片大小 这里是1m, i指已上传片数(i = model.uploadedcount) [handle seektofileoffset:ksuperuploadblocksize * i]; //读取数据 nsdata *blockdata = [handle readdataoflength:ksuperuploadblocksize];
这里我将大文件切成最小1m的小文件来上传。这边使用到一个model,该数据模型主要存放上传列表中所需要的一些基本数据。因为我们每次上传完一片,需要更新ui。由于这边需要支持断点续传,因此需要记录文件的进度值,已上传的片数我们需要保存下来。保存上传文件路径和文件进度可以使用数据库或则plist文件等方式,这边需要保存的数据不是很多,所以我直接保存在了偏好设置中。每片文件上传成功,设置该模型已上传片数,并且更新本地文件进度值。
我们可以大致看下所用到的model
yjtuploadmanager.h
#import <foundation/foundation.h> @interface yjtdocuploadmodel : nsobject // 方便操作(暂停取消)正在上传的文件 @property (nonatomic, strong) nsurlsessiondatatask *datatask; // 总大小 @property (nonatomic, assign) int64_t totalsize; // 总片数 @property (nonatomic, assign) nsinteger totalcount; // 已上传片数 @property (nonatomic, assign) nsinteger uploadedcount; // 上传所需参数 @property (nonatomic, copy) nsstring *uptoken; // 上传状态标识, 记录是上传中还是暂停 @property (nonatomic, assign) bool isrunning; // 缓存文件路径 @property (nonatomic, copy) nsstring *filepath; // 用来保存文件名使用 @property (nonatomic, copy) nsstring *lastpathcomponent; // 以下属性用于给上传列表界面赋值 @property (nonatomic, assign) nsinteger doctype; @property (nonatomic, copy) nsstring *title; @property (nonatomic, copy) nsstring *progresslabletext; @property (nonatomic, assign) cgfloat uploadpercent; @property (nonatomic, copy) void(^progressblock)(cgfloat uploadpersent,nsstring *progresslabletext); // 保存上传成功后调用保存接口的参数 @property (nonatomic, strong) nsmutabledictionary *parameters; @end yjtuploadmanager.m #import "yjtdocuploadmodel.h" @implementation yjtdocuploadmodel // 上传完毕后更新模型相关数据 - (void)setuploadedcount:(nsinteger)uploadedcount { _uploadedcount = uploadedcount; self.uploadpercent = (cgfloat)uploadedcount / self.totalcount; self.progresslabletext = [nsstring stringwithformat:@"%.2fmb/%.2fmb",self.totalsize * self.uploadpercent /1024.0/1024.0,self.totalsize/1024.0/1024.0]; if (self.progressblock) { self.progressblock(self.uploadpercent,self.progresslabletext); } // 刷新本地缓存 [[yjtuploadmanager shareuploadmanager] refreshcaches]; } @end
3.上传
上传可以采用同步和异步执行。这里不太建议通过for遍历来开太多的线程上传,开线程是耗内存的。这边我是通过同步的方式。也就是采用递归,一片文件上传完毕后再上传下一片文件,如果失败,再次上传。有一点需要强调,最后一片的大小一般都比会小于预设的最小分割值。另外,如果分的片段大小大于文件的总大小也可能会出问题,客户端和服务器沟通好规则处理即可。
关于上传进度,可以粗略计算。也可使用nsurlsessiondatatask的countofbytessent实时监控。其实nsurlsessiontask在ios11以后还提供了progress属性。附上核心代码提供参考。
首次调用上传接口
#pragma mark- first upload 断点 // 上传初始化 - (void)uploaddata:(nsdata *)data withmodel:(yjtdocuploadmodel *)model { // 计算片数 nsinteger count = data.length / (ksuperuploadblocksize); nsinteger blockcount = data.length % (ksuperuploadblocksize) == 0 ? count : count + 1; // 给model赋值 model.filepath = [self writetocachevideo:data appendnamestring:model.lastpathcomponent]; model.totalcount = blockcount; model.totalsize = data.length; model.uploadedcount = 0; model.isrunning = yes; // 上传所需参数 nsmutabledictionary *parameters = [nsmutabledictionary dictionary]; parameters[@"sequenceno"] = @0; parameters[@"blocksize"] = @(ksuperuploadblocksize); parameters[@"totfilesize"] = @(data.length); parameters[@"suffix"] = model.filepath.pathextension; parameters[@"token"] = @""; nsstring *requesturl = @"上传接口"; afhttpsessionmanager *manager = [afhttpsessionmanager manager]; nsurlsessiondatatask *datatask = [manager post:requesturl parameters:parameters constructingbodywithblock:^(id<afmultipartformdata> _nonnull formdata) { [formdata appendpartwithfiledata:[nsdata data] name:@"block" filename:model.filepath.lastpathcomponent mimetype:@"application/octet-stream"]; } success:^(nsurlsessiondatatask * _nonnull task, id _nonnull responseobject) { nsdictionary *datadict = responseobject[kret_success_data_key]; model.uptoken = datadict[@"uptoken"]; nsfilehandle *handle = [nsfilehandle filehandleforreadingatpath:model.filepath]; if (handle == nil) { return; } [self continueuploadwithmodel:model]; [self adduploadmodel:model]; [[vmprogresshud sharedinstance] showtiptextonly:@"正在后台上传" dealy:2]; } failure:^(nsurlsessiondatatask * _nonnull task, nserror * _nonnull error) { [[vmprogresshud sharedinstance] showtiptextonly:error.localizeddescription dealy:1]; }]; model.datatask = datatask; }
核心代码
#pragma mark- continue upload - (void)continueuploadwithmodel:(yjtdocuploadmodel *)model { if (!model.isrunning) { return; } __block nsinteger i = model.uploadedcount; nsmutabledictionary *parameters = [nsmutabledictionary dictionary]; parameters[@"blocksize"] = @(ksuperuploadblocksize); parameters[@"totfilesize"] = @(model.totalsize); parameters[@"suffix"] = model.filepath.pathextension; parameters[@"token"] = @""; parameters[@"uptoken"] = model.uptoken; parameters[@"crc"] = @""; parameters[@"sequenceno"] = @(i + 1); nsstring *requesturl = [[api getrooturl] stringbyappendingstring:@"上传接口"]; afhttpsessionmanager *manager = [afhttpsessionmanager manager]; nsurlsessiondatatask *datatask = [manager post:requesturl parameters:parameters constructingbodywithblock:^(id<afmultipartformdata> _nonnull formdata) { nsfilehandle *handle = [nsfilehandle filehandleforreadingatpath:model.filepath]; [handle seektofileoffset:ksuperuploadblocksize * i]; nsdata *blockdata = [handle readdataoflength:ksuperuploadblocksize]; [formdata appendpartwithfiledata:blockdata name:@"block" filename:model.filepath.lastpathcomponent mimetype:@"application/octet-stream"]; } success:^(nsurlsessiondatatask * _nonnull task, id _nonnull responseobject) { i ++; model.uploadedcount = i; nsdictionary *datadict = responseobject[kret_success_data_key]; nsstring *fileurl = datadict[@"fileurl"]; if ([fileurl iskindofclass:[nsstring class]]) { [model.parameters setvalue:fileurl forkey:@"url"]; // 最后所有片段上传完毕,服务器返回文件url,执行后续操作 [self saverequest:model]; }else { if (i < model.totalcount) { [self continueuploadwithmodel:model]; } } } failure:^(nsurlsessiondatatask * _nonnull task, nserror * _nonnull error) { // 上传失败重试 [self continueuploadwithmodel:model]; }]; model.datatask = datatask; }
总结
以上所述是小编给大家介绍的ios 断点上传文件的实现方法,希望对大家有所帮助
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
iOS 使用UITextField自定义搜索框 实现用户输入完之后“实时搜索”功能
网友评论