NSURLConnection文件下载和NSRunLoopCommonModes

传智播客视频学习笔记+个人总结
发送请求的步骤:
1.设置url
2.设置request
3.发送请求,同步or异步

使用同步方法下载文件

在主线程调用同步方法,一直在等待服务器返回数据,代码会卡住,如果服务器,没有返回数据,那么在主线程UI会卡住不能继续执行操作。有返回值NSData

//1.url
NSString *urlstr = @"xxxx";
urlstr = [urlstr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:urlstr];
//2.request
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.connection
NSLog(@"start");
NSData *data=[NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
//一直在等待服务器返回数据
NSLog(@"--%d--",data.length);
  • 注意:同步的连接会阻塞调用它的线程,不一定是主线程

使用异步方法下载文件

没有返回值

    //1.url
    NSString *urlstr = @"xxxx";
    urlstr = [urlstr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url = [NSURL URLWithString:urlstr];
    
    //2.request
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    //3.connection
    NSLog(@"start");
    //使用这个方法会有内存峰值
    //queue参数是指定block的执行队列
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        //将文件写入磁盘
        //内存峰值:下载完成瞬间再把文件写入磁盘。下载文件有多大,NSData就会占用多大的内存。
        [data writeToFile:@"XXXXXX" atomically:YES];
        NSLog(@"finsh");
    }];
  • 开一条新的线程去发送请求,主线程继续往下走,当拿到服务器的返回数据的数据的时候再回调block,执行block代码段。不会卡住主线程。
  • 关于queue参数,队列的作用:An NSOperationQueue upon which the handler block will be dispatched.决定这个block操作放在哪个线程执行。
    如果是NSOperationQueue *queue=[[NSOperationQueue alloc]init];默认是异步执行。(接下来也会有提及)
    如果是主队列mainqueue,那么在子线程发送请求成功并获取到服务器的数据响应。就可以回到主线程中解析数据,并刷新UI界面。
  • 如果有刷新UI界面的操作应该放在主线程执行,不能放在子线程。

存在的问题:
1.没有下载进度,会影响用户体验
2.使用异步方法,下载完成执行回调时再把文件写入磁盘,会造成内存峰值的问题。下载文件有多大,NSData就会占用多大的内存

使用代理

问题1的解决办法:使用代理NSURLConnectionDataDelegate
1.在响应方法中获得文件总大小
2.每次接收到数据,计算数据的总长度,和总大小相比,得出百分比

//要下载文件的总长度
@property (nonatomic , assign)long long expectedContentLength;
//当前下载的长度
@property (nonatomic , assign)long long currentLength;

    //1.url
    NSString *urlstr = @"xxxx";
    urlstr = [urlstr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url = [NSURL URLWithString:urlstr];
    
    //2.request
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    //3.connection,不做设置的话是在主线程中执行之后的下载
    NSLog(@"start");
    NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
    
    //设置代理工作的操作队列
    [conn setDelegateQueue:[[NSOperationQueue alloc]init]];
    
    //4.启动连接
    [conn start];
//代理
//1.接收到服务器的响应 :状态行和响应头,用来做一些准备工作
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    NSLog(@"%@",response);//response里面有状态行和响应头
    //记录文件总大小
    self.expectedContentLength = response.expectedContentLength;
    self.currentLength = 0;
}

//2.接收到服务器的数据 :此方法可能会执行多次,因为会接收到多个data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    NSLog(@"接收到的数据长度%tu",data.length);
    //计算百分比
    self.currentLength += data.length;
    
    float progress = (float)self.currentLength / self.expectedContentLength;
    NSLog(@"%f",progress);    
}

//3.所有数据加载完成 : 所有数据传输完毕之后被调用,是最后一个的通知
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
}

//4.下载失败或者错误
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
}

  • 要下载的文件总大小在服务器返回的响应头里面可以拿到
NSDictionary *headerDic = response.allHeaderFields; 
self.fileLength = [[headerDic objectForKey:@"Content-Length"] intValue];

或者

response.expectedContentLength 要下载的文件总大小
response.suggestedFilename 获取下载的文件名
  • +(NSURLConnection*) connectionWithRequest:delegate:
    During the download the connection maintains a strong reference to the delegate. It releases that strong reference when the connection finishes loading, fails, or is canceled. connection对代理方法强引用

