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阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容