iOS基礎之網絡請求

目錄

       1. AFN框架
       2. NSURLSession
       3. NSURLConnection 
       4. 其他
Cocoa 中網絡編程層次結構分為三層,自上而下分別是:

    Cocoa 層:NSURL,Bonjour,Game Kit,WebKit
    Core Foundation 層:基于 C 的 CFNetwork 和 CFNetServices
    OS 層:基于 C 的 BSD socket

1. AFN框架(第三方庫)常用

    // 1.創建網絡請求manager
    AFHTTPSessionManager *manger=[AFHTTPSessionManager manager];
    // 1.1 設置請求的數據類型
    // 設置 request類型為二進制類型(默認)
    [manger setRequestSerializer:[AFHTTPRequestSerializer serializer]];
    // 設置 請求超時時間
    [manger.requestSerializer setTimeoutInterval:6.f];
    // 1.2 設置 response
    // 設置 response類型為二進制類型(默認:JSON類型,已經解析)
    [manger setResponseSerializer:[AFHTTPResponseSerializer serializer]];
    // 設置 允許接收的數據類型
    [manger.responseSerializer setAcceptableContentTypes:[NSSet setWithObjects:@"application/json",@"text/json", @"text/javascript",@"text/html", nil]];
    
    // 2.發送請求
    // url
    NSString *urlStr=@"url";
    // 參數(可以是數組/字典/nil)
    NSDictionary *paraDic=@{@"userId":@""};
    [manger POST:urlStr parameters:paraDic progress:^(NSProgress * _Nonnull uploadProgress) {
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        // 主線程:可以直接更新UI
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"%@發生錯誤: \n%@",urlStr,error);
    }];


'
     請求格式
        AFHTTPRequestSerializer            二進制格式        (默認)
        AFJSONRequestSerializer            JSON
        AFPropertyListRequestSerializer    PList(是一種特殊的XML,解析起來相對容易)
     返回格式
        AFHTTPResponseSerializer           二進制格式  (不作任何處理:NSData,當返回的數據不是JSON/XML/plist/image要設置,如:HTML、Text)
        AFJSONResponseSerializer           JSON            (默認)
        AFXMLParserResponseSerializer      XML,只能返回XMLParser,還需要自己通過代理方法解析
        AFXMLDocumentResponseSerializer (Mac OS X)
        AFPropertyListResponseSerializer   PList
        AFImageResponseSerializer          Image
        AFCompoundResponseSerializer       組合
'
上傳
    // url
    NSString *urlStr=@"url";
    // 參數(可以是數組/字典/nil)
    NSDictionary *paraDic=@{@"userId":@""};
    [manger POST:urlStr parameters:paraDic constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
        // img->data
        NSData *imgData=UIImagePNGRepresentation([UIImage imageNamed:@""]);
        
        // 設置需要上傳的文件(需要上傳的文件,后臺規定的參數名,文件名,后臺規定的文件類型)
        [formData appendPartWithFileData:imgData name:@"headImage" fileName:@"hello.png" mimeType:@"image/png"];
    } progress:^(NSProgress * _Nonnull uploadProgress) {
        //
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        // 上傳成功
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"%@發生錯誤: \n%@",urlStr,error);
    }];

2. NSURLSession (原生網絡請求類---目前用)

    // 1.創建請求
    NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:@"urlStr"]];
    // 2.創建會話
    NSURLSession *session=[NSURLSession sharedSession];
    // 3.創建任務
    NSURLSessionDataTask *task=[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if(error==nil){
            NSDictionary *dcit=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
            // 刷新UI在主線程中
        }
    }];
    // 3.1啟動任務
    [task resume];
繼承關系:
  NSObject
      NSURLSessionTask
          NSURLSessionDataTask         NSURLSessionDownloadTask
            NSURLSessionUploadTask

說明:
    NSURLSessionUploadTask          上傳專用Task(不接收數據)
    NSURLSessionDownloadTask        下載專用Task
    NSURLSessionDataTask            上傳數據,并接收返回數據
0.創建NSURLRequest (3方式)
方式一
    NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:@""]];

方式二    
    NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:@""] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:2];

