AOP實(shí)現(xiàn) iOS無侵入列表曝光埋點(diǎn)

最近的需求自己造個(gè)輪子
Lits+Exposure.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

typedef enum : NSUInteger {
    YBMExposureScrollDirectionVertical = 0,
    YBMExposureScrollDirectionHorizontal = 1,
} YBMExposureScrollDirection;

@protocol YBMExposureDelegate <NSObject>

@optional
/// 提供給TableView / CollectionView回調(diào)的曝光方法
/// @param indexPath 對(duì)應(yīng)的列表位置
- (void)exposureBuriedPointWithIndexPath:(NSIndexPath *)indexPath;

/// 提供給ScrollView 回調(diào)曝光的方法 使用時(shí)要注意scrollView上自帶的滾動(dòng)條
/// @param view 對(duì)應(yīng)的子視圖
- (void)exposureBuiedPointWithView:(UIView *)view;

/// 完全停止?jié)L動(dòng)的回調(diào)
/// @param scrollView 當(dāng)前的滾動(dòng)視圖
- (void)scrollViewDidEndScroll:(UIScrollView *)scrollView;

@end

@interface UIScrollView (Exposure)

@property (nonatomic, weak) id<YBMExposureDelegate> exposureDelegate;

/// 設(shè)置滾動(dòng)方向 默認(rèn)YBMExposureScrollDirectionVertical
@property (nonatomic, assign) YBMExposureScrollDirection direction;

@end

@interface UITableView (Exposure)

@property (nonatomic, weak) id<YBMExposureDelegate> exposureDelegate;

@property (nonatomic, readonly, getter=getLastVisibleIndexPaths , copy) NSArray<NSIndexPath *> * lastVisibleIndexPaths;

@property (nonatomic, readonly, getter=getCalculateExposureIndexPaths , copy) NSArray<NSIndexPath *> * calculateExposureIndexPaths;

@end


@interface UICollectionView (Exposure)

@property (nonatomic, weak) id<YBMExposureDelegate> exposureDelegate;

@property (nonatomic, readonly, getter=getLastVisibleIndexPaths , copy) NSArray<NSIndexPath *> * lastVisibleIndexPaths;

@property (nonatomic, readonly, getter=getCalculateExposureIndexPaths , copy) NSArray<NSIndexPath *> * calculateExposureIndexPaths;

/// 設(shè)置滾動(dòng)方向 默認(rèn)YBMExposureScrollDirectionVertical
@property (nonatomic, assign) YBMExposureScrollDirection direction;

@end
NS_ASSUME_NONNULL_END

Lits+Exposure.m

#import "List+Exposure.h"
#import <objc/runtime.h>

static const int TableCacheIndexsKey;

static const int CollectionCacheIndexsKey;

static const int ScrollExposureDelegateKey;
static const int ScrollExposureDirection;

// 露出曝光 百分比
CGFloat const ExposurePercentage = 0.8;

