How to implement iOS to upload files by imitating breakpoint mechanism

How to implement iOS to upload files by imitating breakpoint mechanism

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.

  1. #pragma mark- write cache file
  2. - (NSString *)writeToCacheVideo:(NSData *)data appendNameString:(NSString *) name {
  3.      
  4. NSString *cachesDirectory = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
  5. NSString *createPath = [cachesDirectory stringByAppendingPathComponent:@ "video" ];
  6. NSFileManager *fileManager = [[NSFileManager alloc] init];
  7. [fileManager createDirectoryAtPath:createPath withIntermediateDirectories:YES attributes:nil error:nil];
  8. NSString *path = [cachesDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@ "/video/%.0f%@" ,[NSDate date ].timeIntervalSince1970, name ]];
  9. [data writeToFile:path atomically: NO ];
  10.      
  11. return path;
  12. }

Here I will just talk about the functions of several folders in the sandbox directory.

  1. 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
  2. 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
  3. 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.

  1. // model.filePath file path
  2. NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:model.filePath];
  3. // Move the file pointer
  4. // kSuperUploadBlockSize upload block size Here is 1M, i refers to the number of uploaded blocks (i = model.uploadedCount)
  5. [handle seekToFileOffset:kSuperUploadBlockSize * i];
  6. //Read data
  7. 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

  1. @implementation YJTDocUploadModel
  2. // Update model related data after uploading
  3. - (void)setUploadedCount:(NSInteger)uploadedCount {
  4.      
  5. _uploadedCount = uploadedCount;
  6.      
  7. self.uploadPercent = (CGFloat)uploadedCount / self.totalCount;
  8. self.progressLableText = [NSString stringWithFormat:@ "%.2fMB/%.2fMB" ,self.totalSize * self.uploadPercent /1024.0/1024.0,self.totalSize/1024.0/1024.0];
  9. if (self.progressBlock) {
  10. self.progressBlock(self.uploadPercent,self.progressLableText);
  11. }
  12. // Refresh the local cache
  13. [[YJTUploadManager shareUploadManager] refreshCaches];
  14.      
  15. }
  16. @ 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

  1. #pragma mark- first upload breakpoint
  2. // Initialize upload
  3. - (void)uploadData:(NSData *)data withModel:(YJTDocUploadModel *)model {
  4.      
  5. // Calculate the number of slices
  6. NSInteger count = data.length / (kSuperUploadBlockSize);
  7. NSInteger blockCount = data.length % (kSuperUploadBlockSize) == 0 ? count : count + 1;
  8.      
  9. // Assign values ​​to the model
  10. model.filePath = [self writeToCacheVideo:data appendNameString:model.lastPathComponent];
  11. model.totalCount = blockCount;
  12. model.totalSize = data.length;
  13. model.uploadedCount = 0;
  14. model.isRunning = YES;
  15.      
  16. // Upload the required parameters
  17. NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
  18.      
  19. parameters[@ "sequenceNo" ] = @0;
  20. parameters[@ "blockSize" ] = @(kSuperUploadBlockSize);
  21. parameters[@ "totFileSize" ] = @(data.length);
  22. parameters[@ "suffix" ] = model.filePath.pathExtension;
  23. parameters[@ "token" ] = @ "" ;
  24. NSString *requestUrl = @ "Upload interface" ;
  25.      
  26. AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
  27. NSURLSessionDataTask *dataTask = [manager POST:requestUrl parameters:parameters constructingBodyWithBlock:^(id _Nonnull formData) {
  28. [formData appendPartWithFileData:[NSData data] name :@ "block" fileName:model.filePath.lastPathComponent mimeType:@ "application/octet-stream" ];
  29.          
  30. } success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) {
  31.          
  32. NSDictionary *dataDict = responseObject[kRet_success_data_key];
  33. model.upToken = dataDict[@ "upToken" ];
  34.          
  35. NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:model.filePath];
  36. if (handle == nil) { return ; }
  37. [self continueUploadWithModel:model];
  38. [self addUploadModel:model];
  39.          
  40. [[VMProgressHUD sharedInstance] showTipTextOnly:@ "Uploading in the background" dealy:2];
  41.          
  42. } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) {
  43. [[VMProgressHUD sharedInstance] showTipTextOnly:error.localizedDescription dealy:1];
  44. }];
  45.      
  46. model.dataTask = dataTask;
  47. }

Core code

  1. #pragma mark- continue upload
  2. - (void)continueUploadWithModel:(YJTDocUploadModel *)model {
  3. if (!model.isRunning) {
  4. return ;
  5. }
  6. __block NSInteger i = model.uploadedCount;
  7.      
  8. NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
  9. parameters[@ "blockSize" ] = @(kSuperUploadBlockSize);
  10. parameters[@ "totFileSize" ] = @(model.totalSize);
  11. parameters[@ "suffix" ] = model.filePath.pathExtension;
  12. parameters[@ "token" ] = @ "" ;
  13. parameters[@ "upToken" ] = model.upToken;
  14. parameters[@ "crc" ] = @ "" ;
  15. parameters[@ "sequenceNo" ] = @(i + 1);
  16.      
  17. NSString *requestUrl = [[Api getRootUrl] stringByAppendingString:@ "Upload interface" ];
  18. AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
  19. NSURLSessionDataTask *dataTask = [manager POST:requestUrl parameters:parameters constructingBodyWithBlock:^(id _Nonnull formData) {
  20.          
  21. NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:model.filePath];
  22. [handle seekToFileOffset:kSuperUploadBlockSize * i];
  23. NSData *blockData = [handle readDataOfLength:kSuperUploadBlockSize];
  24. [formData appendPartWithFileData:blockData name :@ "block" fileName:model.filePath.lastPathComponent mimeType:@ "application/octet-stream" ];
  25.          
  26. } success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) {
  27.          
  28. i++;
  29. model.uploadedCount = i;
  30. NSDictionary *dataDict = responseObject[kRet_success_data_key];
  31. NSString *fileUrl = dataDict[@ "fileUrl" ];
  32. if ([fileUrl isKindOfClass:[NSString class]]) {
  33. [model.parameters setValue:fileUrl forKey:@ "url" ];
  34. // ***After all the clips are uploaded, the server returns the file URL and performs subsequent operations
  35. [self saveRequest:model];
  36. } else {
  37. if (i < model.totalCount) {
  38. [self continueUploadWithModel:model];
  39. }
  40. }
  41.          
  42. } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) {
  43. // Retry if upload fails
  44. [self continueUploadWithModel:model];
  45. }];
  46.      
  47. model.dataTask = dataTask;
  48. }

<<:  The twelfth episode of the Aiti tribe clinic: How to distribute tens of millions of web requests

>>:  WeChat ID can be changed at will? Official: It's just a bug

Recommend

Why can domestic mobile phones occupy nearly half of the market share in India?

Not long ago, according to overseas media reports...

Low budget user growth model!

"Growth hacking" must be familiar to th...

Why do sea turtles also drown in the ocean?

The Republic of Cape Verde is located in the west...

In-depth analysis of event operations (I): Preparation before the event

In the next few articles, I will introduce to you...

Product Operation: How to run a good lead generation training camp?

How to do a good drainage training camp, I will b...

What are the ten rivers with the largest flow in the world?

There are many large rivers in the world, and the...

Alipay password red envelope: common scenarios and gameplay summary

Passwords are not as simple as they seem. I will ...

Is a plane crash in India considered news? But this time is different

According to reports from India Today and Indian ...

[Wuzhuzhiyu] How to participate in the latest hot topics in 2022

[Wuzhuzhiyu] How to participate in the latest hot...