iOS AsyncDisplayKit 代碼閱讀筆記

如果看過 保持界面流暢技巧
這篇文章肯定會對AsyncDisplayKit 有強烈的好奇心。

此文章系 AsyncDisplayKit 的源碼閱讀筆記。零散的記錄一些點。讀完了說不定就能開始分析 AsyncDisplayKit 了

1:后臺釋放變量
void ASPerformBlockOnDeallocationQueue(void (^block)())
{
  static dispatch_queue_t queue;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    queue = dispatch_queue_create("org.AsyncDisplayKit.deallocationQueue", DISPATCH_QUEUE_SERIAL);
  });
  
  dispatch_async(queue, block);
}

ASPerformBlockOnDeallocationQueue用來釋放對象在后臺線程,讓界面更流暢。 比如應(yīng)用

- (void)_clearImage
{
  // Destruction of bigger images on the main thread can be expensive
  // and can take some time, so we dispatch onto a bg queue to
  // actually dealloc.
  __block UIImage *image = self.image;
  CGSize imageSize = image.size;
  BOOL shouldReleaseImageOnBackgroundThread = imageSize.width > kMinReleaseImageOnBackgroundSize.width ||
                                              imageSize.height > kMinReleaseImageOnBackgroundSize.height;
  if (shouldReleaseImageOnBackgroundThread) {
    ASPerformBlockOnDeallocationQueue(^{
      image = nil;
    });
  }
///TODO
///

根據(jù)注釋看,說大圖在主線程釋放的時候會消耗更好的性能和時間。此處,最小尺寸是

static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};

20x20尺寸的圖,在我們的應(yīng)用里比這個大很多的多的是吧!但是從來沒有想過,要在其他線程釋放!
所以 AsyncDisplayKit 性能好是有道理的,性能處理,做到極致!!

2:一些特定的方法只能在父類中實現(xiàn)。
BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector)
{
  Method superclassMethod = class_getInstanceMethod(superclass, selector);
  Method subclassMethod = class_getInstanceMethod(subclass, selector);
  IMP superclassIMP = superclassMethod ? method_getImplementation(superclassMethod) : NULL;
  IMP subclassIMP = subclassMethod ? method_getImplementation(subclassMethod) : NULL;
  return (superclassIMP != subclassIMP);
}

ASSubclassOverridesSelector用來判斷子類是不是實現(xiàn)了父類的某個方法,用法,配合#define ASDisplayNodeAssert(...) NSAssert(__VA_ARGS__)宏,在initialize方法中進行斷言,某些方法,子類一定不能實現(xiàn)。。

比如ASImageNode

+ (void)initialize
{
  [super initialize];
  
  if (self != [ASImageNode class]) {
    // Prevent custom drawing in subclasses
    ASDisplayNodeAssert(!ASSubclassOverridesClassSelector([ASImageNode class], self, @selector(displayWithParameters:isCancelled:)), @"Subclass %@ must not override displayWithParameters:isCancelled: method. Custom drawing in %@ subclass is not supported.", NSStringFromClass(self), NSStringFromClass([ASImageNode class]));
  }
}

3:任務(wù)放到主線程的正確姿勢
void ASPerformBlockOnMainThread(void (^block)())
{
  if (block == nil){
    return;
  }
  if (ASDisplayNodeThreadIsMain()) {
    block();
  } else {
    dispatch_async(dispatch_get_main_queue(), block);
  }
}

如果不做判斷,就會讓任務(wù)執(zhí)行的時機發(fā)生變化!

4:消耗性能的操作,盡量只做一次求值操作

比如:

CGFloat ASScreenScale()
{
  static CGFloat __scale = 0.0;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    ASDisplayNodeCAssertMainThread();
    __scale = [[UIScreen mainScreen] scale];
  });
  return __scale;
}

ASScreenScale的調(diào)用時機在 ASDisplayNodeload方法中

+ (void)load
{
  // Ensure this value is cached on the main thread before needed in the background.
  ASScreenScale();
}

確保scale 的值已經(jīng)計算好了,并且是在主線程中計算的。因為使用UIKit相關(guān)代碼了,所以一定要確保在主線程。

5:ASSentinel

ASSentinel 的作用是防止 layer 被重復(fù)渲染使用的,比如,一個layer 已經(jīng)超出屏幕或者下一次渲染的數(shù)據(jù)已經(jīng)來了,那么,需要一個標(biāo)示,來標(biāo)記,此次不用渲染了。

@interface ASSentinel : NSObject
- (int32_t)value;
- (int32_t)increment;
@end