void SwizzlingMethod(Class cls, SEL originSEL, SEL swizzledSEL) {
    Method originMethod = class_getInstanceMethod(cls, originSEL);
    Method swizzledMethod = nil;
    if (!originMethod) {
        originMethod  =  class_getClassMethod(cls, originSEL);
        if (!originMethod) {
            return;
        }
        swizzledMethod = class_getClassMethod(cls, swizzledSEL);
        if (!swizzledMethod) {
            return;
        }
    } else {
        swizzledMethod = class_getInstanceMethod(cls, swizzledSEL);
        if (!swizzledMethod) {
            return;
        }
    }
    if (class_addMethod(cls, originSEL, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
    } else {
        method_exchangeImplementations(originMethod, swizzledMethod);
    }
}

void SwizzlingDelegateMethod(Class delegateClass, Class listClass, SEL originSEL, SEL swizzledSEL) {
    Method originMethod = class_getInstanceMethod(delegateClass, originSEL);
    
    Method swizzledMethod = class_getInstanceMethod(listClass, swizzledSEL);
    
    class_addMethod(delegateClass, swizzledSEL, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
    
    BOOL c = class_addMethod(delegateClass, originSEL, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    
    if (!c) {
        method_exchangeImplementations(originMethod, swizzledMethod);
    }
}

@interface UIView (Window)

@end

@implementation UIView (Window)

- (UIWindow *)lastWindow{
    NSArray *windows = [UIApplication sharedApplication].windows;
    for (UIWindow *window in [windows reverseObjectEnumerator]) {
        if ([window isKindOfClass:[UIWindow class]] && CGRectEqualToRect(window.bounds, [UIScreen mainScreen].bounds)) {
            return window;
        }
    }
    return windows.lastObject;
}

@end

#pragma mark - 用來弱引用動(dòng)態(tài)綁定的代理類
@interface YBMExposureDelegateModel: NSObject

@property (nonatomic, weak) id<YBMExposureDelegate> delegate;

@end

@implementation YBMExposureDelegateModel

@end

#pragma mark - 用來緩存已經(jīng)上報(bào)的indexpaths
@interface YBMExposureCacheIndexPaths: NSObject

@property (nonatomic, strong) NSMutableArray<NSIndexPath *> * curExposureIndexPaths;

@property (nonatomic, strong) NSMutableArray<NSIndexPath *> * historyIndexPaths;

@end

@implementation YBMExposureCacheIndexPaths

- (NSMutableArray<NSIndexPath *> *)curExposureIndexPaths {
    if (!_curExposureIndexPaths) {
        _curExposureIndexPaths = [NSMutableArray array];
    }
    return _curExposureIndexPaths;
}

- (NSMutableArray<NSIndexPath *> *)historyIndexPaths {
    if (!_historyIndexPaths) {
        _historyIndexPaths = [NSMutableArray array];
    }
    return _historyIndexPaths;
}

@end

@implementation UIScrollView (Exposure)

+ (void)load {
    /// hook 設(shè)置代理的方法
    SEL originSelector = @selector(setDelegate:);
    SEL swizzledSelector = @selector(swizzled_setDelegate:);
    SwizzlingMethod(self, originSelector, swizzledSelector);
}

- (void)swizzled_setDelegate:(id<UIScrollViewDelegate>)delegate {
    [self swizzled_setDelegate: delegate];
    /// 如果當(dāng)前類 是tableview 或其子類 并且準(zhǔn)守 曝光代理 則進(jìn)行hook方法 檢測(cè)滾動(dòng)停止及刷新方法
    if ([self isKindOfClass:[UIScrollView class]] && [delegate conformsToProtocol:@protocol(YBMExposureDelegate)]) {
        [self hookScrollEndMethod];
    }
}

#pragma mark - hook滾動(dòng)停止方法
- (void)hookScrollEndMethod {
    ///  hook 自然滾動(dòng)停止
    [self hookEndDecelerating];
    
    ///  hook 手勢(shì)介入停止
    [self hookEndDragging];
    
    ///  hook 滾動(dòng)到頂部
    [self hookScrollTop];
}

- (void)hookEndDecelerating {
    SEL originSelector = @selector(scrollViewDidEndDecelerating:);
    SEL swizzledSelector = @selector(swizzled_scrollViewDidEndDecelerating:);
    SwizzlingDelegateMethod([self.delegate class], [self class], originSelector, swizzledSelector);
}

- (void)hookEndDragging {
    SEL originSelector = @selector(scrollViewDidEndDragging:willDecelerate:);
    SEL swizzledSelector = @selector(swizzled_scrollViewDidEndDragging:willDecelerate:);
    SwizzlingDelegateMethod([self.delegate class], [self class], originSelector, swizzledSelector);
}

- (void)hookScrollTop {
    SEL originSelector = @selector(scrollViewDidScrollToTop:);
    SEL swizzledSelector = @selector(swizzled_scrollViewDidScrollToTop:);
    SwizzlingDelegateMethod([self.delegate class], [self class], originSelector, swizzledSelector);
}

#pragma mark - 監(jiān)聽滾動(dòng)停止
- (void)swizzled_scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    if ([self respondsToSelector:@selector(swizzled_scrollViewDidEndDecelerating:)]) {
        [self swizzled_scrollViewDidEndDecelerating:scrollView];
    }
    BOOL scrollToScrollStop = !scrollView.tracking && !scrollView.dragging && !scrollView.decelerating;
    if (scrollToScrollStop) {
        [scrollView scrollViewDidEndScroll];
    }
}

- (void)swizzled_scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if ([self respondsToSelector:@selector(swizzled_scrollViewDidEndDragging:willDecelerate:)]) {
        [self swizzled_scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
    }
    if (!decelerate) {
        BOOL dragToDragStop = scrollView.tracking && !scrollView.dragging && !scrollView.decelerating;
        if (dragToDragStop) {
            [scrollView scrollViewDidEndScroll];
        }
    }
}

- (void)swizzled_scrollViewDidScrollToTop:(UIScrollView *)scrollView {
    if ([self respondsToSelector:@selector(swizzled_scrollViewDidScrollToTop:)]) {
        [self swizzled_scrollViewDidScrollToTop: scrollView];
    }
    [scrollView scrollViewDidEndScroll];
}

- (void)scrollViewDidEndScroll {
    if (self.exposureDelegate && [self.exposureDelegate respondsToSelector:@selector(scrollViewDidEndScroll:)]) {
        [self.exposureDelegate scrollViewDidEndScroll:self];
    }
    for (UIView * view in self.subviews.objectEnumerator) {
        if (view.isHidden == NO && view.alpha > 0) {
            CGRect previousCellRect = view.frame;
            UIWindow * window = [self lastWindow];
            CGRect convertRect = [self convertRect:previousCellRect toView:window];
            CGRect scrollRect = CGRectIntersection([self.superview convertRect:self.frame toView:window], window.bounds);
            if (CGRectContainsRect(scrollRect, convertRect)) {
                if (self.exposureDelegate && [self.exposureDelegate respondsToSelector:@selector(exposureBuiedPointWithView:)]) {
                    [self.exposureDelegate exposureBuiedPointWithView:view];
                }
            }
        }
        
    }
}

#pragma mark - 動(dòng)態(tài)綁定代理
- (id<YBMExposureDelegate>)exposureDelegate {
    YBMExposureDelegateModel * model = objc_getAssociatedObject(self, &ScrollExposureDelegateKey);
    return model.delegate;
}

- (void)setExposureDelegate:(id<YBMExposureDelegate>)exposureDelegate {
    YBMExposureDelegateModel * model = [[YBMExposureDelegateModel alloc] init];
    model.delegate = exposureDelegate;
    objc_setAssociatedObject(self, &ScrollExposureDelegateKey, model, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

#pragma mark - 綁定屬性
- (YBMExposureScrollDirection)direction {
    NSNumber * number = objc_getAssociatedObject(self, &ScrollExposureDirection);
    YBMExposureScrollDirection direction = [number unsignedIntegerValue];
    return direction;
}

- (void)setDirection:(YBMExposureScrollDirection)direction {
    NSNumber * number = [NSNumber numberWithUnsignedInteger:direction];
    objc_setAssociatedObject(self, &ScrollExposureDirection, number, OBJC_ASSOCIATION_ASSIGN);
}
@end

#pragma mark - UITableView
@interface UITableView()

@property (nonatomic, strong) YBMExposureCacheIndexPaths * cacheIndexs;

@end


@implementation UITableView (Exposure)

#pragma mark - hook滾動(dòng)停止方法
- (void)hookScrollEndMethod {
    [super hookScrollEndMethod];
    /// 設(shè)置緩存屬性
    self.cacheIndexs = [[YBMExposureCacheIndexPaths alloc] init];
    
    ///  hook Reload 方法
    [self hookReload];
}

-  (void)hookReload {
    SEL originSelector = @selector(reloadData);
    SEL swizzledSelector = @selector(swizzled_Reload);
    SwizzlingMethod([self class], originSelector, swizzledSelector);
}

#pragma mark - 獲取上一次上報(bào)的位置信息,供外部使用
- (NSArray<NSIndexPath *> *)getLastVisibleIndexPaths {
    return self.cacheIndexs.curExposureIndexPaths;
}

#pragma mark - 重新計(jì)算當(dāng)前區(qū)域曝光的IndexPath
- (NSArray<NSIndexPath *> *)getCalculateExposureIndexPaths {
    __block NSMutableArray * array = [NSMutableArray array];
    NSArray<NSIndexPath *> * indexPathsForVisibleRows = self.indexPathsForVisibleRows;
    
    [indexPathsForVisibleRows enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([self calculateExposureForIndexPath:obj]) {
            [array addObject:obj];
        }
    }];
    return array;
}

#pragma mark - 監(jiān)聽滾動(dòng)停止

- (void)scrollViewDidEndScroll {
    if (self.exposureDelegate && [self.exposureDelegate respondsToSelector:@selector(scrollViewDidEndScroll:)]) {
        [self.exposureDelegate scrollViewDidEndScroll:self];
    }
    NSLog(@"分割線 -----------**************----------- 分割線");
    
    self.cacheIndexs.historyIndexPaths = [self.cacheIndexs.curExposureIndexPaths copy];
    [self.cacheIndexs.curExposureIndexPaths removeAllObjects];
    
    NSArray<NSIndexPath *> * indexPathsForVisibleRows = self.indexPathsForVisibleRows;
    
    [indexPathsForVisibleRows enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [self exposureBuriedPoint: obj];
    }];
}

#pragma mark - 曝光位置判斷
- (void)exposureBuriedPoint:(NSIndexPath *)indexPath {
    BOOL isExposure = [self calculateExposureForIndexPath: indexPath];
    if (isExposure) {
        [self callDelegateExposure:indexPath];
    }
}

- (BOOL)calculateExposureForIndexPath:(NSIndexPath *)indexPath {
    CGRect previousCellRect = [self rectForRowAtIndexPath:indexPath];
    
    UIWindow * window = [self lastWindow];
    
    CGRect convertRect = [self convertRect:previousCellRect toView:window];
    
    CGRect tabRect = CGRectIntersection([self.superview convertRect:self.frame toView:window], window.bounds);
    
    CGFloat currentTop = CGRectGetMinY(convertRect) - CGRectGetMinY(tabRect);
    if (currentTop < 0) {
        CGFloat percentage = (convertRect.size.height + currentTop) / convertRect.size.height;
        if (percentage >= ExposurePercentage) {
            return YES;
        }
    } else {
        CGFloat currentBottom = CGRectGetMaxY(tabRect) - CGRectGetMaxY(convertRect);
        if (currentBottom < 0) {
            CGFloat percentage = (convertRect.size.height + currentBottom) / convertRect.size.height;
            if (percentage >= ExposurePercentage) {
                return YES;
            }
        } else {
            return YES;
        }
    }
    return NO;
}

- (void)callDelegateExposure:(NSIndexPath *)indexPath {
    /// 重復(fù)上報(bào)控制
    if (![self.cacheIndexs.historyIndexPaths containsObject:indexPath]) {
        if (self.exposureDelegate && [self.exposureDelegate respondsToSelector:@selector(exposureBuriedPointWithIndexPath:)]) {
            [self.exposureDelegate exposureBuriedPointWithIndexPath:indexPath];
        }
    }
    [self.cacheIndexs.curExposureIndexPaths addObject:indexPath];
}

/// 計(jì)算tableview的總行數(shù)
- (NSInteger)calculationTotalNumber {
    NSInteger totalNumber = 0;
    NSInteger sections = self.numberOfSections;
    for (NSInteger section = 0; section < sections; section ++) {
        totalNumber += [self numberOfRowsInSection:section];
    }
    return totalNumber;
}

#pragma mark - 刷新方法
- (void)swizzled_Reload {
    /// 記錄頁面Item個(gè)數(shù)
    /// 刷新后item個(gè)數(shù)增加 則判定為加載更多克婶,此時(shí)不做重置處理。 如果刷新后item個(gè)數(shù)減少或不變則判定為刷新阱表,重置上次曝光的位置信息
    
    /// 記錄item數(shù)量
    NSInteger curNumber = [self calculationTotalNumber];
    
    /// 調(diào)用原方法
    [self swizzled_Reload];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        NSInteger endNumber = [self calculationTotalNumber];
        if (endNumber <= curNumber) {
            [self.cacheIndexs.curExposureIndexPaths removeAllObjects];
        }
        /// 刷新結(jié)束后 觸發(fā)一次上報(bào)
        [self scrollViewDidEndScroll];
    });
}

