NSURLSession

一、NSURLSession概览

1 NSURLSession的类型

  • 默认会话(Default Sessions)使用了持久的磁盘缓存,并且将用户的证书存入钥匙串
  • 临时会话(Ephemeral Sessions)没有向磁盘中存入任何数据,所有相关内容都存在RAM中,当会话无效时,证书及缓存的数据都会被清除掉。
  • 后台会话(Background Sessions)除了使用了一个单独的线程来处理会话之外,与默认会话类似。但使用后台会话有一些限制,比如会话必须提供事件交付的代理方法、只有HTTP和HTTPS协议支持后台会话、总是伴随着重定向。仅仅在上传文件时才支持后台会话,当你上传二进制对象或者数据流时是不支持后台会话的。当App进入后台时,后台传输就会被初始化。(需要注意的是iOS8和OS X 10.10之前的版本中后台会话是不支持数据任务(data task)的)

会话的类型都要通过NSURLSessionConfiguration来指定
NSURLSessonConfiguration的创建

//默认会话
    NSURLSessionConfiguration* defaultSessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
//临时会话
    NSURLSessionConfiguration* ephemeralSessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
//后台会话
    NSURLSessionConfiguration* backgroundSessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"bacjgroundID"];

+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;

+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;

2 NSURLSession的各种任务

在一个Session会话中可以发起的任务分为三种:数据任务(Data task),下载任务(Download task),上传任务(Upload task)。

  • Data Task(数据任务)负责使用NSData对象来发送和接收数据。Data Task是为了那些简短的并且经常从服务器请求的数据而准备的。该任务可以没请求一次就对返回的数据进行一次处理。
  • Download task(下载任务)以表单的形式接收一个文件的数据,该任务支持后台下载。
  • Upload task(上传任务)以表单的形式上传一个文件的数据,该任务同样支持后台下载。

二.RUL编码

1 概述

1.URL编码概述

无论是GET、POST还是其他的请求,与服务器交互的URL是需要进行编码的。因为进行URL编码的参数服务器那边才能进行解析,为了能和服务器正常的交互,我们需要对我们的参数进行转义和编码。先简单的聊一下什么是URL吧,其实URL是URI(Uniform Resource Identifier ---- 统一资源定位符)的一种。URL就是互联网上资源的地址,用户可以通过URL来找到其想访问的资源。RFC3986文档规定,<font color = red>Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符,</font>如果你的URL中含有汉字,那么就需要对其进行转码了。<font color = red>RFC3986中指定了以下字符为保留字符:! * ' ( ) ; : @ & = + $ , / ? # [ ]。</font>

在URL编码时有一定的规则,在Get请求中Query是存放在URL后边,而在POST中是放在Request的Body中。如果你的参数只是一个<font color = red>key-Value</font>, 那么Query的形式就是key = value。如果你的参数是一个数组比如<font color = red>key = [itme1, item2, item3,……]</font>,那么你的Query的格式就是<font color = red>key[]=item1&key[itme2]&key[item3]……</font>。如果你的参数是一个字典比如<font color = red>key = ["subKey1":"item1", "subKey2":"item2"]</font>, 那么Query对应的形式就是<font color = red>key[subKey1]=item1&key[subKey2]=item2</font>.接下来我们要做的就是将字典进行URL编码。

2.编码

AFNetworking中对应的编码如下:


NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }

    return [mutablePairs componentsJoinedByString:@"&"];
}

NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}

NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

    if ([value isKindOfClass:[NSDictionary class]]) {
        NSDictionary *dictionary = value;
        // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
        for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            id nestedValue = dictionary[nestedKey];
            if (nestedValue) {
                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
            }
        }
    } else if ([value isKindOfClass:[NSArray class]]) {
        NSArray *array = value;
        for (id nestedValue in array) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
        }
    } else if ([value isKindOfClass:[NSSet class]]) {
        NSSet *set = value;
        for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
        }
    } else {
        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
    }

    return mutableQueryStringComponents;
}

三、数据任务--NSURLSesionDataTask

