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。理解這一點有助於實現封裝避免重複代碼。

相關文章
相關標籤/搜索