#pragma mark - 綁定屬性
- (YBMExposureCacheIndexPaths *)cacheIndexs {
    YBMExposureCacheIndexPaths * cache = objc_getAssociatedObject(self, &TableCacheIndexsKey);
    return cache;
}

- (void)setCacheIndexs:(YBMExposureCacheIndexPaths *)cacheIndexs {
    objc_setAssociatedObject(self, &TableCacheIndexsKey, cacheIndexs, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end


#pragma mark - UICollectionView
@interface UICollectionView()

@property (nonatomic, strong) YBMExposureCacheIndexPaths * cacheIndexs;

@end

@implementation UICollectionView (Exposure)

#pragma mark - hook滾動(dòng)停止方法
- (void)hookScrollEndMethod {
    [super hookScrollEndMethod];
    
    /// 設(shè)置緩存屬性
    self.cacheIndexs = [[YBMExposureCacheIndexPaths alloc] init];
    
    ///  hook Reload 方法
    [self hookReload];
}

-  (void)hookReload {
    SEL originSelector = @selector(reloadData);
    SEL swizzledSelector = @selector(swizzled_reload);
    SwizzlingMethod([self class], originSelector, swizzledSelector);
}

#pragma mark - 獲取當(dāng)前可見的IndexPath 供外部使用
- (NSArray<NSIndexPath *> *)getLastVisibleIndexPaths {
    return self.cacheIndexs.curExposureIndexPaths;
}

#pragma mark - 重新計(jì)算當(dāng)前區(qū)域曝光的IndexPath
- (NSArray<NSIndexPath *> *)getCalculateExposureIndexPaths {
    __block NSMutableArray * array = [NSMutableArray array];
    NSArray<NSIndexPath *> * indexPathsForVisibleRows = self.indexPathsForVisibleItems;
    
    [indexPathsForVisibleRows enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([self calculateExposureForIndexPath:obj]) {
            [array addObject:obj];
        }
    }];
    return array;
}

#pragma mark - 監(jiān)聽滾動(dòng)停止
- (void)scrollViewDidEndScroll {
    if (self.exposureDelegate && [self.exposureDelegate respondsToSelector:@selector(scrollViewDidEndScroll:)]) {
        [self.exposureDelegate scrollViewDidEndScroll:self];
    }
    NSLog(@"分割線 -----------**************----------- 分割線");
    self.cacheIndexs.historyIndexPaths = [self.cacheIndexs.curExposureIndexPaths copy];
    [self.cacheIndexs.curExposureIndexPaths removeAllObjects];
    
    NSArray<NSIndexPath *> * indexPathsForVisibleRows = self.indexPathsForVisibleItems;
    
    [indexPathsForVisibleRows enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [self exposureBuriedPoint: obj];
    }];
}

