Cocoa Touch(五):网络请求 NSURLSession/AFNetworking, GCD, NSURLResquest

 

NSURLRequesthtml

    网络请求的关键的就是NSURLRequest类,它的实例表示了请求报文实体以及请求的缓存策略等等,各类网络框架的最终目标都是把这个对象编译成为请求报文发送出去。下面用一个实例来讲明它的用法。程序员

//一、设置url和请求方法
NSString *urlString = [NSString stringWithFormat:@"http://maplecode.applinzi.com"]; 
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; 
[request setURL:[NSURL URLWithString:urlString]]; 
[request setHTTPMethod:@"POST"]; 

//二、设置报文头
NSString *contentType = [NSString stringWithFormat:@"text/xml"]; 
[request addValue:contentType forHTTPHeaderField: @"Content-Type"]; 

//三、设置报文体
NSMutableData *postBody = [NSMutableData data]; 
[postBody appendData:[[NSString stringWithFormat:@"id=%@&password=%@",@"admin02",@"admin02"] dataUsingEncoding:NSUTF8StringEncoding]]; 
[postBody appendData:[[NSString stringWithFormat:@"<Request  Action=\"Login\">"] dataUsingEncoding:NSUTF8StringEncoding]]; 
[request setHTTPBody:postBody]; 


//四、发送请求报文并处理响应报文
NSHTTPURLResponse* urlResponse = nil;     
NSError *error = [[NSError alloc] init]; 
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&error]; 
NSString *result = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];

注意到咱们每每使用mutable的对象,方便修改。此外NSURLConnection类已经被建议废弃,如今可使用NSURLSession建立task。web

 

AFNetworkingjson

一、基于NSOperation发送网络请求设计模式

    本方法只适用于2.x版本的AFNetworking,新版本再也不支持基于NSURLConnection的API。多线程每每用于实现异步网络请求,配合封装了通讯接口的NSURLSession, CFNetwork, AFNetworking使用。下面重点介绍AFNetworking。api

这个库集XML解析,Json解析,plist解析,数据流上传,下载,缓存等众多功能于一身,配合操做队列的用法以下:缓存

NSString *str=[NSString stringWithFormat:@"https://alpha-api.app.net/stream/0/posts/stream/global"];
NSURL *url = [NSURL URLWithString:[str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSString *html = operation.responseString;
        NSData* data=[html dataUsingEncoding:NSUTF8StringEncoding];
id dict=[NSJSONSerialization  JSONObjectWithData:data options:0 error:nil];
        NSLog(@"获取到的数据为:%@",dict);
    }failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"发生错误!%@",error);
    }];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];

    继承关系:AFHTTPRequestOperation : AFURLConnectionOperation : NSOperation,也就是说AFHTTPRequestOperation类和NSInvocationOperation的使用方法是一致的,把它加入操做队列就能够了。网络

二、新版AFNetworking基于NSURLSessionsession

    若是须要细致的控制请求报文,那么对于低版本的AFNetworking,可使用AFHTTPClient类,它的实例表示了一个客户端,能够发出GET/POST请求。不过对于新版来讲,这个类已经不存在了,能够用AFHTTPSessionManager来代替发送GET/POST请求,并且基于NSURLSession,还能够很方便的实现全局配置和文件上传下载。多线程

@interface AFHTTPSessionManager
- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                      success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                      failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
- (NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(id)parameters
                       success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                       failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
@end

能够看出,用这个类来发送请求,甚至不须要事先生成表明请求报文的NSURLRequest对象,简化了操做过程,也不须要基于NSOperation,可是须要基于新的类NSURLSessionTask,好比AFNetworking 3.x下:

NSURL *URL = [NSURL URLWithString:@"http://example.com/resources/123.json"];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:URL.absoluteString parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
    NSLog(@"JSON: %@", responseObject);
} failure:^(NSURLSessionTask *operation, NSError *error) {
    NSLog(@"Error: %@", error);
}];

当下载任务结束后,怎么样在回调block中使用task实例和responseObject呢,咱们只须要看一看一个task的建立和使用过程:

NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:londonWeatherUrl]
      completionHandler:^(NSData *data,
                          NSURLResponse *response,
                          NSError *error) {
        // handle response
}] resume];

一个task须要持有一个block的引用,做为数据传输结束的处理任务,而且能够调用resume方法启动下载任务。

 

原生NSURLSession

一、异步请求

    依赖于第三方库并不老是但愿的状况,好比做为一个有轻微强迫症的开发者,老是但愿工程尽量简单。NSURLSession类自己提供了很是清晰方便的接口,支持网络任务的resume, suspend, cancel, invalidate等等,支持文件直接上传下载,若是能够直接对NSURLSession进行简单封装的处理,就不肯意去依赖AFNetworking。注意头文件中的语句:

@property (readonly, retain) NSOperationQueue *delegateQueue;

这说明NSURLSession类的实例也是经过操做队列完成网络操做,而且以retain方式拥有一个操做队列做为委托对象,所以程序员并不须要在代码中建立NSOperationQueue对象了。

    一个NSURLSession类的实例表示一系列会话的集合,程序员能够用一个NSURLSession的实例创造一个task对象来表示网络传输任务,正如上文中的代码片断能够创建一个异步网络请求,session能够维护这个task,而且session对象能够在网络传输结束后,把这个task的回调block放到delegateQueue中执行。

    NSURLSession和NSURLSessionDataTask的关系,正是工厂设计模式的一个体现。

 

二、同步请求

若是程序员要以同步方式完成网络操做,过去经过 NSURLConnection.sendSynchronousRequest() 方法能同步请求数据。从iOS9起,苹果建议废除 NSURLConnection,使用 NSURLSession 代替 NSURLConnection,那么应该怎么办呢?使用信号、信号量就能够实现

public static func requestSynchronousData(request: NSURLRequest) -> NSData? {
        var data: NSData? = nil
        let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0)
        let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {
            taskData, _, error -> () in
            data = taskData
            if data == nil, let error = error {print(error)}
            dispatch_semaphore_signal(semaphore);
        })
        task.resume()
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
        return data
}

或者用Objective-C的方式 

@implementation NSURLSession (Synchronous)

+ (NSData *)requestSynchronousDataWithRequest:(NSURLRequest *)request{
    __block NSData * data;
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    void (^completion)(NSData * , NSURLResponse * , NSError * ) = ^(NSData * taskData, NSURLResponse * response, NSError * error){
        data = taskData;
        dispatch_semaphore_signal(sem);
    };
    NSURLSessionDataTask * task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:completion];
    [task resume];
    dispatch_semaphore_wait(sem,DISPATCH_TIME_FOREVER);
    return data;
}
@end

 因为completion block中的内容不会被主线程执行,而是被其余线程执行,在更新UI时可能会出问题甚至crash,好比当实例化UIWebView的时候,会发出错误:

Tried to obtain the web lock from a thread other than the main thread or the web thread. 
This may be a result of calling to UIKit from a secondary thread. Crashing now...

用同步的方式就能够解决这个问题。或者就要使用GCD或者performSelector法某些代码放到主线程执行。

 

网络请求快捷方式

不少类能够直接获取JSON数据,图片数据等。在UI上,用SDWebImage模块中的分类实现了快捷方法,能够借助 [imageView setImageWithURL: placeholderImage:],能够直接请求须要显示的图片,而且有各类策略选项,减小冗长代码。

 

封装block

    在特定业务环境下,网络请求每每存在大量重复代码,时常须要封装一些以block做为参数的函数。因此最后写一点关于block的感悟:

若是一个函数以block做为参数,那么这个函数的最终目标就是要生成这个block的参数。

    这并不难于理解,由于block用于回调,咱们构造通用函数,天然就是要构造出正确的值做为参数,而后才能调用传进来的block。同时,一个以block为参数的函数的形式上的返回值每每是不重要的,一般是void。理解这一点有助于实现封装避免重复代码。

相关文章
相关标签/搜索