NSURLSessionDataTask的使用步骤

  • 首先我们先创建会话使用的URL,在创建URL是我们要对parameters字典参数进行URL编码。如果是GET方式的请求的话就使用?号将我们编码后的字符串拼接到URL后方即可。
  • 然后创建我们会话使用的请求(NSURLMutableRequest),在创建请求时我们要指定请求方式是POST还是GET。如果是POST方式,我们就将编码后的URL字符串放入request的HTTPBody中即可,有一点需要注意的是我们传输的数据都是二进制的,所以在将字符串存入HTTPBody之前要将其转换成二进制,在转换成二进制的同时我们使用的是UTF8这种编码格式。
  • 创建完Request后,我们就该创建URLSession了,此处我们为了简单就获取了全局的Session单例。我们使用这个Session单例创建了含有Request对象的一个DataTask。在这个DataTask创建时,有一个尾随闭包,这个尾随闭包用来接收服务器返回来的数据。当然此处可以指定代理,使用代理来接收和解析数据的,稍后会介绍到。
  • 最后切记创建好的Data Task是处于挂起状态的,需要你去唤醒它,所以我们要调用dataTask的resume方法进行唤醒。具体如下所示。
    NSString* baseUrlStr = [NSString stringWithUTF8String:"www.baidu.com"];
    
    NSURL* url = [NSURL URLWithString:baseUrlStr];
    
    NSMutableURLRequest* mutableRequest = [NSMutableURLRequest requestWithURL:url];
    
    //设置请求方式
    mutableRequest.HTTPMethod = @"GET";
    
    //参数编码
    
    //拼接参数
    //get 用 ?
    //post httpbody
    
    
    //创建data task
    NSURLSessionDataTask* dataTask = [self.session dataTaskWithRequest:mutableRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
    }];
    
    //唤起
    [dataTask resume];
    

四、上传任务 --- Upload Task

NSURLSessionUploadTask的使用步骤:

  • 先创建URL和request并为request指定请求方式为POST
  • 然后创建Session,此处我们使用SessionConfiguration为Session的类型指定为default session类型。并为该Session指定代理对象为self。
  • 最后使用Session来创建upload task,在创建upload task时为上传任务指定NSURLRequest对象,并且传入要上传的表单数据formData,当然不要忘了将任务进行唤醒。
    NSString* baseUrlStr = [NSString stringWithUTF8String:"www.baidu.com"];
    
    NSURL* url = [NSURL URLWithString:baseUrlStr];
    
    NSMutableURLRequest* mutableRequest = [NSMutableURLRequest requestWithURL:url];
    
    //设置请求方式
    mutableRequest.HTTPMethod = @"POST";
    //参数编码
    
    //拼接参数
    //post httpbody
    
    //上传文件的数据
    NSString* uploadStr = @"this is a upload str";
    NSData* uploadData = [uploadStr dataUsingEncoding:NSUTF8StringEncoding];
    
    NSURLSessionUploadTask* uploadTask = [self.session uploadTaskWithRequest:mutableRequest fromData:uploadData completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
    }];
    
    //唤醒
    [uploadTask resume];

备注:
在上传文件时,如果你想时刻的监听上传的进度,你可以去实现NSURLSessionTaskDelegate中的didSendBodyData方法,该方法会实时的监听文件上传的速度。

bytesSent回调参数表示本次上传的字节数,
totalBytesSend回调参数表示已经上传的数据大小,totalBytesExpectedToSend表示文件公有的大小。

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend

五、下载任务--Download Task

1.创建后台会话

    NSURLSessionConfiguration* backgroundSessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"bacjgroundID"];
    
    self.session = [NSURLSession sessionWithConfiguration:backgroundSessionConfiguration delegate:self delegateQueue:queue];

2.开始下载

    NSURLSessionConfiguration* backgroundSessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"bacjgroundID"];
    
    self.session = [NSURLSession sessionWithConfiguration:backgroundSessionConfiguration delegate:self delegateQueue:nil];
    
    
    if (self.resumeData) {
        //如果有已下载的数据
        self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            //Block下载方式不适合大文件下载
            
            //接收数据
            //确定文件路径
            //剪切文件
        }];
    }else{
        //没下载过
        NSString* baseUrlStr = [NSString stringWithUTF8String:"www.baidu.com"];
        
        NSURL* url = [NSURL URLWithString:baseUrlStr];
        
        NSMutableURLRequest* mutableRequest = [NSMutableURLRequest requestWithURL:url];
        
        //设置请求方式
        mutableRequest.HTTPMethod = @"POST";
        //参数编码
        
        //拼接参数
        //post httpbody
        
        //上传文件的数据
        NSString* uploadStr = @"this is a upload str";
        NSData* uploadData = [uploadStr dataUsingEncoding:NSUTF8StringEncoding];
        
        self.downloadTask = [self.session downloadTaskWithRequest:mutableRequest completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            //Block下载方式不适合大文件下载
            
            //接收数据
            //确定文件路径
            //剪切文件
            
        }];
    }
    [self.downloadTask resume];

推荐使用代理方法来处理下载

3.下载的暂停与恢复