#pragma mark - 曝光位置判斷
- (void)exposureBuriedPoint:(NSIndexPath *)indexPath {
    BOOL isExposure = [self calculateExposureForIndexPath: indexPath];
    if (isExposure) {
        [self callDelegateExposure:indexPath];
    }
}

- (BOOL)calculateExposureForIndexPath:(NSIndexPath *)indexPath {
    CGRect previousCellRect = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath].frame;
    
    UIWindow * window = [self lastWindow];
    
    CGRect convertRect = [self convertRect:previousCellRect toView:window];
    
    CGRect tabRect = CGRectIntersection([self.superview convertRect:self.frame toView:window], window.bounds);
    
    if (self.direction == YBMExposureScrollDirectionVertical) {
        CGFloat currentTop = CGRectGetMinY(convertRect) - CGRectGetMinY(tabRect);
        if (currentTop < 0) {
            CGFloat percentage = (convertRect.size.height + currentTop) / convertRect.size.height;
            if (percentage >= ExposurePercentage) {
                return YES;
            }
        } else {
            CGFloat currentBottom = CGRectGetMaxY(tabRect) - CGRectGetMaxY(convertRect);
            if (currentBottom < 0) {
                CGFloat percentage = (convertRect.size.height + currentBottom) / convertRect.size.height;
                if (percentage >= ExposurePercentage) {
                    return YES;
                }
            } else {
                return YES;
            }
        }
    } else {
        CGFloat currentLeft = CGRectGetMinX(convertRect) - CGRectGetMinX(tabRect);
        if (currentLeft < 0) {
            CGFloat percentage = (convertRect.size.width + currentLeft) / convertRect.size.width;
            if (percentage >= ExposurePercentage) {
                return YES;
            }
        } else {
            CGFloat currentRight = CGRectGetMaxX(tabRect) - CGRectGetMaxX(convertRect);
            if (currentRight < 0) {
                CGFloat percentage = (convertRect.size.width + currentRight) / convertRect.size.width;
                if (percentage >= ExposurePercentage) {
                    return YES;
                }
            } else {
                return YES;
            }
        }
    }
    return NO;
}