定義很簡單,返回一個值,increment 遞增當(dāng)前的值。
在基類的 _initializeInstance函數(shù)中初始化,

_displaySentinel = [[ASSentinel alloc] init];

在需要關(guān)閉當(dāng)前渲染的時候進行自增

- (void)cancelDisplayAsyncLayer:(_ASDisplayLayer *)asyncLayer
{
  [_displaySentinel increment];
}
或者 
- (void)cancelAsyncDisplay
{
  ASDisplayNodeAssertMainThread();
  [_displaySentinel increment];
.........
}

delegate 函數(shù)的判斷,也可以使用結(jié)構(gòu)體標(biāo)示有沒有實現(xiàn)某個方法,比如:

struct ASDisplayNodeFlags {
    // public properties
    unsigned synchronous:1;
    unsigned layerBacked:1;
    unsigned displaysAsynchronously:1;
    unsigned shouldRasterizeDescendants:1;
    unsigned shouldBypassEnsureDisplay:1;
    unsigned displaySuspended:1;
    unsigned shouldAnimateSizeChanges:1;
    unsigned hasCustomDrawingPriority:1;
    }

具體使用如下(ps鄙人項目中的使用):

struct {
        unsigned hasJingxiangAction:1;
        unsigned hasCameraAction   :1;
        unsigned hasMuteAction     :1;
        unsigned hasRoomManagerAction:1;
        unsigned hasFollowlistAction:1;
    }_delefateAction; ///<delelgate 行為
    
    設(shè)置代理的時候
    - (void)setDelegate:(id<PXYMoreActionDelegate>)delegate {
    _delegate = delegate;
    _delefateAction.hasJingxiangAction = [_delegate respondsToSelector:@selector(jingxiangAction)];
    _delefateAction.hasCameraAction = [_delegate respondsToSelector:@selector(cameraAction)];
    _delefateAction.hasMuteAction = [_delegate respondsToSelector:@selector(muteAction)];
    _delefateAction.hasRoomManagerAction = [_delegate respondsToSelector:@selector(roomManagerAciton)];
    _delefateAction.hasFollowlistAction = [_delegate respondsToSelector:@selector(followerListAction)];
}

不用每次都調(diào)用respondsToSelector 方法,稍微提升一點效率,使用的時候就可以這樣子判斷了

if (_delefateAction.hasRoomManagerAction) {
     [_delegate roomManagerAciton];
   }

省去了之前繁瑣的判斷。

華麗分割線!!!!

我們看看 node 是什么時候創(chuàng)建 view 或者 layer 的。(顯示視圖,肯定是 view或者layer了)


- (void)addSubnode:(ASDisplayNode *)subnode
{
  ///斷言 #省略
  ASDisplayNodeAssert(subnode, @"Cannot insert a nil subnode");
  ASDisplayNode *oldParent = subnode.supernode;
  ///#不能重復(fù)添加或者自己添加自己
  if (!subnode || subnode == self || oldParent == self) {
    return;
  }
  ///#添加子節(jié)點
  BOOL isMovingEquivalentParents = disableNotificationsForMovingBetweenParents(oldParent, self);
  if (isMovingEquivalentParents) {
    [subnode __incrementVisibilityNotificationsDisabled];
  }
  [subnode removeFromSupernode];

  if (!_subnodes) {
    _subnodes = [[NSMutableArray alloc] init];
  }

  [_subnodes addObject:subnode];
  
  // This call will apply our .hierarchyState to the new subnode.
  // If we are a managed hierarchy, as in ASCellNode trees, it will also apply our .interfaceState.
  [subnode __setSupernode:self];
  ///#如果當(dāng)前的node 已經(jīng)有 view 或者 layer 就給子節(jié)點同樣創(chuàng)建一個 view 或者 layer并且添加進來。。
  if (self.nodeLoaded) {
    ASPerformBlockOnMainThread(^{
      [self _addSubnodeSubviewOrSublayer:subnode];
    });
  }

  ASDisplayNodeAssert(isMovingEquivalentParents == disableNotificationsForMovingBetweenParents(oldParent, self), @"Invariant violated");
  if (isMovingEquivalentParents) {
    [subnode __decrementVisibilityNotificationsDisabled];
  }
}

從流程上看,在 addSubnode 的時候,如果父node已經(jīng)有顯示出來了,就創(chuàng)建。

還有另外一種情況,調(diào)用 view的 addSubnode 方法:

- (void)addSubnode:(ASDisplayNode *)subnode
{
  if (subnode.layerBacked) {
    // Call -addSubnode: so that we use the asyncdisplaykit_node path if possible.
    [self.layer addSubnode:subnode];
  } else {
    ASDisplayNode *selfNode = self.asyncdisplaykit_node;
    if (selfNode) {
      [selfNode addSubnode:subnode];
    } else {
      [self addSubview:subnode.view];
    }
  }
}

最后如果一步 [self addSubview:subnode.view];的時候,就開始走 view的創(chuàng)建流程了。

view 的創(chuàng)建流程:

- (UIView *)view
{
 if (_flags.layerBacked) {
    return nil;
  }
  if (!_view) {
    ASDisplayNodeAssertMainThread();
    [self _loadViewOrLayerIsLayerBacked:NO];
  }
  return _view;
}

- (CALayer *)layer
{
  if (!_layer) {
    ASDisplayNodeAssertMainThread();

    if (!_flags.layerBacked) {
      return self.view.layer;
    }
    [self _loadViewOrLayerIsLayerBacked:YES];
  }
  return _layer;
}

創(chuàng)建 view 或者 layer 的時候,都會使用 ASDisplayNodeAssertMainThread(); 宏來限定,只能在主線程創(chuàng)建。。最后都會調(diào)用 _loadViewOrLayerIsLayerBacked 函數(shù)。如果創(chuàng)建layer 傳入 YES 創(chuàng)建 view 傳入 NO

- (void)_loadViewOrLayerIsLayerBacked:(BOOL)isLayerBacked
{
  /// 如果node 已經(jīng)是釋放狀態(tài),不創(chuàng)建
  if (self._isDeallocating) {
    return;
  }

  /// 如果沒有父節(jié)點,直接返回
  if (![self __shouldLoadViewOrLayer]) {
    return;
  }

  /// 創(chuàng)建 layer
  if (isLayerBacked) {
    _layer = [self _layerToLoad];
    _layer.delegate = (id<CALayerDelegate>)self;
  } else {
  /// 創(chuàng)建 view
    _view = [self _viewToLoad];
    _view.asyncdisplaykit_node = self;
    _layer = _view.layer;
  }
  _layer.asyncdisplaykit_node = self;

  self.asyncLayer.asyncDelegate = self;

///# 其他狀態(tài)設(shè)置
  ......
}

創(chuàng)建view 的過程 如下:

- (UIView *)_viewToLoad
{
  UIView *view;
  ASDN::MutexLocker l(__instanceLock__);

  if (_viewBlock) {
    view = _viewBlock();
    ASDisplayNodeAssertNotNil(view, @"View block returned nil");
    ASDisplayNodeAssert(![view isKindOfClass:[_ASDisplayView class]], @"View block should return a synchronously displayed view");
    _viewBlock = nil;
    _viewClass = [view class];
  } else {
    if (!_viewClass) {
      _viewClass = [self.class viewClass];
    }
    view = [[_viewClass alloc] init];
  }
  
  // Update flags related to special handling of UIImageView layers. More details on the flags
  if (_flags.synchronous && [_viewClass isSubclassOfClass:[UIImageView class]]) {
    _flags.canClearContentsOfLayer = NO;
    _flags.canCallSetNeedsDisplayOfLayer = NO;
  }

  return view;
}

如果初始化的時候有提供_viewBlock,就是用 _viewBlock 返回一個view,要么就是用默認(rèn)的view進行創(chuàng)建。

+ (Class)viewClass
{
  return [_ASDisplayView class];
}

+ (Class)layerClass
{
  return [_ASDisplayLayer class];
}

ASDisplayNode 提供了內(nèi)置的 view 和 layer ,_ASDisplayView 用來轉(zhuǎn)發(fā)一些事件,_ASDisplayLayer 是 性能提升的關(guān)鍵。

從上面可以看出 node 節(jié)點就是包裝了 view 和 layer 的 載體,可以通過node 設(shè)置 view 或者 layer的屬性。在view 或者 layer需要顯示的時候,把屬性設(shè)置上去。

這里忽略掉了布局相關(guān)的代碼。以后有空了補補

_ASDisplayView

_ASDisplayView 是 ASDisplayNode 的 一個私有類,只能在ASDisplayNode 中使用,不能繼承和重寫。前面有提到過

+ (Class)viewClass
{
  return [_ASDisplayView class];
}

_ASDisplayView 的 作用就是重寫了UIView的一些方法用來轉(zhuǎn)發(fā)一些事件給 ASDisplayNode,下面,我們看看主要的函數(shù)。