//取消下载(不可以恢复)
- (void)cancleDownLoad
{
    [self.downloadTask cancel];
}
//可以恢复下载
- (void)cancleByResumeData
{
    [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        self.resumeData = resumeData;
    }];
}
//挂起下载
- (void)suspendDownLoad
{
    [self.downloadTask suspend];
}
//回复继续下载
- (void)resumeDownLoad
{
    [self.downloadTask resume];
}

4.下载任务的回调

NSURLSessionDownloadDelegate中的方法

/**
 下载完成

 @param session session对象
 @param downloadTask downloadTask对象
 @param location 临时文件的下载目录
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    //临时文件在下载完成之后如果你不做任何处理的话,那么就会被自动删除
    
    //创建文件存储路径
    //移动临时文件
}


/**
 下载文件偏移,主要用于断点续传

 @param session session对象
 @param downloadTask downloadTask对象
 @param fileOffset 已经下载文件的大小
 @param expectedTotalBytes 文件的总大小
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
    //更新下载进度条
}



/**
 实时监听下载任务回调

 @param session session对象
 @param downloadTask downloadTask对象
 @param bytesWritten 本次下载的数据
 @param totalBytesWritten 已经下载的数据总量
 @param totalBytesExpectedToWrite 文件的总量
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    //更新下载进度条
}

//后台下载完成之后的操作
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
    //配合application:handleEventsForBackgroundURLSession:completionHandler:方法使用
}

//当NSURLSession在后台开启几个任务之后, 如果有其他几个任务完成后系统会调用应用程序的
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
    
}

NSURLSessionTaskDelegate中的方法

//任务完成 不管是不是下载都会调用
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    
}

六、网络缓存

网络缓存在网络请求中使用的还是蛮多的,尤其是加载一些H5页面时经常会加一些缓存来提高用户体验。有时的一些数据也会进行缓存,你可将数据缓存到你的SQLite数据库、PList文件,或者直接使用NSURLSession相关的东西进行缓存。

1、缓存策略

在配置网络请求缓存时,有着不同的请求缓存策略。下方就是所有支持的网络缓存策略:

  • NSURLRequestUseProtocolCachePolicy -- 缓存存在就读缓存,若不存在就请求服务器
  • NSURLRequestReloadIgnoringLocalCacheData -- 忽略缓存,直接请求服务器数据
  • NSURLRequestReturnCacheDataElseLoad -- 本地如有缓存就使用,忽略其有效性,无则请求服务器
  • NSURLRequestReturnCacheDataDontLoad -- 直接加载本地缓存,没有也不请求网络
  • NSURLRequestReloadIgnoringLocalAndRemoteCacheData -- 尚未实现
  • NSURLRequestReloadRevalidatingCacheData -- 尚未实现

2、NSMutableURLRequest指定缓存策略

mutableRequest.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData

缓存目录默认为~/Library/Caches/[Boundle ID]/fsCachedData/缓存文件

3、NSURLSessionConfiguration指定缓存策略

    SessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;

4、NSURLCache + request进行缓存

    NSInteger memoryCapacity = 4 * 1024 * 1024;
    NSInteger diskCapacity = 10 * 1024 * 1024;
    NSString* cacheFilePath = @"myCache";
    
    NSURLCache* urlCache = [[NSURLCache alloc] initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:cacheFilePath];
    [NSURLCache setSharedURLCache:urlCache];

有一点需要注意的是此处设置的缓存路径是相对于/Library/Caches/[Boundle ID]/的

5、NSURLCache +SessionConfiguration进行缓存

    SessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
    NSInteger memoryCapacity = 4 * 1024 * 1024;
    NSInteger diskCapacity = 10 * 1024 * 1024;
    NSString* cacheFilePath = @"myCache";
    
    NSURLCache* urlCache = [[NSURLCache alloc] initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:cacheFilePath];
    SessionConfiguration.URLCache = urlCache;

6、清除缓存

NSFileManager removeItemAtPath

七、请求认证

1.认证方式

  • NSURLAuthenticationMethodHTTPBasic: HTTP基本认证,需要提供用户名和密码
  • NSURLAuthenticationMethodHTTPDigest: HTTP数字认证,与基本认证相似需要用户名和密码
  • NSURLAuthenticationMethodHTMLForm: HTML表单认证,需要提供用户名和密码
  • NSURLAuthenticationMethodNTLM: NTLM认证,NTLM(NT LAN Manager)是一系列旨向用户提供认证,完整性和机密性的微软安全协议
  • NSURLAuthenticationMethodNegotiate: 协商认证
  • NSURLAuthenticationMethodClientCertificate: 客户端认证,需要客户端提供认证所需的证书
  • NSURLAuthenticationMethodServerTrust: 服务端认证,由认证请求的保护空间提供信任

后两个就是我们在请求HTTPS时会遇到的认证,需要服务器或者客户端来提供认证的,这个证书就是我们平时常说的CA证书.

2.认证处理策略

当我们进行网络求时,会对相应的认证做出响应。在NSURLSession进行网络请求时支持四种证书处理策略,这些认证处理策略以枚举的形式来存储,枚举的类型为NSURLSessionAuthChallengeDisposition。下方就是认证的所有处理策略:

  • NSURLSessionAuthChallengeUseCredential 使用证书
  • NSURLSessionAuthChallengePerformDefaultHandling 执行默认处理, 类似于该代理没有被实现一样,credential参数会被忽略
  • NSURLSessionAuthChallengeCancelAuthenticationChallenge 取消请求,credential参数同样会被忽略
  • NSURLSessionAuthChallengeRejectProtectionSpace 拒绝保护空间,重试下一次认证,credential参数同样会被忽略

3.HTTPS请求证书处理

发起上述https请求后,就会执行下方的代理方法。下方的委托代理方法属于NSURLSessionDelegate中处理认证的方法,也就是如果服务器需要认证时就会执行下方的回调方法。下方代码首先从授权质疑的保护空间中取出认证方式,然后根据不同的认证方式进行不同的处理。下方给出了两种认证方式的处理,上面的if语句块赋值服务端认证,下面的if语句块负责HTTP的基本认证。具体处理方式如下所示。有一点需要注意的是如果在该委托回调方法中如果不执行completionHandler闭包,那么认证就会失效,是请求不到数据的。


/**
 服务器需要验证时会触发

 @param session session对象
 @param challenge 授权质疑
 @param completionHandler block
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {
    
    // 判断是否是信任服务器证书
    if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
        //https
        // 告诉服务器,客户端信任证书
        // 创建凭据对象
        NSURLCredential *credntial = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        // 通过completionHandler告诉服务器信任证书
        completionHandler(NSURLSessionAuthChallengeUseCredential,credntial);
    }
    /**
      * HTTP BASE
     */
    if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic) {
        NSURLCredential *credntial = [[NSURLCredential alloc] initWithUser:@"username" password:@"password" persistence:NSURLCredentialPersistenceForSession];
        completionHandler(NSURLSessionAuthChallengeUseCredential,credntial);
    }
    //取消请求
    completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,nil);
}