- (void)callDelegateExposure:(NSIndexPath *)indexPath {
    /// 重復(fù)上報(bào)控制
    if (![self.cacheIndexs.historyIndexPaths containsObject:indexPath]) {
        if (self.exposureDelegate && [self.exposureDelegate respondsToSelector:@selector(exposureBuriedPointWithIndexPath:)]) {
            [self.exposureDelegate exposureBuriedPointWithIndexPath:indexPath];
        }
    }
    [self.cacheIndexs.curExposureIndexPaths addObject:indexPath];
}

/// 計(jì)算CollectionView的總行數(shù)
- (NSInteger)calculationTotalNumber {
    NSInteger totalNumber = 0;
    if (self.dataSource != nil) {
        NSInteger sections = [self.dataSource numberOfSectionsInCollectionView:self];
        for (NSInteger section = 0; section < sections; section ++) {
            totalNumber += [self.dataSource collectionView:self numberOfItemsInSection:section];
        }
    }
    return totalNumber;
}

#pragma mark - 刷新方法
- (void)swizzled_reload {
    /// 記錄頁面Item個(gè)數(shù)
    /// 刷新后item個(gè)數(shù)增加 則判定為加載更多淹禾,此時(shí)不做重置處理。 如果刷新后item個(gè)數(shù)減少或不變則判定為刷新茴扁,重置上次曝光的位置信息
    
    /// 記錄item數(shù)量
    NSInteger curNumber = [self calculationTotalNumber];
    
    /// 調(diào)用原方法
    [self swizzled_reload];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        NSInteger endNumber = [self calculationTotalNumber];
        if (endNumber <= curNumber) {
            [self.cacheIndexs.curExposureIndexPaths removeAllObjects];
        }
        /// 刷新結(jié)束后 觸發(fā)一次上報(bào)
        [self scrollViewDidEndScroll];
    });
}