方式三
    NSMutableURLRequest *muRequest=[NSMutableURLRequest requestWithURL:[NSURL URLWithString:@""]];
    // 設置請求超時時間
    [muRequest setTimeoutInterval:10];  
    // 默認GET,設置請求方式
    [muRequest setHTTPMethod:@"POST"]; 
    // 設置請求體
    [muRequest setHTTPBody:[@"key=value&key2=value2" dataUsingEncoding:NSUTF8StringEncoding]];  
    // 設置請求頭
    [muRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    // [muRequest addValue:@"" forHTTPHeaderField:@"Content-Length"];  



1. 創建NSURLSession(3方式)
方式一    全局Session(有局限)
    NSURLSession *session=[NSURLSession sharedSession];

方式二    SessionConfiguration
    NSURLSessionConfiguration *connfi=[NSURLSessionConfiguration defaultSessionConfiguration];
    [connfi setTimeoutIntervalForRequest:5];        // 設置請求超時
    NSURLSession *session=[NSURLSession sessionWithConfiguration:connfi];
    NSURLSession *session=[NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    后臺Session
    NSURLSessionConfiguration *config=[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@""];
    NSURLSession *session=[NSURLSession sessionWithConfiguration:connfi];



2. 創建NSURLSessionConfiguration (3方式)
  方式一
    // 存儲Cache在硬盤(默認模式,保存用戶的證書到鑰匙串,使用共享cookie存儲)
    NSURLSessionConfiguration *config=[NSURLSessionConfiguration defaultSessionConfiguration];

  方式二
    // 存儲Cache在內存(用于無痕瀏覽,會話結束后清空數據)
    NSURLSessionConfiguration *config=[NSURLSessionConfiguration ephemeralSessionConfiguration];

  方式三    
    // 將上傳下載移到后臺
    NSURLSessionConfiguration *config=[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@""];
3. 創建NSURLSessionDataTask (4方式)
方式一
    NSURLSessionDataTask *dataTask=[session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]]];
方式二
    NSURLSessionDataTask *dataTask=[session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if(!error){
        }
    }];
方式三
    NSURLSessionDataTask *dataTask=[session dataTaskWithURL:[NSURL URLWithString:@""]];
方式四
    NSURLSessionDataTask *dataTask=[session dataTaskWithURL:[NSURL URLWithString:@""] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if(!error){
        }
    }];
    [dataTask resume];  // 任務開始
    [dataTask suspend]; // 任務暫停
    [dataTask cancel];  // 任務取消



4. 創建NSURLSessionUploadTask(5方式)     上傳data
方式一
    [session uploadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] fromData:data];
方式二
    [session uploadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] fromData:data completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if(!error){
        }
    }];
方式三
    [session uploadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] fromFile:[[NSURL alloc]initFileURLWithPath:[NSString stringWithFormat:@"%@/Documents/1.txt",NSHomeDirectory()]]];
方式四
    [session uploadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] fromFile:[[NSURL alloc]initFileURLWithPath:[NSString stringWithFormat:@"%@/Documents/1.txt",NSHomeDirectory()]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if(!error){
        }
    }];
方式五
[session uploadTaskWithStreamedRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]]];



2. 創建NSURLSessionDownloadTask(6方式)下載data
方式一
    NSURLSessionDownloadTask *dataTask=[session downloadTaskWithURL:[NSURL URLWithString:@""]];
方式二
    NSURLSessionDownloadTask *dataTask=[session downloadTaskWithURL:[NSURL URLWithString:@""] completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if(!error){
        }
    }];
方式三
    NSURLSessionDownloadTask *dataTask=[session downloadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]]];
方式四
    NSURLSessionDownloadTask *dataTask=[session downloadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if(!error){
        }
    }];
方式五
    NSURLSessionDownloadTask *dataTask=[session downloadTaskWithResumeData:data];
方式六
    NSURLSessionDownloadTask *dataTask=[session downloadTaskWithResumeData:data completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if(!error){
        }
    }];

3. NSURLConnection(原生網絡請求類---已過時)

方式一:使用dele(異步)
<NSURLConnectionDelegate,NSURLConnectionDataDelegate>
@property (nonatomic,strong) NSMutableData *contentData;

    // 1.創建請求,建立連接
    NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:@"urlStr"]];
    NSURLConnection *conn=[NSURLConnection connectionWithRequest:request delegate:self];
    [conn start];

