AFNetworking 2.x 阅读笔记(二)

上一章讲解了AFNetworking 2.x的基本用法,主要从AFHTTPRequestOperationManager调用POST/GET方法开始通过requestSerilizer请求序列化,然后创建AFHTTPRequestOperation对象,使用operation对象进行网络请求,最后在设置operation结束后的completionBlock,内部包括responseSerilizer响应序列化。整个过程都是围绕operation对象的,可以说整个AFNetworking 2.x 网络库的核心就是AFHTTPRequestOperation&AFURLRequestOperation。本文就来梳理这个核心部分内容。

AFURLReuqestOperation&AFHTTPRequestOperation主要的工作分为两部分:

  • 继承NSOPeration的常见方法:设置completionBlock, operation状态管理,start方法等等
  • 实现NSURLConnetion的几个代理方法

1 completionBlock调用failure&success

在NSOperation中,completionBlock会在finished属性被设置成YES以后调用,并且调用的线程不一定是主线程,因此如果有耗时操作需要自己放到子线程中去完成。AFNetworking中关于completionBlock就是这样处理的,将completionBlock中的参数block方法加入到自定义的completionGroup中,然后将耗时操作success&failure放到自定义的completionQueue中。

- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                              failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    // completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
//会触发父类的setCompletionBlock方法 -- 需要先看父类的方法 -- 见下面
    self.completionBlock = ^{
//completionGroup会在父类的setCompletionBlock方法里面初始化,使用的公用的自定义的group
        if (self.completionGroup) {
            dispatch_group_enter(self.completionGroup);
        }
//创建processing_queue,然后在这个queue里面异步调用
        dispatch_async(http_request_operation_processing_queue(), ^{
            if (self.error) {//如果有_responseSerializationError 或者其他的error
                if (failure) {
//异步在completion group queue中调用 failure block
                    dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                        failure(self, self.error);
                    });
                }
            } else {
//直接在responseObject中调用(*)responseSerializer进行响应的解析
                id responseObject = self.responseObject;
                if (self.error) {//判断解析过程中是否有error发生
                    if (failure) {//如果有error发生,同上
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            failure(self, self.error);
                        });
                    }
                } else {//说明response 解析没有出错,到达真正的success
                    if (success) {//在completionGroup和queue中调用success block
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            success(self, responseObject);
                        });
                    }
                }
            }
//注意:上面的if else语句中最终只会有一个failure 或者 success加入都 dispatch_group中
            if (self.completionGroup) {
                dispatch_group_leave(self.completionGroup);
            }
        });
    };
#pragma clang diagnostic pop
}

//这是父类AFURLConnectionOperation重写的NSOperation的completionBlock的setter方法,这个
//过程是加锁的
- (void)setCompletionBlock:(void (^)(void))block {
    [self.lock lock];
//如果传入的block为nil,直接将completionBlock设置为nil
    if (!block) {
        [super setCompletionBlock:nil];
    } else {
//使用weak strong引用的技巧,使用weakSelf是防止block应用operation对象,operation对象强引
//用这个completionBlock,在block执行过程中,将weakSelf 转化为强引用,是防止在block执行过程
//中,self对象被释放,因此这里会有循环引用,然后在最后group内的所有的block都执行完成以后,
//在自定义的queue中将completionBlock设置成nil,主动破解循环引用。
        __weak __typeof(self)weakSelf = self;
//如果block不为nil,就给设置completionblock,等operation执行完成以后执行以下的代码
        [super setCompletionBlock:^ {
            __strong __typeof(weakSelf)strongSelf = weakSelf;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
//使用了?:运算符,如果当前completionGroup自定义了会使用自定义的group,否则会用onceToken
//创建一个dispatch_group,这个 group 是所有的operation共用的,后面所有的operation的completionBlock设置为nil以及block的运行也与这个group有关!!!
            dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
//completionQueue同理,如果自定义了就用自定义的否则会使用mainqueue
            dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop
//异步使用group queue 调用传入的block,这里所有的operation对象都会在同一个group中去异步执行block
            dispatch_group_async(group, queue, ^{
//ps:在block中执行结束后只会有一个failure 或者 success block会被加入到group中异步执行
                block();
            });
//在传入的completionBlcok中使用了dispatch_group_enter &dispatch_group_leave方法,其中所有加入到queue的方法都会受到这个group的影响,这里等待所有进入group的任务都执行完了,再在自定义的queue中将NSOperation的completionBlock手动设置为nil,这里是在一个全局共用的自定义的queue中执行的。
            dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
                [strongSelf setCompletionBlock:nil];
            });
        }];
    }
    [self.lock unlock];
}

