iOS开发基础42-网络编程之文件下载与处理

在iOS应用开发中,文件下载是一个常见的需求,可能涉及下载小图片、音频文件或者大型的视频、压缩包等。本文将详细介绍如何在iOS中进行文件下载,包括小文件和大文件的下载处理,文件的压缩/解压缩,获取文件MIMEType,以及文件上传和断点续传。

一、小文件下载

对于小文件的下载,可以直接使用NSURLConnection的异步请求方法:

NSURL *url = [NSURL URLWithString:@"http://example.com/path/to/file.png"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];

[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    if (data) {
        UIImage *image = [UIImage imageWithData:data];
        // 将图片用于界面展示或保存等操作
    } else {
        // 错误处理
        NSLog(@"Download failed with error: %@", connectionError);
    }
}];

二、大文件下载

对于大文件的下载,直接使用内存处理会导致内存暴涨,因此需要更高效的方式来处理大文件的下载。下面介绍三种处理方式:

1. 使用 NSMutableData

这种方法简单但会导致内存暴涨,不推荐:

@property (nonatomic, strong) NSMutableData *fileData;

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.fileData appendData:data];
}

2. 使用 NSFileHandle

NSFileHandle 是专门用于文件操作的类,通过创建文件句柄,将下载的数据写入文件,节省内存:

@interface ViewController ()<NSURLConnectionDataDelegate>
@property (nonatomic, strong) NSFileHandle *fileHandle;
@property (nonatomic, copy) NSString *filePath;
@property (nonatomic, assign) NSUInteger totalLength;
@property (nonatomic, assign) NSUInteger currentLength;
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSURL *url = [NSURL URLWithString:@"http://example.com/path/to/file.mp4"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [NSURLConnection connectionWithRequest:request delegate:self];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    NSString *fileName = response.suggestedFilename;
    self.filePath = [[self documentsDirectory] stringByAppendingPathComponent:fileName];
    [[NSFileManager defaultManager] createFileAtPath:self.filePath contents:nil attributes:nil];
    self.fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.filePath];
    self.totalLength = response.expectedContentLength;
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.fileHandle seekToEndOfFile];
    [self.fileHandle writeData:data];
    self.currentLength += data.length;
    self.progressView.progress = (CGFloat)self.currentLength / self.totalLength;
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [self.fileHandle closeFile];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"Download failed with error: %@", error);
}

- (NSString *)documentsDirectory {
    return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
}

@end

3. 使用 NSOutputStream

NSOutputStream 是另一种处理大文件下载的方式,性能良好且内存占用少:

@property (nonatomic, strong) NSOutputStream *outputStream;

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    self.filePath = [[self documentsDirectory] stringByAppendingPathComponent:response.suggestedFilename];
    self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.filePath append:YES];
    [self.outputStream open];
    self.totalLength = response.expectedContentLength;
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.outputStream write:data.bytes maxLength:data.length];
    self.currentLength += data.length;
    self.progressView.progress = (CGFloat)self.currentLength / self.totalLength;
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [self.outputStream close];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"Download failed with error: %@", error);
}

三、文件压缩/解压缩(使用第三方框架ZipArchive)

使用 ZipArchive 框架可以快速实现文件的压缩和解压缩:

压缩文件

NSArray *files = @[@"path/to/file1", @"path/to/file2"];
NSString *zipPath = @"path/to/archive.zip";
[SSZipArchive createZipFileAtPath:zipPath withFilesAtPaths:files];

解压缩文件

NSString *zipPath = @"path/to/archive.zip";
NSString *destinationPath = @"path/to/destination";
[SSZipArchive unzipFileAtPath:zipPath toDestination:destinationPath];

四、MIMEType(获取文件类型)

获取文件的MIMEType有两种方法:使用 NSURLConnection 和 C语言API。

使用 NSURLConnection

- (NSString *)MIMETypeForURL:(NSURL *)url {
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLResponse *response = nil;
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
    return response.MIMEType;
}

使用 C语言API

+ (NSString *)mimeTypeForFileAtPath:(NSString *)path {
    if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
        return nil;
    }

    CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)[path pathExtension], NULL);
    CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType);
    CFRelease(UTI);
    if (!MIMEType) {
        return @"application/octet-stream";
    }
    return (__bridge_transfer NSString *)MIMEType;
}

五、文件上传

文件上传需要构建特定的HTTP请求头和请求体。

设置请求头

[request setValue:@"multipart/form-data; boundary=Boundary" forHTTPHeaderField:@"Content-Type"];

构建请求体

NSMutableData *body = [NSMutableData data];
NSString *boundary = @"Boundary";

// 文件参数
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"file\"; filename=\"%@\"\r\n", fileName] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", mimeType] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:fileData];
[body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];

// 非文件参数
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Disposition: form-data; name=\"param\"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"paramValue" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];

// 结束标记
[body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

request.HTTPBody = body;

发送请求

[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    if (data) {
        // 上传成功处理
    } else {
        // 错误处理
        NSLog(@"Upload failed with error: %@", connectionError);
    }
}];

六、断点下载(续传)

断点下载的关键是通过自定义HTTP请求的头部的 Range 属性来实现:

NSURL *url = [NSURL URLWithString:@"http://example.com/path/to/file.mp4"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:@"bytes=20000-" forHTTPHeaderField:@"Range"];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];

// 将数据写入文件
[self writeToFile:data filePath:@"path/to/local/file.mp4"];

写入数据到文件

- (void)writeToFile:(NSData *)data filePath:(NSString *)filePath {
    if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
        [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
    }
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
    [fileHandle seekToEndOfFile];
    [fileHandle writeData:data];
    [fileHandle closeFile];
}
posted @ 2015-08-23 09:47  Mr.陳  阅读(1050)  评论(0编辑  收藏  举报