#pragma mark dele
// 2.收到響應時調用
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    _contentData.length=0;
}
// 3.收到數據時調用
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    [_contentData appendData:data];
}
// 4.數據接收完畢后調用
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{}
// 4.1連接出錯時調用
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{}
方式二:sendAsynchronousRequest(異步)
    [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"urlStr"]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
    }];
方式三:sendSynchronousRequest(同步)
    NSData *contentData=[NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"urlStr"]] returningResponse:&response error:nil];
上面提到的 NSURLConnection 的異步方法實際上還是跑在主線程當中,在主線程中執行網絡操作會帶來兩個問題:

    盡管在網絡連接過程中不會對主線程造成阻塞,但是 delegate 的回調方法還是在主線程中執行的。如果我們在回調方法中(特別是 completion 回調)中進行了大量的耗時操作,仍然會造成主線程的阻塞。
    NSURLConnection 默認會跑在當前的 runloop 中,并且跑在 Default Mode,當用戶執行滾動的 UI 操作時會發生 runloop mode 的切換,也就導致了 NSURLConnection 不能及時執行和完成回調。
簡單地把start函數放到后臺的 queue 中是不行的。因為 dispatch_async 開出的線程中,默認 runloop 沒有執行,因此線程會立即結束,來不及調用回調方法。

dispatch_async(connectionQueue, ^{
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
        [request setURL:[NSURL URLWithString:[NSString stringWithFormat:someURL]]];

        NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; // 沒有設置 startImmediately 為 NO,會立即開始
        //[connection start]; 這一句沒有必要寫,寫了也一樣不能 work。
});
這樣又帶來一個問題,這個線程中 runloop 會一直跑著,導致這個線程也一直不結束

dispatch_async(connectionQueue, ^{
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
        [request setURL:[NSURL URLWithString:[NSString stringWithFormat:someURL]]];

        NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
        [[NSRunLoop currentRunLoop] run];
});
dispatch_async(connectionQueue, ^{
  NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
  [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; // 添加 inputSource,讓 runloop 保持 alive
  [self.connection scheduleInRunLoop:runLoop
                             forMode:NSDefaultRunLoopMode];   
  [self.connection start];
  [runLoop run];
});

- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    CFRunLoopStop(CFRunLoopGetCurrent());
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
    CFRunLoopStop(CFRunLoopGetCurrent());
}
方法二(使用NSOperationQueue)

dispatch_async(connectionQueue, ^{
  NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:aURLRequest
                                                              delegate:self
                                                      startImmediately:NO];
  [connection setDelegateQueue:[[NSOperationQueue alloc] init]];
  [connection start];
});

4. 其他

注意:

  1.App如果需要進行網絡操作,則要在info.plist文件中添加權限:

        <key>NSAppTransportSecurity</key>
        <dict>
            <key>NSAllowsArbitraryLoads</key>
            <true/>
        </dict>
菊花
    // 是否打開菊花(狀態欄上:用來提示用戶正在請求網絡)
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:true];
data<->str
    // data->str
    NSString *str=[[NSString alloc]initWithData:[NSData new] encoding:NSUTF8StringEncoding];
    // str->data
    NSData *data=[@"" dataUsingEncoding:NSUTF8StringEncoding];
中文
    對中文編碼(url中有中文)
    string.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLFragmentAllowedCharacterSet())! 
    對中文解碼       
    paraStr.stringByRemovingPercentEncoding
Charles(抓包)
    1. 下載charles
    2. 連上同一Wifi,手機設置wifi為手動:服務器(mac終端:ifconfig en0 找到地址) 端口:8888(默認:charles的Proxy中設置的)
    3. OK

XML(過時, 現只用于存儲,不用于傳輸)

用來存儲和傳遞數據(優點:可讀性強,缺點:太冗余)
    文檔由節點(開始標簽和結束標簽組成)構成

DOM解析:
  一次性將整個XML文檔加載進內存,比較適合解析小文件,例如:GDataXml解析
SAX解析:
  從根元素開始,按順序一個元素一個元素往下解析,比較適合解析大文件,例如:NSXMLParser解析

例:
<?xml version="1.0" encoding="UTF-8">
<books>                             根節點
    <book id="0">                   id是屬性節點
        <name>book1</name>          name是元素節點
        <price>12</price>
    </book>
</books>