+ (Class)layerClass
{
  return [_ASDisplayLayer class];
}

重寫 layerClass 函數(shù),使用可以異步繪制的 類 _ASDisplayLayer。

- (void)willMoveToWindow:(UIWindow *)newWindow
{
  BOOL visible = (newWindow != nil);
  if (visible && !_node.inHierarchy) {
    [_node __enterHierarchy];
  }
}

將要添加到window上的時候,把事件轉(zhuǎn)給Node. 當(dāng)然還有其他的類似 didMoveToWindow

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
  if (_node.methodOverrides & ASDisplayNodeMethodOverrideTouchesBegan) {
    [_node touchesBegan:touches withEvent:event];
  } else {
    [super touchesBegan:touches withEvent:event];
  }
}

touche 系列的函數(shù)的重寫,判斷當(dāng)前node 是否實現(xiàn)了對應(yīng)的方法,如果實現(xiàn)了,就把事件轉(zhuǎn)發(fā)給node. ASControlNode的實現(xiàn),就得益于touch 函數(shù)的轉(zhuǎn)發(fā)。

分割線

判斷兩個obj是否相等

ASDISPLAYNODE_INLINE BOOL ASObjectIsEqual(id<NSObject> obj, id<NSObject> otherObj)
{
  return obj == otherObj || [obj isEqual:otherObj];
}

參數(shù)可以為nil 此處!

_ASDisplayLayer

看看_ASDisplayLayer 的實現(xiàn)。

typedef BOOL(^asdisplaynode_iscancelled_block_t)(void);

用來標(biāo)示當(dāng)前異步的操作是否已經(jīng)結(jié)束

@interface _ASDisplayLayer : CALayer
///#設(shè)置是否異步展示
@property (nonatomic, assign) BOOL displaysAsynchronously;
///#關(guān)閉當(dāng)前的異步展示
- (void)cancelAsyncDisplay;
///#異步展示的隊列
+ (dispatch_queue_t)displayQueue;
///#計數(shù)器,之前講過的
@property (nonatomic, strong, readonly) ASSentinel *displaySentinel;
///#展示過程中的代理
@property (nonatomic, weak) id<_ASDisplayLayerDelegate> asyncDelegate;
///#掛起異步顯示
@property (nonatomic, assign, getter=isDisplaySuspended) BOOL displaySuspended;
///#直接展示
- (void)displayImmediately;
ASCALayerExtendedDelegate

bounds 改變的時候回調(diào)用這個代理的方法。

_ASDisplayLayerDelegate

異步渲染的時候一些回調(diào),node 可以實現(xiàn)這些方法,來實現(xiàn)自己的渲染,

_ASDisplayLayer實現(xiàn)

- (instancetype)init
{
  if ((self = [super init])) {
    _displaySentinel = [[ASSentinel alloc] init];

    self.opaque = YES;
  }
  return self;
}

_displaySentinel 初始化異步展示關(guān)閉的條件。

看一些主要的方法。

+ (id)defaultValueForKey:(NSString *)key
{
  if ([key isEqualToString:@"displaysAsynchronously"]) {
    return @YES;
  } else {
    return [super defaultValueForKey:key];
  }
}

設(shè)置layer的默認(rèn)的屬性值,這是設(shè)置 displaysAsynchronously 默認(rèn)為 YES。(默認(rèn)開啟異步展示效果)

- (void)setNeedsDisplay
{
///#判斷是不是主線程
  ASDisplayNodeAssertMainThread();
///#線程鎖
  _displaySuspendedLock.lock();
  ///關(guān)閉當(dāng)前的繪制,已經(jīng)在繪制的不能被關(guān)閉,如果還沒有開始繪制,但是在隊列里面,是能關(guān)閉的
  [self cancelAsyncDisplay];

  // Short circuit if display is suspended. When resumed, we will setNeedsDisplay at that time.
  if (!_displaySuspended) {
    [super setNeedsDisplay];
  }
  _displaySuspendedLock.unlock();
}

setNeedsDisplay 設(shè)置內(nèi)容需要重新繪制。開始繪制之前,先關(guān)閉上次還沒有開始繪制的任務(wù)!

+ (dispatch_queue_t)displayQueue
{
  static dispatch_queue_t displayQueue = NULL;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    displayQueue = dispatch_queue_create("org.AsyncDisplayKit.ASDisplayLayer.displayQueue", DISPATCH_QUEUE_CONCURRENT);
    // we use the highpri queue to prioritize UI rendering over other async operations
    dispatch_set_target_queue(displayQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
  });

  return displayQueue;
}