问题2的解决办法:
保存文件的思路:
a.拼接完成写入磁盘
b.下载一个写一个
1>NSFileHandle
2>NSOutputStream

** a.拼接完成写入磁盘**
1.生成目标文件路径
2.在didReceiveData里拼接数据
3.拼接完成写入磁盘(在完成下载的方法里)
4.释放内存

//文件保存的路径
@property (nonatomic , strong) NSString *targetFilePath;
//用来每次接收到数据,拼接数据使用
@property (nonatomic , strong) NSMutableData *fileData;
//代理
//1.接收到服务器的响应 :状态行和响应头,用来做一些准备工作
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    NSLog(@"%@",response);
    
    //放到沙盒
    //NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    //self.targetFilePath = [cache stringByAppendingPathComponent:response.suggestedFilename];
    //生成目标文件路径    
    self.targetFilePath = [@"/Users/apple/xxxxxx"stringByAppendingPathComponent:response.suggestedFilename];//下载后的文件名字不变
    //删除文件,如果文件存在,就会直接删除,如果文件不存在就什么也不做
    [[NSFileManager defaultManager]removeItemAtPath:self.targetFilePath error:NULL];
}

- (NSMutableData *)fileData{
    if (_fileData == nil) {
        _fileData = [[NSMutableData alloc]init];
    }
    return _fileData;
}

//2.接收到服务器的数据 :次方法可能会执行多次,因为会接收到多个data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    NSLog(@"接收到的数据长度%tu",data.length);
    
    //拼接数据
//     a.拼接完成写入磁盘
    [self.fileData appendData:data];
}

//3.所有数据加载完成 : 所有数据传输完毕之后被调用,是最后一个的通知
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
     //a.拼接完成写入磁盘
    [self.fileData writeToFile:self.targetFilePath atomically:YES];
    //释放内存
    self.fileData = nil;//不释放的话,内存一直被占用,文件多大被占用的就有多大
}
  • 把下载好的文件放到沙盒:
NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *file = [cache stringByAppendingPathComponent:response.suggestedFilename];```
response.suggestedFilename:建议保存的文件名
- 文件写入磁盘后要释放data:不释放的话,内存一直被占用,文件多大被占用的就有多大

存在的问题:测试结果:也是存在内存峰值

** b.下载一个写一个**
1>NSFileHandle

//2.接收到服务器的数据 :次方法可能会执行多次,因为会接收到多个data

  • (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

    //拼接数据
    // b.下载一个写一个
    [self writeToFileWithData:data];
    }

  • (void)writeToFileWithData:(NSData )data{
    //文件操作
    /

    NSFileManager:主要功能,创建目录,检查目录是否存在,遍历目录,删除文件...针对文件的操作,类似于Finder
    NSFileHandle:文件“句柄(文件指针)”,如果在开发中,看到Handle这个单词,就意味着是对前面的单词“File”进行操作的对象
    主要功能,就是对同一个文件进行二进制的读写操作的对象。
    */

    //如果文件不存在,fp在实例化的结果是空
    NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:self.targetFilePath];
    //判断文件是否存在
    //如果不存在,直接将数据存入磁盘
    if(fp == nil){
    [data writeToFile:self.targetFilePath atomically:YES];
    }//如果存在,将Data追缴到现有文件
    else{
    //1.将文件指针移到文件的末尾
    [fp seekToEndOfFile];
    //2.写入文件
    [fp writeData:data];
    //3.关闭文件。在c语言开发中,凡是涉及到文件读写,打开和关闭通常是成对出现的
    [fp closeFile];
    }
    }

上面的写法多次打开、关闭文件,下面进行改进:
  • (void)connection:(NSURLConnection )connection didReceiveResponse:(NSURLResponse )response
    {
    NSString
    ceches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    NSString
    filepath = [ceches stringByAppendingPathComponent:response.suggestedFilename];

    // 创建一个空的文件到沙盒中
    NSFileManager* mgr = [NSFileManager defaultManager];
    [mgr createFileAtPath:filepath contents:nil attributes:nil];
    // 创建一个用来写数据的文件句柄对象
    self.writeHandle = [NSFileHandle fileHandleForWritingAtPath:filepath];
    }

  • (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
    [self.writeHandle seekToEndOfFile];
    [self.writeHandle writeData:data];
    }

  • (void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
    [self.writeHandle closeFile];
    self.writeHandle = nil;
    }

测试结果:彻底解决了内存峰值的问题。是传统的文件操作方式。

2>NSOutputStream输出流
1.创建流
2.打开流
3.将数据追加到流
4.关闭流

//保存文件的输出流
/*

  • (void)open;写入之前打开流
  • (void)close;完成之后关闭流
    */
    @property (nonatomic , strong) NSOutputStream *fileStream;

//代理
//1.接收到服务器的响应 :状态行和响应头,用来做一些准备工作

  • (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    NSLog(@"%@",response);

    //生成目标文件路径
    self.targetFilePath = [@"/Users/apple/xxxxxx"stringByAppendingPathComponent:response.suggestedFilename];
    //删除文件,如果文件存在,就会直接删除,如果文件不存在没就什么也不做
    [[NSFileManager defaultManager]removeItemAtPath:self.targetFilePath error:NULL];

    self.fileStream = [[NSOutputStream alloc]initToFileAtPath:self.targetFilePath append:YES];
    [self.fileStream open];
    }

//2.接收到服务器的数据 :次方法可能会执行多次,因为会接收到多个data

  • (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

    //拼接数据
    //将数据追加到文件流中
    [self.fileStream write:data.bytes maxLength:data.length];
    }

//3.所有数据加载完成 : 所有数据传输完毕之后被调用,是最后一个的通知

  • (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    //关闭文件流
    [self.fileStream close];
    }

###断点续传
要实现断点续传要利用HTTP的range请求头。bytes = 500-999表示从500-999的500个字节。续传的Demo:[断点续传](http://www.cnblogs.com/GeekStar/p/4409714.html)
demo里面的例子只适合于应用运行期间续传。比如,一旦应用在下载期间中途退出,再次运行时,下载将会重新开始

// 设置请求头数据
NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentLen];
[request setValue:range forHTTPHeaderField:@"Range"];

多线程断点续传:http://www.cnblogs.com/wendingding/p/3947550.html

###总结
- 小文件下载:可以使用sendAsynchronousRequest:queue:completionHandler 这个方法一次性返回整个下载到的文件,返回的data在内存中,如果下载一个几百兆的东西,这样会造成内存峰值。( [NSData dataWithContentsOfURL:url]这个方法也是一样)
- 大文件下载:使用代理


####NSURLConnection+NSRunLoop
新问题:下载默认下在主线程工作。下载本身是不是异步的(NSURLConnection实例运行在主线程)
 ```[conn setDelegateQueue:[[NSOperationQueue alloc]init]];```
指定了代理的工作队列之后,整个下载仍然会受主线程的干扰,以及更新ui(进度条)不及时。[在storyboard上添加了一个进度条以及一个uitextview,下载的时候,进度条会卡顿,滑动textview下载会暂停,停止滑动后又继续下载]
 
``` NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];```在这里,delegate参数在api里面的说明如下:
 The delegate object for the connection. The connection calls methods on this delegate as the load progresses. Delegate methods are called on the same thread that called this method. For the connection to work correctly, the calling thread’s run loop must be operating in the default run loop mode.代理方法被调用connectionWithRequest:delegate:这个方法的同一个线程调用,为了保证连接的工作正常,调用线程的runloop必须运行在默认的运行循环模式下。

[一个异步网络请求的坑:关于NSURLConnection和NSRunLoopCommonModes](http://www.hrchen.com/2013/06/nsurlconnection-with-nsrunloopcommonmodes/)这篇博文有提到这个问题:
如果是直接调用initWithRequest:delegate:startImmediately:(第三个参数用YES)或者方法initWithRequest:delegate:时(调用完connection就直接运行了),NSURLConnection会默认运行在**NSDefaultRunLoopMode**模式下,即使再使用scheduleInRunLoop:forMode:设置运行模式也没有用。如果NSURLConnection是运行在NSDefaultRunLoopMode,而当前线程是**主线程**(NSURLConnection实例运行在主线程,主线程有一个运行的runloop实例来支持NSURLConnection的异步执行),并且UI上有类似滚动这样的操作,那么主线程的Run Loop会运行在**UITrackingRunLoopMode**下,就无法响应NSURLConnnection的回调。此时需要首先使用initWithRequest:delegate:startImmediately:(第三个参数为NO)生成NSURLConnection,再重新设置NSURLConnection的运行模式为**NSRunLoopCommonModes**,那么UI操作和回调的执行都将是非阻塞的,因为NSRunLoopCommonModes是一组run loop mode的集合,默认情况下包含了NSDefaultRunLoopMode和UITrackingRunLoopMode。
  • (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode在api中的说明
    将connection实例回调加入到一个runloop,NSURLConnectionDelegate回调会在这个runloop中响应
    Determines the run loop and mode that the connection uses to call methods on its delegate.
    By default, a connection is scheduled on the current thread in the default mode when it is created. If you create a connection with the initWithRequest:delegate:startImmediately: method and provide NO for the startImmediately parameter, you can schedule the connection on a different run loop or mode before starting it with the start method. You can schedule a connection on multiple run loops and modes, or on the same run loop in multiple modes.
    You cannot reschedule a connection after it has started.It is an error to schedule delegate method calls with both this method and the setDelegateQueue: method.
    方法参数:
    aRunLoop:The NSRunLoop instance to use when calling delegate methods。
    mode:The mode in which to call delegate methods.
    这个方法不能和setDelegateQueue方法一起使用

总结一下个人理解,默认情况下,代理方法被调用connectionWithRequest:delegate:这个方法的同一个线程调用,NSURLConnection默认运行在NSDefaultRunLoopMode模式下;但是使用scheduleInRunLoop: forMode:可以设置代理方法运行在哪个runloop(相当于是设置运行线程?)和mode下

//运行在主线程下
NSMutableURLRequest* request = [[NSMutableURLRequest alloc]
initWithURL:self.URL
cachePolicy:NSURLCacheStorageNotAllowed
timeoutInterval:self.timeoutInterval];
self.connection =[[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:NO];
//设置回调代理方法运行在那个runloop和mode,这里设置在当前runloop也就是主线程里面执行代理方法
[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.connection start];


**1.为了不影响ui线程,把下载工作放到子线程里面**

dispatch_async(dispatch_get_global_queue(0, 0), ^{//这样写,下载不执行了。[conn start];之后子线程被回收释放内存空间
//1.url
NSString *urlstr = @"xxxx";
urlstr = [urlstr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:urlstr];

    //2.request
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    //3.connection,不做设置的话是在主线程中执行之后的下载 
    NSLog(@"start,%@",[NSThread currentThread]);
    NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
    
    //设置代理工作的操作队列
    [conn setDelegateQueue:[[NSOperationQueue alloc]init]];
    
    //4.启动连接
    [conn start];

});

把网络操作方法放到子线程执行以后,回调代理方法无法执行,无法下载。
原因:主线程runloop会自动启动,子线程runloop默认不启动。将网络操作放在异步执行,异步的runloop不启动,没有办法监听到网络事件。[conn start];之后子线程被回收释放内存空间。

[一个异步网络请求的坑:关于NSURLConnection和NSRunLoopCommonModes](http://www.hrchen.com/2013/06/nsurlconnection-with-nsrunloopcommonmodes/)这篇博文有提到这个问题:如果是用GCD在其他线程中启动NSURLConnection:不会得到NSURLConnection回调,而从主线程中启动NSURLConnection可以得到回调,这是由于在GCD全局队列中执行时没有运行Run Loop,那么NSURLConnection也就无法触发回调了。
解决的办法如下:

**2.启动子线程runloop**
a.[[NSRunLoop currentRunLoop] run];使用这种方法使用,runloop永远释放不掉
b.开启一个循环,判断下载是否完成。这种方法对系统消耗非常大

@property (nonatomic , assign , getter = isFinished) BOOL finished;

//主线程runloop会自动启动,子线程runloop默认不启动
//将网络操作放在异步执行,异步的runloop不启动,没有办法监听到网络事件
dispatch_async(dispatch_get_global_queue(0, 0), ^{//这样写,下载不执行了。[conn start];之后子线程被回收释放内存空间
    ......
    
    //4.启动连接
    [conn start];
    
    self.finished = NO;
    //5.启动运行循环

// a. [[NSRunLoop currentRunLoop] run];//这样写永远释放不掉
// b.
while (!self.isFinished) {
//启动一个死循环,每次监听0.1秒.
//对系统消耗非常大
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
});

  • (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    ......
    self.finished = YES;
    }

c.方法b的改进,例子里面使用到CFRunLoop

@property (nonatomic , assign) CFRunLoopRef rl;

//主线程runloop会自动启动,子线程runloop默认不启动
//将网络操作放在异步执行,异步的runloop不启动,没有办法监听到网络事件
dispatch_async(dispatch_get_global_queue(0, 0), ^{//这样写,下载不执行了。[conn start];之后子线程被回收释放内存空间
    
    //1.url
    NSString *urlstr = @"xxxx";
    urlstr = [urlstr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url = [NSURL URLWithString:urlstr];
    
    //2.request
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    //3.connection,不做设置的话是在主线程中执行之后的下载
    //开始时的线程是由dispatch_async 创建的
    NSLog(@"start,%@",[NSThread currentThread]);
    NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
    
    //设置代理工作的操作队列
    [conn setDelegateQueue:[[NSOperationQueue alloc]init]];
    //指定调度代理工作的操作队列。操作队列的特点:添加任务,立即异步执行,具体的线程程序员不能决定
    
    //4.启动连接
    [conn start];
    
    //5。启动运行循环
    //CF框架

// CFRunLoopStop(CFRunLoopRef rl);停止指定的runloop
// CFRunLoopGetCurrent();当前线程的runloop
// CFRunLoopRun();直接运行当前线程的runloop
//1.拿到当前的runloop
self.rl = CFRunLoopGetCurrent();
//2.启动运行循环
CFRunLoopRun();
});

  • (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    //结束时代理工作的线程,是由指定的NSOperationQueue创建的,和创建下载操作的线程是不一样的。
    //关闭runloop,要关闭指定线程上的runloop。在这里拿到创建下载那个线程的runloop
    NSLog(@"finish,%@",[NSThread currentThread]);
    //关闭文件流
    ......
    CFRunLoopStop(self.rl);
    }
![Runloop的启动和关闭示意图.png](http://upload-images.jianshu.io/upload_images/1727123-40091d2fffa8ac38.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 开始时的下载线程是由dispatch_async 创建的,```NSLog(@"start,%@",[NSThread currentThread]);```
 结束时**代理方法**工作的线程,是由指定的NSOperationQueue创建的:```[conn setDelegateQueue:[[NSOperationQueue alloc]init]];```,和创建下载操作的线程是不一样的。```NSLog(@"finish,%@",[NSThread currentThread]);```这里打印的两个线程结果是不一样的。
- ```[conn setDelegateQueue:[[NSOperationQueue alloc]init]];```指定调度**代理方法**工作的操作队列。操作队列的特点:添加任务,立即异步执行,具体的线程程序员不能决定.
- 启动runloop:1.拿到当前的runloop ```self.rl = CFRunLoopGetCurrent();```2.启动runloop``` CFRunLoopRun();```
 关闭runloop:要关闭指定线程上的runloop。在这里拿到创建下载那个线程的runloop ```CFRunLoopStop(self.rl);```

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

推荐阅读更多精彩内容

  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,118评论 29 470
  • 父类实现深拷贝时,子类如何实现深度拷贝。父类没有实现深拷贝时,子类如何实现深度拷贝。• 深拷贝同浅拷贝的区别:浅拷...
    JonesCxy阅读 989评论 1 7
  • 1,NSObject中description属性的意义,它可以重写吗?答案:每当 NSLog(@"")函数中出现 ...
    eightzg阅读 4,127评论 2 19
  • 1.请简单说明多线程技术的优点和缺点? 优点能适当提高程序的执行效率能适当提高资源的利用率(CPU/内存利用率) ...
    彼岸的黑色曼陀罗阅读 449评论 0 2
  • 该文章属于<简书 — Timhbw>原创,转载请注明: <简书社区 — Timhbw>http://www.jia...
    伯虔阅读 17,089评论 3 158