使用(2方式):
—————————方式一
第一步:
    1. 導入Data文件,建立橋接文件  :   #import "GDataXMLNode.h"
    2. Build Phase  |  Link Binary With Libraries  添加libxml2.tbd
    3. Build Phase  |  Compile Source   .m文件后雙擊 +  -fno-objc-arc
    4. BuildSettings | header Search Path  +  /usr/include/libxml2
第二步:
        let path=NSBundle.mainBundle().pathForResource("xml", ofType: "txt")
        let data=NSData.init(contentsOfFile: path!)
        let doc=try! GDataXMLDocument.init(data: data, options: 0)      // 解析數據
        
        let rootE=doc.rootElement()     // 獲取根節點
        print(rootE.XMLString())        // 打印節點
        
        // 獲取節點的內容(根據節點名)
        let booksArr=rootE.elementsForName("books") as! [GDataXMLElement]       // 獲取元素節點(返回數組)
        let booksEl=booksArr[0]
        
        let bookArr=booksEl.elementsForName("book") as! [GDataXMLElement]
        for bookEl in bookArr{
            let name=(bookEl.elementsForName("name")[0]) as! GDataXMLElement    // 
            let attName=bookEl.attributes()[0] as! GDataXMLNode                 // 獲取屬性節點(返回數組)
            print(name.stringValue())                                           // 節點值      name.name()節點名
        }
—————————方式二
    XPath 使用路徑表達式獲取節點
    /   從根節點獲取
    //  查詢和名稱相同的節點(不考慮位置)
    .   獲取當前節點
    ..  獲取當前節點的父節點
    @   獲取屬性

        // 位置
        let pathT="/root/books/book/name"
        // 查詢所有 符合pathT位置 的節點
        let nameArr=try! doc.nodesForXPath(pathT)
        print(nameArr[0].stringValue())

擴展:
            根名             獲取 根名 元素的所有子節點
            /根名            獲取 根名 元素的所有子節點
            /根名/元素名      獲取 和路徑匹配的所有元素節點
            //元素名                獲取 元素名相同的所有元素節點
            根名/元素名//元素名      獲取 元素名相同的所有元素節點(在根名/元素名 下)
            //@屬性名        獲取 屬性名相同 的所有元素節點


            /根名/元素名[1]  獲取第一個滿足path的元素節點
            [last()]         最后一個
            [position()<3]   前2個
            [屬性名>3]/元素名

            //title[@length]        或取所有 屬性名length的元素名title 的元素
            //title[@length='e']    或取所有 屬性名length且值為e的元素名title 的元素

            *      匹配任何元素節點。
            @*     匹配任何屬性節點。
            node()  匹配任何類型的節點。
            |      多個路徑

監測網絡狀態 (需引入AFN框架)

1. cocoaPods
    pod 'AFNetworking'
2.AppDelegate+
#import <AFNetworking/AFNetworking.h>

// 監聽網絡狀態
-(void)mangeNet{
    // 1.獲取網絡管理者
    AFNetworkReachabilityManager *netManger=[AFNetworkReachabilityManager sharedManager];
    // 2.網絡狀態發生變化后調用
    [netManger setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
        switch (status) {
            case AFNetworkReachabilityStatusUnknown:{
                // 檢測到網絡狀態前為此狀態
                NSLog(@"網絡未知");
            }
                break;
            case AFNetworkReachabilityStatusNotReachable:{
                NSLog(@"連接不到網絡");
                // 提示用戶,跳到系統設置頁設置網絡
            }
                break;
            case AFNetworkReachabilityStatusReachableViaWWAN:{
                NSLog(@"流量");
            }
                break;
            case AFNetworkReachabilityStatusReachableViaWiFi:{
                NSLog(@"wifi");
            }
                break;
        }
    }];
    // 3.監測網絡變化
    [netManger startMonitoring];
}
    // 獲取當前網絡狀態
    AFNetworkReachabilityStatus status=netManger.networkReachabilityStatus;

    // 跳轉到設置---蜂窩網絡
    [[UIApplication sharedApplication]openURL:[NSURL URLWithString:@"App-Prefs:root=MOBILE_DATA_SETTINGS_ID"]];

@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying>