#pragma mark - 綁定屬性
- (YBMExposureCacheIndexPaths *)cacheIndexs {
    YBMExposureCacheIndexPaths * cache = objc_getAssociatedObject(self, &CollectionCacheIndexsKey);
    return cache;
}

- (void)setCacheIndexs:(YBMExposureCacheIndexPaths *)cacheIndexs {
    objc_setAssociatedObject(self, &CollectionCacheIndexsKey, cacheIndexs, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末铃岔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子峭火,更是在濱河造成了極大的恐慌毁习,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卖丸,死亡現(xiàn)場(chǎng)離奇詭異纺且,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)稍浆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門载碌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人衅枫,你說我怎么就攤上這事嫁艇。” “怎么了弦撩?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵步咪,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我益楼,道長(zhǎng)猾漫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任感凤,我火速辦了婚禮悯周,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘俊扭。我一直安慰自己队橙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布萨惑。 她就那樣靜靜地躺著捐康,像睡著了一般。 火紅的嫁衣襯著肌膚如雪庸蔼。 梳的紋絲不亂的頭發(fā)上解总,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音姐仅,去河邊找鬼花枫。 笑死刻盐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的劳翰。 我是一名探鬼主播敦锌,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼佳簸!你這毒婦竟也來了乙墙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤生均,失蹤者是張志新(化名)和其女友劉穎听想,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體马胧,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡汉买,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了佩脊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛙粘。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖邻吞,靈堂內(nèi)的尸體忽然破棺而出组题,到底是詐尸還是另有隱情,我是刑警寧澤抱冷,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布崔列,位于F島的核電站,受9級(jí)特大地震影響旺遮,放射性物質(zhì)發(fā)生泄漏赵讯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一耿眉、第九天 我趴在偏房一處隱蔽的房頂上張望边翼。 院中可真熱鬧,春花似錦鸣剪、人聲如沸组底。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽债鸡。三九已至,卻和暖如春铛纬,著一層夾襖步出監(jiān)牢的瞬間厌均,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工告唆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留棺弊,地道東北人晶密。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像模她,于是被迫代替她去往敵國和親稻艰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

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