The original link: blog.csdn.net/mumubumaopa…

Kill application after the continuation of the breakpoint implementation

Before using NSURLSession to do a breakpoint continuation demo, the main implementation of the download process to interrupt the download, and then can start again continue the last download link to continue the download function. Principle is the task of the resumeData cancelByProducingResumeData Block, Block () method gets down, when the download again, through the session downloadTaskWithResumeData method using the resumeDa The ta creates a new task and starts the download to implement the breakpoint continuation function. But if the task is being downloaded, the program was kill off after cut to the background, when to start the application again, will not be able to continue to download like last time, that is to say, just the kind of thinking, is only applicable to the user manual suspended in the case of the program does not exit the breakpoint continuingly, if applied directly to the end will not continue to download, that is not true Because when you start the application again, you still have to download it again.

So I started thinking about how I could start the app again and still continue the last download if the app quit unexpectedly. The data saved in the resumenData is the download information of the current task. If we serialize it into a string output, it will look like this (some of the redundant content has been cut out and important information has been annotated):

This block holds the status of the current download, including the name of the temporary file and the size of the current download. When the file is downloaded, will generate a in the TMP folder. TMP file, save the current actual download data, when through the session downloadTaskWithResumeData method using the resumeData to create a new task, and then start the download, tas K will find the file through the data block and continue downloading. So it looks like all we need to do is save the resumeData block and save the.tmp file that stores the data. When the app exits unexpectedly and starts the download task again, we just need to create the download task using resumeData and put the.tmp file in the TMP folder.

The question now is how to save the resumeData block and the.tmp file in the event of an unexpected exit. How do we do that?

Method one: when the program exit, the current controller invokes the – (void) viewWillDisappear: (BOOL) animated, application proxy invokes – applicationWillTerminate: (UIApplication *)application, we can work with these two methods.

In the – (void) viewWillDisappear: (BOOL) animated or – applicationWillTerminate: (UIApplication *) resumeData is stored in the cache folder

To get resuemData, you have to go through

__weaktypeof(self)weakSelf =self; [self. DownloadTaskcancelByProducingResumeData: ^ (NSData * _Nullable resumeData) {/ / here you can get to resumeDataweakSelf. ResumeData = resumeData; }];

The method to get resumeData, but in actual use, have met the question, whether put the code in the – (void) viewWillDisappear: (BOOL) animated or – applicationWillTerminate: (UIAp In the application method,weakSelf. ResumeData is always empty. After the test, when the program is running, the code in the block does not go, but all the code outside the block is executed. We all know that when a session is created, if the queue is set to nil, its proxy methods are done in the global concurrent queue. Is that why? Does the child thread code in either method not execute when the program exits unexpectedly? I did the following experiment:

Dispatch_async (dispatch_get_global_queue(0,0), ^{for(inti =0; i <100; i ++){NSLog(@”%d”,i); }});

When killing an application in the background, it sometimes prints 0, but if you add the following code to the method, the code inside will still not execute

dispatch_async(dispatch_get_main_queue(), ^{for(inti =0; i <100; i ++){NSLog(@”%d”,i); }});

But if you just take

for(inti=0; i<100; i++){

NSLog(@”%d”,i);

}

These lines of code can be executed completely in both methods, which makes people puzzled.

But think about it:

  • When we set the session to give priority to the thread, checked cancelByProducingResumeData methods can be found in the Block, Block is in the main thread running;

  • When we set the session threads is nil, the proxy method is executed in a global concurrent queue, including cancelByProducingResumeData method in Block, Block is also running in the child thread.

So we can speculate about, at the bottom of OC, cancelByProducingResumeData method is also like dispatch_async method is likely to add task thread queue to seize the opportunity of execution. With the above three experiments, MY guess is that when the program terminates, only the code in the main thread will be executed. If a task is added to the main thread through the fetch thread, the task will not be added to the main thread queue, let alone to the child thread. OC is a compiled language, when applied to end, only those who wrote the main thread of the above, the compiled code when end program can be implemented, and at the time of end dynamically add task cannot add success, so in these two methods, when the accident put an end to the program, it is not able to get to the resumeData.

Method 2: Dynamic save

Since resumeData cannot be retrieved at the end of the program, it can only be saved dynamically during the download process. When an application is downloaded, it calls its proxy method frequently:

-(void)URLSession:(NSURLSession*)session downloadTask:(NSURLSessionDownloadTask*)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWrittentotalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{ }}

Whenever there is a data over time, can call this method, by experiments can be found that this method is called frequency is very high, especially in the global concurrent queue execution, according to the test, the method of the call rate reached more than 1200 times, so if every download to save, so will greatly affect the efficiency of the application, because the file stream Input and output are inherently performance-intensive, and with such a high frequency of calls, the impact on performance is unimaginable. So I designed it to get resumeData every time a tenth of the file is downloaded.

Now that the resumeData problem is solved, it’s time to get the.tmp file. If we want to operate on the.tmp file, we need to get the full file name of the file, because most files in the TMP folder end in.tmp, and every time you download the.tmp file is different, so it would be very unreasonable to save all of the.tmp files. At this time, we used the resumeData we obtained, because after deserialization, we can know that resumeData is stored in the current download of the temporary file name, so we can parse resumeData, take out the temporary file name, and when downloaded, it must be in the TMP file And with that, we can save our.tmp file.

Finally adopted method flow:

With the resumeData and TMP files, we can continue the previous download at the next startup of the application, saving the file every tenth of the time it is downloaded. Specific operations are as follows:

The session download agent method detects the file download process, every time more than a tenth of the download time, obtains the resumeData block and the path of the. TMP file, then writes the resumeData to the Cache folder, copies the. TMP file to the Cache folder, at the same time in the Cache file A plist file is created under the folder. The key value is. TMP file name and the value is a Bool to indicate whether the file has been downloaded.

When the application starts again, read the download directory from the Cache folder, search for files that have not been downloaded, copy the temporary file named. TMP to the TMP folder, and obtain Resume_ + the file name Named the last saved resumeData data that is used to create a task and enable download.

Note: The Caches folder is the cache path provided by Apple for users, the TMP folder will not be cleared when the app restarts, while files in the Documents directory are uploaded to iCloud when backing up and soon run out of limited space, so we choose to cache the files we need in the Cache folder.

After this process, basically realize that when the program unexpectedly exits, restart can still continue the last unfinished download. However, there is a slight performance impact and it does not renew 100 percent of the data that was not downloaded last time, it loses a little bit (because it is not saved instantly). It would be better to use FMDB or some other database to manage the cached files, but this time it won’t work if you try to write them yourself.

I wrote a Demo for this function. When it is downloaded and started again, it will play the local file directly. It will continue to download if there is no cached data. The Demo includes a basic player and progress indicator that I packaged myself. The function of cache I wrote a singleton in the project, convenient for you to view. The Demo is hosted on GitHub at github.com/TheRuningAn… .

If everybody has better train of thought or suggestion, kneel beg give directions, thank you!

Join the audit rejected exchange group, exchange experience with the audit shelves ~~ group number: 689757099