// 獲取相對路徑(readonly)
/**
相對路徑的使用
    NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];
    [NSURL URLWithString:@"foo" relativeToURL:baseURL];                  // http://example.com/v1/foo
    [NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL];          // http://example.com/v1/foo?bar=baz
    [NSURL URLWithString:@"/foo" relativeToURL:baseURL];                 // http://example.com/foo
    [NSURL URLWithString:@"foo/" relativeToURL:baseURL];                 // http://example.com/v1/foo
    [NSURL URLWithString:@"/foo/" relativeToURL:baseURL];                // http://example.com/foo/
    [NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/
*/
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;

/**
以什么格式序列化請求體,默認:AFHTTPRequestSerializer
requestSerializer不能為nil
 */
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;

/**
以什么格式序列化響應體,默認:AFJSONResponseSerializer

responseSerializer不能為nil
 */
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;

///-------------------------------
/// @name Managing Security Policy
///-------------------------------

/**
 The security policy used by created session to evaluate server trust for secure connections. `AFURLSessionManager` uses the `defaultPolicy` unless otherwise specified. A security policy configured with `AFSSLPinningModePublicKey` or `AFSSLPinningModeCertificate` can only be applied on a session manager initialized with a secure base URL (i.e. https). Applying a security policy with pinning enabled on an insecure session manager throws an `Invalid Security Policy` exception.
 */
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;

///---------------------
/// @name Initialization
///---------------------

// 獲取AFHTTPSessionManager對象
+ (instancetype)manager;

/**
 Initializes an `AFHTTPSessionManager` object with the specified base URL.

 @param url The base URL for the HTTP client.

 @return The newly-initialized HTTP client
 */
- (instancetype)initWithBaseURL:(nullable NSURL *)url;

/**
 Initializes an `AFHTTPSessionManager` object with the specified base URL.

 This is the designated initializer.

 @param url The base URL for the HTTP client.
 @param configuration The configuration used to create the managed session.

 @return The newly-initialized HTTP client
 */
- (instancetype)initWithBaseURL:(nullable NSURL *)url
           sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;

///---------------------------
/// @name Making HTTP Requests
///---------------------------

/**
創建并運行一個NSURLSessionDataTask的GET方式任務請求
  內部調用dataTaskWithHTTPMethod方法

URLString:請求字符串
parameters:參數字典(根據requestSerializer序列化)
headers:追加至默認的請求頭
downloadProgress:下載過程中的回調,不在主線程隊列中執行。
success:請求成功并獲得響應時調用
failure:出錯時的回調(請求出錯、解析出錯)
 */
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
                            parameters:(nullable id)parameters
                               headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                              progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
                               success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                               failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

/**
 Creates and runs an `NSURLSessionDataTask` with a `HEAD` request.
 
 @param URLString The URL string used to create the request URL.
 @param parameters The parameters to be encoded according to the client request serializer.
 @param headers The headers appended to the default headers for this request.
 @param success A block object to be executed when the task finishes successfully. This block has no return value and takes a single arguments: the data task.
 @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
 
 @see -dataTaskWithRequest:completionHandler:
 */
