Objective
Network download is the function we often use in the project, if it is a small file download, such as pictures and text, we can directly request the source address, and then download the complete. But if you are downloading large audio and video files, it is not possible to download it once, the user may download a period of time, close the program, go home and then download. At this time, you need to implement the function of breakpoint continuation. Allow users to pause the download at any time, the next time to start downloading, but also the last download progress.
Today we'll look at how we can simply encapsulate a breakpoint continuation class to achieve the following functions.
1. The user only needs to call an interface to download, and can get the progress of the downloads.
2. Download successful, can get the location of file storage
3. Download failed, give reasons for failure
4. You can pause the download, the next time you start downloading, and then continue to download the last progress
Principle explanation
To achieve the function of the continuation of the breakpoint, it is usually necessary for the client to record the current download progress and notify the service side of the content fragment that needs to be downloaded when it needs to be continued.
In the HTTP1.1 Protocol (RFC2616) defines the continuation of a breakpoint related HTTP headers Range
and Content-Range
fields, one of the simplest extension implementations of breakpoints is probably as follows:
1. The client downloads a 1024K file and has downloaded 512K
2. Network interruption, client request renewal, so you need to declare in the HTTP header this need to continue the fragment: Range:bytes=512000-
this header notifies the server to start transferring files from the 512K location of the file
3. The server receives a breakpoint renewal request, starts transmission from the 512K location of the file, and increases in the HTTP header: Content-Range:bytes 512000-/1024000
and the HTTP status code returned by the server at this time should be 206 instead of 200.
Difficult notes
1. How the client obtains the number of bytes of files that have been downloaded
Client side, we need to record the size of each user download each time, and then implement the principle of step 1 of the function.
So how to record it?
In fact, we can directly get the size of the file under the specified path, iOS has provided the relevant functions, the implementation code as follows,
[[[Nsfilemanager Defaultmanager] Attributesofitematpath:filestorepath error:nil][nsfilesize] IntegerValue]
2. How to get the total bytes of downloaded files
In the previous step, we got the number of bytes of the downloaded file, we need to get the total number of bytes downloaded, and with these two values, we can work out the download progress.
So how do we get it? Here we need to use the HTTP header conten-length
field, first to see the meaning of the field
Content-Length
Used to describe the transmission length of an HTTP message entity the transfer-length of the message-body
. In the HTTP protocol, the message entity length differs from the message entity's transmission length, for example, under gzip compression, the message entity length is the length before compression, and the message entity's transmission length is the length of the gzip compression.
To put it simply, the content-length
number of bytes that represents the downloaded file.
In the third step of the principle of contrast, we can see that if you want to calculate the total number of bytes in the file, you must add the number of bytes that have already been downloaded content-length
.
We need to store the total number of bytes per downloaded file, where we choose to use the Plist file to record that the plist file contains a dictionary. Sets the file name to the key value, and the number of file bytes already downloaded is a value.
File name to prevent duplication, here we set the file name of the download URL hash
value, can be guaranteed not heavy.
The implementation code is as follows:
-(void) Urlsession: (Nsurlsession *) session Datatask: (Nsurlsessiondatatask *) Datatask Didreceiveresponse: ( Nshttpurlresponse *) Response Completionhandler: (void (^) (nsurlsessionresponsedisposition)) Completionhandler
{
self.totallength = [response.allheaderfields[@ "Content-length"] integervalue] + downloadlength;
Nsmutabledictionary *dict = [Nsmutabledictionary dictionarywithcontentsoffile:totallengthplist];
if (dict = = nil) Dict = [Nsmutabledictionary dictionary];
dict[Filename] = @ (self.totallength);
[Dict writetofile:totallengthplist atomically:yes];
}
The above NSSessionDelegate
method is called once when the request receives a response, and we can get the response information in the method and take out the content-length
field.
3. Package a method to achieve download progress, success, failure tips
We can imitate AFNetwork
, package the download to a method, and then use different block
to achieve the download progress, success, after the failure of the callback.
The definition is as follows:
-(void) Downloadwithurl: (NSString *) URL
progress: (Progressblock) Progressblock
success: (Successblock) Successblock
Faile: (faileblock) faileblock
{
self.successblock = Successblock;
Self.failedblock = Faileblock;
Self.progressblock = Progressblock;
Self.downloadurl = URL;
[Self.task resume];
}
The above three block
all adopt the macro definition way, this looks relatively concise, the concrete code refers to the following complete code.
We can then NSURLSessionDataDelegate
implement three calls in the corresponding proxy method block
, and then pass in the corresponding arguments. This way, when someone else calls our method, the callback can be implemented in the appropriate block
. The code refers to the complete code below
Full Code implementation
Here is the complete code implementation
#import <Foundation/Foundation.h> typedef void (^successblock) (NSString *
Filestorepath);
typedef void (^faileblock) (Nserror *error);
typedef void (^progressblock) (float progress);
@interface downloadmanager:nsobject <NSURLSessionDataDelegate> @property (copy) Successblock Successblock;
@property (copy) Faileblock Failedblock;
@property (copy) Progressblock Progressblock; -(void) Downloadwithurl: (NSString *) URL progress: (Progressblock) Progressblock success: (Successblock) SUCCESSB
Lock Faile: (Faileblock) Faileblock;
+ (instancetype) sharedinstance;
-(void) stoptask; @end
#import "DownLoadManager.h" #import "nsstring+hash.h" @interface downloadmanager ()/** Download Task * * @property (nonatomic, St
Rong) Nsurlsessiondatatask *task;
/** Session * * @property (nonatomic, strong) nsurlsession *session;
/** writes the stream object of the file * * * @property (nonatomic, strong) Nsoutputstream *stream;
The total size of the/** file * * * @property (nonatomic, assign) Nsinteger totallength;
@property (Nonatomic,strong) NSString *downloadurl; The @end//file name (the file name in the sandbox) is generated using the MD5 hash URL, which guarantees that the filename is unique #define FILENAME self.downLoadUrl.md5String//File storage path (caches) #define F Ilestorepath [[Nssearchpathfordirectoriesindomains (Nscachesdirectory, Nsuserdomainmask, YES) LastObject] Stringbyappendingpathcomponent:filename]//Use plist file to store downloaded file size #define Totallengthplist [ Nssearchpathfordirectoriesindomains (Nscachesdirectory, Nsuserdomainmask, YES) Lastobject] stringbyappendingpathcomponent:@ "Totallength.plist"]//file has been downloaded size #define DOWNLOADLENGTH [[[Nsfilemanager Defaultmanager] Attributesofitematpath:filestorepath Error:nil][nsfilesiZe] integervalue] @implementation Downloadmanager #pragma mark-Create a single instance static ID _instance;
+ (Instancetype) Allocwithzone: (struct _nszone *) zone {static dispatch_once_t oncetoken;
Dispatch_once (&oncetoken, ^{_instance = [Super Allocwithzone:zone];
});
return _instance;
} + (Instancetype) sharedinstance {static dispatch_once_t oncetoken;
Dispatch_once (&oncetoken, ^{_instance = [[Self alloc] init];
});
return _instance;
}-(ID) Copywithzone: (Nszone *) zone {return _instance;}
-(ID) Mutablecopywithzone: (Nszone *) zone {return _instance;} #pragma mark-Public Method-(void) Downloadwithurl: (NSString *) URL progress: (Progressblock) Progressblock success: (
Successblock) Successblock Faile: (faileblock) faileblock {self.successblock = Successblock;
Self.failedblock = Faileblock;
Self.progressblock = Progressblock;
Self.downloadurl = URL;
[Self.task resume];
}-(void) stoptask{[self.task suspend]; } #pragma mark-getter Method-(Nsurlsession *) session {if (!_session) {_session = [nsurlsession Sessionwithconfiguration:[nsurlsessionco
Nfiguration Defaultsessionconfiguration] delegate:self delegatequeue:[[nsoperationqueue alloc] init];
return _session; }-(Nsoutputstream *) stream {if (!_stream) {_stream = [Nsoutputstream Outputstreamtofileatpath:filestorepath AP
Pend:yes];
return _stream; }-(Nsurlsessiondatatask *) Task {if (!_task) {Nsinteger totallength = [[Nsdictionary dictionarywithcontentsoffi
le:totallengthplist][Filename] IntegerValue];
if (totallength && downloadlength = = totallength) {NSLog (@ "##### #文件已经下载过了");
return nil; }//Create request Nsmutableurlrequest *request = [Nsmutableurlrequest requestwithurl:[nsurl URLWithString:self.downLoadU
RL]]; Set the request header//range:bytes=xxx-xxx, download the nsstring *range = [NSString stringwithformat:@] from the length of the download to the end of the total length of the file Bytes=%zd
-", downloadlength]; [Request Setvalue:range Forhttpheaderfield:@ "Range"];
Create a data task _task = [Self.session datataskwithrequest:request];
return _task; #pragma mark-<NSURLSessionDataDelegate>/** * 1. Received response * *-(void) Urlsession: (Nsurlsession *) session Datatask: (Nsurlsessiondatatask *) datatask didreceiveresponse: (nshttpurlresponse *) response Completionhandler: (void (^) (
nsurlsessionresponsedisposition)) Completionhandler {//Open the stream [Self.stream open]; /* (The Content-length field returns the server for each client request to download the file size) such as first client request download file A, size 1000byte, then the first server returned content-length = 1000, the client download to 5 00byte, suddenly interrupted, again requested range is "bytes=500-", then the server returned Content-length 500 so for a single file for multiple downloads (breakpoint continued), calculate the total size of the file, Content-length must be returned by the server plus locally stored downloaded file size * * self.totallength = [response.allheaderfields[@ "Content-length"]
IntegerValue] + downloadlength; Store the downloaded file size in the plist file nsmutabledictionary *dict = [Nsmutabledictionary dictionarywithcontentsoffile:
Totallengthplist];
if (dict = = nil) Dict = [Nsmutabledictionary dictionary]; Dict[Filename] = @ (self.totallength);
[Dict writetofile:totallengthplist Atomically:yes];
Receives this request, allows the receiving server the data Completionhandler (Nsurlsessionresponseallow); /** * 2. Receive the data returned by the server (this method may be called n times)/-(void) Urlsession: (Nsurlsession *) session Datatask: (Nsurlsessiondatatask *) data
Task didreceivedata: (NSData *) data {//write to [Self.stream write:data.bytes maxLength:data.length];
Float Progress = 1.0 * DOWNLOADLENGTH/SELF.TOTALLENGTH;
if (self.progressblock) {self.progressblock (progress); }//Download Progress}/** * 3. Completion of request (success/Failure)/-(void) Urlsession: (Nsurlsession *) session Task: (Nsurlsessiontask *) Task Didcomple
Tewitherror: (Nserror *) Error {if (error) {if (Self.failedblock) {self.failedblock (error);
} Self.stream = nil;
Self.task = nil;
}else{if (self.successblock) {self.successblock (Filestorepath);
}//Close stream [Self.stream closed];
Self.stream = nil;
Clear task Self.task = nil; }} @end
How to call
@interface Viewcontroller ()
@end
@implementation viewcontroller
/**
* started downloading * *
(ibaction) Start: (ID) Sender {
//start Task
NSString * DownloadURL = @ "http://audio.xmcdn.com/group11/M01/93/AF/ WKGDA1DZZJLBL0GCAPUZEJQK84Y539.M4A ";
[[Downloadmanager Sharedinstance]downloadwithurl:downloadurl progress:^ (float progress) {
NSLog (@ "###%f", Progress);
} success:^ (NSString *filestorepath) {
NSLog (@ "###%@", Filestorepath);
} faile:^ (Nserror *error) {
NSLog (@ "###%@", Error.userinfo[nslocalizeddescriptionkey]);}
/**
* Suspend Download * *
(ibaction) pause: (ID) Sender {[
downloadmanager sharedinstance]stoptask];
}
@end
Summarize
Here can only achieve a single task to download, we can think of ways to do, see how to achieve multitasking download, and realize the function of breakpoint extension. And in order to make it easier to operate, it is recommended to replace storage information with database storage. The above is the entire content of this article, I hope to learn about the development of iOS help.