上面的代码中,有一处值得注意的地方是 id responseObject = self.responseObject,会触发getter方法:

- (id)responseObject {
    [self.lock lock];
    if (!_responseObject && [self isFinished] && !self.error) {
        NSError *error = nil;
// 调用responseSerializer方法解析response
        self.responseObject = [self.responseSerializer responseObjectForResponse:self.response data:self.responseData error:&error];
        if (error) {
            self.responseSerializationError = error;
        }
    }
    [self.lock unlock];

    return _responseObject;
}

以上设置completionBlock代码中,值得学习的技巧:
1 在block中使用weakSelf,在block内容开始执行时候转化位strong类型,防止运行过程中释放,在block调用结束时,手动释放block,解除循环引用

__weak __typeof(self)weakSelf = self;
[super setCompletionBlock:^ {
__strong __typeof(weakSelf)strongSelf = weakSelf;

2 使用dispatch_once保证只会调用一次,多线程中常用的技巧,并且对于仅仅调用一次的方式使用static method来声明

static dispatch_group_t http_request_operation_completion_group() {
    static dispatch_group_t af_http_request_operation_completion_group;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        af_http_request_operation_completion_group = dispatch_group_create();
    });

    return af_http_request_operation_completion_group;
}

3 自定义thread,让线程一直在后台运行的方法
①_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; 创建新线程
②在startPoint里面加入autoreleasepool。因为自定义线程里面没有主线程的autoreleasepool,需要手动添加。
③设置自定义线程的名称,方便跟踪调试
④给runloop添加一个MachPort信息,然后run runloop,使得线程保持在后台运行

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}

注意前面提到的:completionBlock被执行的时机:completionBlock会在finished属性被设置成YES以后调用


2 自定义operation的状态管理

NSOperation->AFURLRequestOpertion ->AFHTTPRequestOperation是继承关系。犹豫NSOPerationQueue会通过KVO观察NSOperation的状态来判断operation是否finish&excuting&cancel等等,因此在子类创建自己的operation类需要自己用代码管理operation类的状态:isReady,isExecuting,isFinished,并且在状态改变时发送KVO。AFNetworking在AFURLReqeustOperation中进行operation的状态管理,同时增加了一个pause状态.

关于NSOperation state的文章可以参考 NSHipster

- (BOOL)isReady {
    return self.state == AFOperationReadyState && [super isReady];
}
- (BOOL)isExecuting {
    return self.state == AFOperationExecutingState;
}
- (BOOL)isFinished {
    return self.state == AFOperationFinishedState;
}
- (BOOL)isPaused {
    return self.state == AFOperationPausedState;
}

可以看出是通过枚举用于记录operation当前状态定义的状态机,然后使用@property (readwrite, nonatomic, assign) AFOperationState state属性记录当前状态,通过属性的setter进行状态转移的控制。

typedef NS_ENUM(NSInteger, AFOperationState) {
    AFOperationPausedState      = -1,
    AFOperationReadyState       = 1,
    AFOperationExecutingState   = 2,
    AFOperationFinishedState    = 3,
};

- (void)setState:(AFOperationState)state {
//首先判断能否从当前状态转移到newState
    if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) {
        return;
    }

    [self.lock lock];
//为后面KVO做准备,设置key
    NSString *oldStateKey = AFKeyPathFromOperationState(self.state);
    NSString *newStateKey = AFKeyPathFromOperationState(state);

//注意:在operation实现中对于状态变化需要使用KVO通知给NSOperationQueue:finish,excuting,ready等等
    [self willChangeValueForKey:newStateKey];
    [self willChangeValueForKey:oldStateKey];
    _state = state;
    [self didChangeValueForKey:oldStateKey];
    [self didChangeValueForKey:newStateKey];
    [self.lock unlock];
}
//通过static方法来判断是否能: 状态A -> 状态B   返回值表示YES&NO
static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperationState toState, BOOL isCancelled) {
    switch (fromState) {
        case AFOperationReadyState:
            switch (toState) {
                case AFOperationPausedState:
                case AFOperationExecutingState:
                    return YES;
                case AFOperationFinishedState:
                    return isCancelled;
                default:
                    return NO;
            }
        case AFOperationExecutingState:
            switch (toState) {
                case AFOperationPausedState:
                case AFOperationFinishedState:
                    return YES;
                default:
                    return NO;
            }
        case AFOperationFinishedState:
            return NO;
        case AFOperationPausedState:
            return toState == AFOperationReadyState;
        default: {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
            switch (toState) {
                case AFOperationPausedState:
                case AFOperationReadyState:
                case AFOperationExecutingState:
                case AFOperationFinishedState:
                    return YES;
                default:
                    return NO;
            }
        }
#pragma clang diagnostic pop
    }
}
// 用于将状态属性->NSString 的方法
static inline NSString * AFKeyPathFromOperationState(AFOperationState state) {
    switch (state) {
        case AFOperationReadyState:
            return @"isReady";
        case AFOperationExecutingState:
            return @"isExecuting";
        case AFOperationFinishedState:
            return @"isFinished";
        case AFOperationPausedState:
            return @"isPaused";
        default: {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
            return @"state";
#pragma clang diagnostic pop
        }
    }
}