- (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString
                             parameters:(nullable id)parameters
                                headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                                success:(nullable void (^)(NSURLSessionDataTask *task))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

/**
創建并運行一個NSURLSessionDataTask的POST方式任務請求
  內部調用dataTaskWithHTTPMethod方法

URLString:請求字符串
parameters:參數字典(根據requestSerializer序列化)
headers:追加至默認的請求頭
uploadProgress:上傳過程中的回調,不在主線程隊列中執行。
success:請求成功并獲得響應時調用
failure:出錯時的回調(請求出錯、解析出錯)
 */
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
                                headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

/**
創建并運行一個NSURLSessionDataTask的POST方式任務請求(用于上傳圖片等)
  內部調用uploadTaskWithStreamedRequest方法
 
URLString:請求字符串
parameters:參數字典(根據requestSerializer序列化)
headers:追加至默認的請求頭
block:追加data至請求體
uploadProgress:上傳過程中的回調,不在主線程隊列中執行。
success:請求成功并獲得響應時調用
failure:出錯時的回調(請求出錯、解析出錯)
 */
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
                                headers:(nullable NSDictionary <NSString *, NSString *> *)headers
              constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

/**
 Creates and runs an `NSURLSessionDataTask` with a `PUT` request.
 
 @param URLString The URL string used to create the request URL.
 @param parameters The parameters to be encoded according to the client request serializer.
 @param headers The headers appended to the default headers for this request.
 @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
 @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
 
 @see -dataTaskWithRequest:completionHandler:
 */
- (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString
                            parameters:(nullable id)parameters
                               headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                               success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                               failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

/**
 Creates and runs an `NSURLSessionDataTask` with a `PATCH` request.
 
 @param URLString The URL string used to create the request URL.
 @param parameters The parameters to be encoded according to the client request serializer.
 @param headers The headers appended to the default headers for this request.
 @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
 @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
 
 @see -dataTaskWithRequest:completionHandler:
 */
- (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString
                              parameters:(nullable id)parameters
                                 headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                                 success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                 failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

/**
 Creates and runs an `NSURLSessionDataTask` with a `DELETE` request.
 
 @param URLString The URL string used to create the request URL.
 @param parameters The parameters to be encoded according to the client request serializer.
 @param headers The headers appended to the default headers for this request.
 @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
 @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
 
 @see -dataTaskWithRequest:completionHandler:
 */
- (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString
                               parameters:(nullable id)parameters
                                  headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                                  success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                  failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

/**
創建并運行一個NSURLSessionDataTask任務請求
  內部調用dataTaskWithHTTPMethod方法

method:POST、GET
URLString:請求字符串
parameters:參數字典(根據requestSerializer序列化)
headers:追加至默認的請求頭
uploadProgress:上傳過程中的回調,不在主線程隊列中執行。
downloadProgress:下載過程中的回調,不在主線程隊列中執行。
success:請求成功并獲得響應時調用
failure:出錯時的回調(請求出錯、解析出錯)
 */
- (nullable NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                                URLString:(NSString *)URLString
                                               parameters:(nullable id)parameters
                                                  headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                                           uploadProgress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                         downloadProgress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
                                                  success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                                  failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

@end
//
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
                                headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure
{
    // 創建NSURLSessionDataTask并執行
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters headers:headers uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure];
    [dataTask resume];
    
    return dataTask;
}

// 
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(nullable id)parameters
                                         headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                         failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure
{
    // 創建NSMutableURLRequest請求
    NSError *serializationError = nil;
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    // 設置head請求頭
    for (NSString *headerField in headers.keyEnumerator) {
        [request setValue:headers[headerField] forHTTPHeaderField:headerField];
    }
    // 出錯
    if (serializationError) {
        if (failure) {
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
        }

        return nil;
    }

    // 創建NSURLSessionDataTask任務,并返回
    __block NSURLSessionDataTask *dataTask = nil;
    dataTask = [self dataTaskWithRequest:request
                          uploadProgress:uploadProgress
                        downloadProgress:downloadProgress
                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) { // failure回調
                failure(dataTask, error);
            }
        } else {
            if (success) {  // succes回調
                success(dataTask, responseObject);
            }
        }
    }];

    return dataTask;
}

//
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {
    // 創建NSURLSessionDataTask任務
    NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];

    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}

//
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
                uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
              downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
             completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;
    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    [self setDelegate:delegate forTask:dataTask];
}

//
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

- (void)addNotificationObserverForTask:(NSURLSessionTask *)task {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];
}
- (void)taskDidResume:(NSNotification *)notification {
    NSURLSessionTask *task = notification.object;
    if ([task respondsToSelector:@selector(taskDescription)]) {
        if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidResumeNotification object:task];
            });
        }
    }
}
- (void)taskDidSuspend:(NSNotification *)notification {
    NSURLSessionTask *task = notification.object;
    if ([task respondsToSelector:@selector(taskDescription)]) {
        if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidSuspendNotification object:task];
            });
        }
    }
}
AFURLSessionManagerTaskDelegate


@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
- (instancetype)initWithTask:(NSURLSessionTask *)task;
@property (nonatomic, weak) AFURLSessionManager *manager;
@property (nonatomic, strong) NSMutableData *mutableData;
@property (nonatomic, strong) NSProgress *uploadProgress;
@property (nonatomic, strong) NSProgress *downloadProgress;
@property (nonatomic, copy) NSURL *downloadFileURL;
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
@property (nonatomic, strong) NSURLSessionTaskMetrics *sessionTaskMetrics AF_API_AVAILABLE(ios(10), macosx(10.12), watchos(3), tvos(10));
#endif
@property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
@property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock;
@property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock;
@property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler;
@end

