During project development, sometimes we need to upload local files to the server. It is OK for a few simple pictures, but for uploading video files in iPhone, for the sake of user experience, we need to implement breakpoint upload. In fact, it is not a real breakpoint, here we just imitate the breakpoint mechanism. need Since you need to upload files, you must have an upload list interface so that users can manage the uploaded files in real time. Here I simply built an upload list interface, as shown below: Functions implemented in this interface: Swipe left to delete, click to pause, cancel, and clear the list. Exit this interface to upload in the background, pause and restart, or the app is killed to continue uploading. When the upload is completed, the uploading file is deleted, and the upload list is cleared, the locally cached files will be deleted. Implementation The client slices the large file, and the server receives all the slices and stitches them into a complete file. 1. Cache files After recording a video or selecting a video from the system album, you need to write the file to the sandbox. If you do not cache and just get the video through the path, the video in the phone may be deleted. If you choose the system's own compression, the file is only stored in a cache folder of the system. The system may clean up the file, so the next time you get the video through the path, you will not be able to find it. I won't go into details about cache files. Create a new folder Video under the /Library/Caches directory to cache video files. I saw an article before that saved it in the Documents folder, but I don't recommend it. The reason why it is in this directory is that the system will not clean up this folder, and the contents of this folder will not be backed up during iCloud backup. If a large video file is placed in the Documents folder, it will inevitably cause inconvenience to the user. Another point to note is that, as described above, the local cached files must be deleted when the upload is completed, the uploading file is deleted, and the upload list is cleared. Otherwise, the app will take up too much space in the system, and the user will uninstall your app directly after seeing it. To prevent duplication, I put a timestamp in the file name. - #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;
- }
Here I will just talk about the functions of several folders in the sandbox directory. - Documents: Only documents and other data that is user-generated, or that cannot otherwise be recreated by your application, should be stored in the /Documents directory and will be automatically backed up by iCloud. //User-generated data, data that cannot be regenerated by the program, will be backed up by iCloud
- Library: Data that can be downloaded again or regenerated should be stored in the/Library/Caches directory. Examples of files you should put in the Caches directory include database cache files and downloadable content, such as that used by magazine, newspaper, and map applications. // Downloadable data
- tmp: Data that is used only temporarily should be stored in the/tmp directory. Although these files are not backed up to iCloud, remember to delete those files when you are done with them so that they do not continue to consume space on the user's device. // Temporary data
2. Slicing Slicing mainly uses the NSFileHandle class, which actually reads a certain section of content by moving the file pointer. - // model.filePath file path
- NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:model.filePath];
- // Move the file pointer
- // kSuperUploadBlockSize upload block size Here is 1M, i refers to the number of uploaded blocks (i = model.uploadedCount)
- [handle seekToFileOffset:kSuperUploadBlockSize * i];
- //Read data
- NSData *blockData = [handle readDataOfLength:kSuperUploadBlockSize];
Here I cut the large file into small files with a minimum size of 1M to upload. A Model is used here, which mainly stores some basic data required in the upload list. Because we need to update the UI every time we upload a piece. Since we need to support breakpoint resumption here, we need to record the progress value of the file, and we need to save the number of uploaded pieces. You can use a database or plist file to save the uploaded file path and file progress. There is not much data to save here, so I save it directly in the preferences. Each file is uploaded successfully, set the number of uploaded pieces of the model, and update the local file progress value. We can take a look at the Model used YJTUploadManager.h - @implementation YJTDocUploadModel
- // Update model related data after uploading
- - (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);
- }
- // Refresh the local cache
- [[YJTUploadManager shareUploadManager] refreshCaches];
-
- }
- @ end
3. Upload Upload can be performed synchronously or asynchronously. It is not recommended to open too many threads for uploading through for traversal, as opening threads consumes memory. Here I use the synchronous method. That is, recursion is used. After one file is uploaded, the next file is uploaded. If it fails, upload it again. One thing needs to be emphasized, the size of the first piece is generally smaller than the preset minimum segmentation value. In addition, if the size of the segment is larger than the total size of the file, there may be problems. The client and server can communicate the rules to handle it. You can roughly calculate the upload progress. You can also use countOfBytesSent of NSURLSessionDataTask to monitor in real time. In fact, NSURLSessionTask also provides a progress property after iOS11. The core code is attached for reference. ***Call upload interface - #pragma mark- first upload breakpoint
- // Initialize upload
- - (void)uploadData:(NSData *)data withModel:(YJTDocUploadModel *)model {
-
- // Calculate the number of slices
- NSInteger count = data.length / (kSuperUploadBlockSize);
- NSInteger blockCount = data.length % (kSuperUploadBlockSize) == 0 ? count : count + 1;
-
- // Assign values to the model
- model.filePath = [self writeToCacheVideo:data appendNameString:model.lastPathComponent];
- model.totalCount = blockCount;
- model.totalSize = data.length;
- model.uploadedCount = 0;
- model.isRunning = YES;
-
- // Upload the required parameters
- NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
-
- parameters[@ "sequenceNo" ] = @0;
- parameters[@ "blockSize" ] = @(kSuperUploadBlockSize);
- parameters[@ "totFileSize" ] = @(data.length);
- parameters[@ "suffix" ] = model.filePath.pathExtension;
- parameters[@ "token" ] = @ "" ;
- NSString *requestUrl = @ "Upload interface" ;
-
- AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
- NSURLSessionDataTask *dataTask = [manager POST:requestUrl parameters:parameters constructingBodyWithBlock:^(id _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:@ "Uploading in the background" dealy:2];
-
- } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) {
- [[VMProgressHUD sharedInstance] showTipTextOnly:error.localizedDescription dealy:1];
- }];
-
- model.dataTask = dataTask;
- }
Core code - #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:@ "Upload interface" ];
- AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
- NSURLSessionDataTask *dataTask = [manager POST:requestUrl parameters:parameters constructingBodyWithBlock:^(id _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" ];
- // ***After all the clips are uploaded, the server returns the file URL and performs subsequent operations
- [self saveRequest:model];
- } else {
- if (i < model.totalCount) {
- [self continueUploadWithModel:model];
- }
- }
-
- } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) {
- // Retry if upload fails
- [self continueUploadWithModel:model];
- }];
-
- model.dataTask = dataTask;
- }
|