对于新增加的paused状态,使用以下方法进行管理:

- (void)resume {
    if (![self isPaused]) {//必须是paused状态才能resume
        return;
    }

    [self.lock lock];
    self.state = AFOperationReadyState;

    [self start];//手动调用start方法
    [self.lock unlock];
}
- (void)pause {
    if ([self isPaused] || [self isFinished] || [self isCancelled]) {//必须excuting或者ready状态才能pause
        return;
    }

    [self.lock lock];
    if ([self isExecuting]) {
        [self performSelector:@selector(operationDidPause) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];

        dispatch_async(dispatch_get_main_queue(), ^{
            NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
            [notificationCenter postNotificationName:AFNetworkingOperationDidFinishNotification object:self];
        });
    }

    self.state = AFOperationPausedState;
    [self.lock unlock];
}

状态管理总结:
1 创建枚举方法表示NSOperation的状态,至少包括excuting, finished状态。
2 新建static方法AFStateTransitionIsValid判断能否从:状态A->状态B,在此过程中,需要注意isCancelled状态
3 在state属性的setter方法setState中进行 状态改变,其中改变前后需要进行KVO需要通知相关状态的变化。


3 Operation主要的工作

首先看下自定义Operation的核心方法start

- (void)start {
   [self.lock lock];
//在实现NSOperation的方法时,要经常判断operation是否cancel
   if ([self isCancelled]) {
//在自定义的Thread中调用cancelConnection方法
       [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
   } else if ([self isReady]) {
//设置state为excuting状态
       self.state = AFOperationExecutingState;
//在自定义的线程中调用operationDidStart方法
       [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
   }
   [self.lock unlock];
}

//使用once创建一个线程
+ (NSThread *)networkRequestThread {
   static NSThread *_networkRequestThread = nil;
   static dispatch_once_t oncePredicate;
   dispatch_once(&oncePredicate, ^{
//创建一个线程以后就直接start
       _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
       [_networkRequestThread start];
   });

   return _networkRequestThread;
}

//自定义线程的enterPoint
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
   @autoreleasepool {
       [[NSThread currentThread] setName:@"AFNetworking"];//名称,方便调试

//自定义线程没有启动runloop,如果没有runloop,在线程运行完enterPoint方法以后,线程就会退出,这里希望线程长期存在,因此需要在runloop中加入MachPort,防止线程退出
       NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
       [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
       [runLoop run];
   }
}

- (void)operationDidStart {
   [self.lock lock];
   if (![self isCancelled]) {
//在对象的connection属性创建NSURLConnection对象,delegate是operation对象
       self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];

       NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
       for (NSString *runLoopMode in self.runLoopModes) {
//将当前operation中的connetion的delegate方法放到子线程中去执行
           [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
           [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
       }

//启动connetion&outputStream
       [self.outputStream open];
       [self.connection start];
   }
   [self.lock unlock];
//全局消息AFNetworkingOperationDidStartNotification
   dispatch_async(dispatch_get_main_queue(), ^{
       [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self];
   });
}

NSURLConnecion在子线程运行过程中常见错误,delegate方法不会被调用——线程被销毁,因此有了自定义Thread&runloop
官方文档中关于NSURLConnetion的方法注释如下:

- scheduleInRunLoop:forMode:
Determines the run loop and mode that the connection uses to call methods on its delegate.

以上问题和runloop有关,可以参考: 孙源runloop视频深入理解RunLoop


4 Operation实现的NSURLConnetion的几个Delegate方法

当调用了start方法以后,就在自定义的子线程Thread中等待NSURLConnetion的delegate回调。
AFURLReuqestOperation实现了NSURLConnectionDelegate,NSURLConnectionDataDelegate方法

//一般用于HTTPS请求中的SSL/TSL认证,证书用的自签名证书
- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if (self.authenticationChallenge) {
        self.authenticationChallenge(connection, challenge);
        return;
    }

//如果需要认证的是server:host
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//使用安全策略去判断是否信任(*),安全策略后面讲解
        if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
            NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
        } else {
            [[challenge sender] cancelAuthenticationChallenge:challenge];
        }
    } else {
        if ([challenge previousFailureCount] == 0) {
            if (self.credential) {
                [[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge];
            } else {
                [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
            }
        } else {
            [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
        }
    }
}

- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection {
    return self.shouldUseCredentialStorage;
}

//对于url重定向的情况delegate如何处理,如果是自身遇到重定向问题,直接继承operation,直接设置redirectResponse属性即可
- (NSURLRequest *)connection:(NSURLConnection *)connection
             willSendRequest:(NSURLRequest *)request
            redirectResponse:(NSURLResponse *)redirectResponse
{
    if (self.redirectResponse) {
        return self.redirectResponse(connection, request, redirectResponse);
    } else {
        return request;
    }
}

//post请求中,向外提供progress情况,一般通过继承operation以后直接设置uploadProgress
- (void)connection:(NSURLConnection __unused *)connection
   didSendBodyData:(NSInteger)bytesWritten
 totalBytesWritten:(NSInteger)totalBytesWritten
totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
    dispatch_async(dispatch_get_main_queue(), ^{
        if (self.uploadProgress) {
            self.uploadProgress((NSUInteger)bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
        }
    });
}
//在connetion请求的response返回以后调用的第一个delegate方法,得到NSURLResponse ,NSHTTPResponse对象,可以获取responseHeaders,statusCode等信息
- (void)connection:(NSURLConnection __unused *)connection
didReceiveResponse:(NSURLResponse *)response
{
    self.response = response;
}

//response data内容
- (void)connection:(NSURLConnection __unused *)connection
    didReceiveData:(NSData *)data
{
    NSUInteger length = [data length];
//直接将收到的data写到outputStream中,写入内存中
    while (YES) {
        NSInteger totalNumberOfBytesWritten = 0;
        if ([self.outputStream hasSpaceAvailable]) {
            const uint8_t *dataBuffer = (uint8_t *)[data bytes];

            NSInteger numberOfBytesWritten = 0;
            while (totalNumberOfBytesWritten < (NSInteger)length) {
                numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)];
                if (numberOfBytesWritten == -1) {
                    break;
                }

                totalNumberOfBytesWritten += numberOfBytesWritten;
            }

            break;
        } else {
//如果出错直接调用delegate的connection:didFailWithError:方法
//调用方法以后Once the delegate receives this message, it will receive no further messages for connection.
            [self.connection cancel];
            if (self.outputStream.streamError) {
                [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError];
            }
            return;
        }
    }

    dispatch_async(dispatch_get_main_queue(), ^{
        self.totalBytesRead += (long long)length;
//在mainQueue中调用downloadProgress方法,一般通过继承operation以后直接设置uploadProgress
        if (self.downloadProgress) {
            self.downloadProgress(length, self.totalBytesRead, self.response.expectedContentLength);
        }
    });
}

//当url请求成功完成以后会调用,获取了responseData数据,该数据会在completionBlock里面通过
//responeObject 的 getter方法调用
- (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection {
    self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];

    [self.outputStream close];
    if (self.responseData) {
       self.outputStream = nil;
    }

    self.connection = nil;

    [self finish];//改变connection状态
}

- (void)connection:(NSURLConnection __unused *)connection
  didFailWithError:(NSError *)error
{
    self.error = error;

    [self.outputStream close];
    if (self.responseData) {
        self.outputStream = nil;
    }

    self.connection = nil;

    [self finish];//改变connection状态
}

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
                  willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
    if (self.cacheResponse) {
        return self.cacheResponse(connection, cachedResponse);
    } else {
        if ([self isCancelled]) {
            return nil;
        }

        return cachedResponse;
    }
}

这部分代码注释比较清晰,具体的调用流程如下:
①operationDidStart会打开outputStream
②connection:didReceiveData: delegate 方法中每次都往outputStream写数据
③写数据同时回调上层传进来的相应的downloadProgress block
④在connection:DidFinishLoading时,拿到responseData,关闭outputStream,清空ouputstream,设置operation状态。
⑤finished状态以后,会自动调用completionBlock,回调到上层

5 其他内容

① 后台运行使用setShouldExecuteAsBackgroundTaskWithExpirationHandler
② 锁 self.lock = [[NSRecursiveLock alloc] init]; ,在访问重要变量时,都会加锁
③ 为什么在completionBlock中使用dispatch_group系列方法?主要目的是等待加入group中的success&failure执行完成以后使用dispatch_group_notify方法setCompletionBlock:nil

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

推荐阅读更多精彩内容