displayQueue 異步繪制的隊列。注意,隊列是 DISPATCH_QUEUE_CONCURRENT 并行的。并且 dispatch_set_target_queue 設(shè)置的 DISPATCH_QUEUE_PRIORITY_HIGH。跟UI繪制相關(guān)的隊列,優(yōu)先級最高。

- (void)display
{
///#此方法只能在主線程調(diào)用
  ASDisplayNodeAssertMainThread();
  [self _hackResetNeedsDisplay];
///如果已經(jīng)掛起了,返回
  if (self.isDisplaySuspended) {
    return;
  }
///開始調(diào)用繪制
  [self display:self.displaysAsynchronously];
}

- (void)display:(BOOL)asynchronously
{
///如果大小為0 的時候是否繪制
  if (CGRectIsEmpty(self.bounds)) {
    _attemptedDisplayWhileZeroSized = YES;
  }

  id<_ASDisplayLayerDelegate> NS_VALID_UNTIL_END_OF_SCOPE strongAsyncDelegate;
  {
    _asyncDelegateLock.lock();
    strongAsyncDelegate = _asyncDelegate;
    _asyncDelegateLock.unlock();
  }
  ///開始調(diào)用代理的方法,進行繪制
  [strongAsyncDelegate displayAsyncLayer:self asynchronously:asynchronously];
}

so,繪制部分交給了代理,也就是Node....ok,我們看一下node的繪制部分。。。
相關(guān)代碼在ASDisplayNode+AsyncDisplay.mm文件中

- (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously
{
///必須在主線程進行
  ASDisplayNodeAssertMainThread();
///上鎖
  ASDN::MutexLocker l(__instanceLock__);

  if (_hierarchyState & ASHierarchyStateRasterized) {
    return;
  }
///判斷有沒有取消當(dāng)前繪制的block
  ASSentinel *displaySentinel = (asynchronously ? _displaySentinel : nil);
  int32_t displaySentinelValue = [displaySentinel increment];
  asdisplaynode_iscancelled_block_t isCancelledBlock = ^{
    return BOOL(displaySentinelValue != displaySentinel.value);
  };

  ///生成繪制的block 返回一個image 直接設(shè)置在layer上
  asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:asynchronously isCancelledBlock:isCancelledBlock rasterizing:NO];
  ///#如果沒有繪制的block,那就沒有必要進行了
  if (!displayBlock) {
    return;
  }
  ///插入runloop的block
  ASDisplayNodeAssert(_layer, @"Expect _layer to be not nil");
  asyncdisplaykit_async_transaction_operation_completion_block_t completionBlock = ^(id<NSObject> value, BOOL canceled){
    ASDisplayNodeCAssertMainThread();
    if (!canceled && !isCancelledBlock()) {
      UIImage *image = (UIImage *)value;
      BOOL stretchable = (NO == UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero));
      if (stretchable) {
        ASDisplayNodeSetupLayerContentsWithResizableImage(_layer, image);
      } else {
        _layer.contentsScale = self.contentsScale;
        _layer.contents = (id)image.CGImage;
      }
      [self didDisplayAsyncLayer:self.asyncLayer];
    }
  };
///調(diào)用即將渲染layer的函數(shù)
  [self willDisplayAsyncLayer:self.asyncLayer];

  if (asynchronously) {///如果是異步的,加入到runloop中
    // Async rendering operations are contained by a transaction, which allows them to proceed and concurrently
    // while synchronizing the final application of the results to the layer's contents property (completionBlock).
    
    // First, look to see if we are expected to join a parent's transaction container.
    CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ? : _layer;
    
    // In the case that a transaction does not yet exist (such as for an individual node outside of a container),
    // this call will allocate the transaction and add it to _ASAsyncTransactionGroup.
    // It will automatically commit the transaction at the end of the runloop.
    _ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction;
    
    // Adding this displayBlock operation to the transaction will start it IMMEDIATELY.
    // The only function of the transaction commit is to gate the calling of the completionBlock.
    [transaction addOperationWithBlock:displayBlock priority:self.drawingPriority queue:[_ASDisplayLayer displayQueue] completion:completionBlock];
  } else {
  ///不是異步,直接把image,設(shè)置在layer上!!
    UIImage *contents = (UIImage *)displayBlock();
    completionBlock(contents, NO);
  }
}

此處,相關(guān)技術(shù)可以通過YY大神博客了解 保持界面流暢技巧

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

推薦閱讀更多精彩內(nèi)容