@implementation AFURLSessionManagerTaskDelegate

- (instancetype)initWithTask:(NSURLSessionTask *)task {
    self = [super init];
    if (!self) {
        return nil;
    }
    
    _mutableData = [NSMutableData data];
    _uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
    _downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
    
    __weak __typeof__(task) weakTask = task;
    for (NSProgress *progress in @[ _uploadProgress, _downloadProgress ])
    {
        progress.totalUnitCount = NSURLSessionTransferSizeUnknown;
        progress.cancellable = YES;
        progress.cancellationHandler = ^{
            [weakTask cancel];
        };
        progress.pausable = YES;
        progress.pausingHandler = ^{
            [weakTask suspend];
        };
#if AF_CAN_USE_AT_AVAILABLE
        if (@available(macOS 10.11, *))
#else
        if ([progress respondsToSelector:@selector(setResumingHandler:)])
#endif
        {
            progress.resumingHandler = ^{
                [weakTask resume];
            };
        }
        
        [progress addObserver:self
                   forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                      options:NSKeyValueObservingOptionNew
                      context:NULL];
    }
    return self;
}

- (void)dealloc {
    [self.downloadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
    [self.uploadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
}

#pragma mark - NSProgress Tracking

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
   if ([object isEqual:self.downloadProgress]) {
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
    else if ([object isEqual:self.uploadProgress]) {
        if (self.uploadProgressBlock) {
            self.uploadProgressBlock(object);
        }
    }
}

static const void * const AuthenticationChallengeErrorKey = &AuthenticationChallengeErrorKey;

#pragma mark - NSURLSessionTaskDelegate

- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    error = objc_getAssociatedObject(task, AuthenticationChallengeErrorKey) ?: error;
    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;

    NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

    //Performance Improvement from #2672
    NSData *data = nil;
    if (self.mutableData) {
        data = [self.mutableData copy];
        //We no longer need the reference, so nil it out to gain back some memory.
        self.mutableData = nil;
    }

#if AF_CAN_USE_AT_AVAILABLE && AF_CAN_INCLUDE_SESSION_TASK_METRICS
    if (@available(iOS 10, macOS 10.12, watchOS 3, tvOS 10, *)) {
        if (self.sessionTaskMetrics) {
            userInfo[AFNetworkingTaskDidCompleteSessionTaskMetrics] = self.sessionTaskMetrics;
        }
    }
#endif

    if (self.downloadFileURL) {
        userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
    } else if (data) {
        userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
    }

    if (error) {
        userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;

        dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
            if (self.completionHandler) {
                self.completionHandler(task.response, responseObject, error);
            }

            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
            });
        });
    } else {
        dispatch_async(url_session_manager_processing_queue(), ^{
            NSError *serializationError = nil;
            responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

            if (self.downloadFileURL) {
                responseObject = self.downloadFileURL;
            }

            if (responseObject) {
                userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
            }

            if (serializationError) {
                userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
            }

            dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
                if (self.completionHandler) {
                    self.completionHandler(task.response, responseObject, serializationError);
                }

                dispatch_async(dispatch_get_main_queue(), ^{
                    [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
                });
            });
        });
    }
}

#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics AF_API_AVAILABLE(ios(10), macosx(10.12), watchos(3), tvos(10)) {
    self.sessionTaskMetrics = metrics;
}
#endif

#pragma mark - NSURLSessionDataDelegate

- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive;
    self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived;

    [self.mutableData appendData:data];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
    
    self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
    self.uploadProgress.completedUnitCount = task.countOfBytesSent;
}

#pragma mark - NSURLSessionDownloadDelegate

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
    
    self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite;
    self.downloadProgress.completedUnitCount = totalBytesWritten;
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes{
    
    self.downloadProgress.totalUnitCount = expectedTotalBytes;
    self.downloadProgress.completedUnitCount = fileOffset;
}

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    self.downloadFileURL = nil;

    if (self.downloadTaskDidFinishDownloading) {
        self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (self.downloadFileURL) {
            NSError *fileManagerError = nil;

            if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
            } else {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidMoveFileSuccessfullyNotification object:downloadTask userInfo:nil];
            }
        }
    }
}

@end
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內容