八、NSURLSession相关代理

1、NSURLSessionDelegate

  1. didReceiveChallenge
    其他
//Session无效后被调用
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error
{
    
}
//后台Session在执行完后台任务后所执行的方法
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
    
}

2、NSURLSessionTaskDelegate

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
        newRequest:(NSURLRequest *)request
 completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler;
{
    
}

3、NSURLSessionDataDelegate

NSURLSessionDataDelegate中的方法主要是用来处理Data Task任务相应的事件的

1、相应处理策略

  • NSURLSessionResponseCancel 取消数据的加载,默认为 Cancel。此处理方式就是忽略数据的加载,取消对响应数据的进一步解析
  • NSURLSessionResponseAllow 允许继续操作, 会执行 NSURLSessionDataDelegate中的dataTaskDidReceiveData回调方法
  • NSURLSessionResponseBecomeDownload 将Data Task的响应转变为DownloadTask,会执行NSURLSessionDownloadDelegate代理中相应的方法
  • NSURLSessionResponseBecomeStream 将Data Task的响应转变为DownloadTask,会执行NSURLSessionStreamDelegate代理中相应的方法

2.代理方法

/**
 收到响应时会执行的方法

 @param session session
 @param dataTask dataTask
 @param response 服务器响应
 @param completionHandler block
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
    //处理策略设置成Allow后会执行下方的方法,如果响应处理策略不是Allow那么就不会接收到服务器的Data
}

/**
 接收数据后会执行的方法
 
 @param session session
 @param dataTask dataTask
 @param data 接收的数据
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    
}

/**
 任务转变为下载时所执行的方法

 @param session session
 @param dataTask dataTask
 @param downloadTask downloadTask
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
    
}

/**
 任务转变为streamTask时所执行的方法

 @param session session
 @param dataTask dataTask
 @param streamTask streamTask
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask
{
    //iOS9
}


/**
 将要进行缓存时执行的方法

 @param session session
 @param dataTask dataTask
 @param proposedResponse streamTask
 @param completionHandler block
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler
{
    
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容