iOS 全埋點(diǎn)-UITaleView和UICollectionView的點(diǎn)擊事件(4)

寫在前面

傳送門:

前面的系列章節(jié)可以查看上面連接卷中,本章節(jié)主要是介紹 iOS全埋點(diǎn)序列文章(4)UITaleView和UICollectionView的點(diǎn)擊事件

前言

$AppClick事件采集中,還有兩個(gè)比較特殊 的控件议忽。

  • UITableView
  • UICollectionView
    這兩個(gè)控件的點(diǎn)擊事件栈幸,一般指的是點(diǎn)擊 UITableViewCellUICollectionViewCell帮辟。而 UITableViewCellUICollectionViewCell都是直接繼承自UIView類由驹,而不是UIControl類,因此并炮,我們之前實(shí)現(xiàn)$AppClick事件全埋點(diǎn)的兩個(gè)方案均不適用于UITableView和UICollectionView控件甥郑。

關(guān)于實(shí)現(xiàn)UITableView和UICollectionView控 件$AppClick事件的全埋點(diǎn)澜搅,常見的方案有三種。

  1. 方法交換
  2. 動(dòng)態(tài)子類
  3. 消息轉(zhuǎn)發(fā)

這三種方案各有優(yōu)缺點(diǎn)养篓。下面赂蕴,我們以 UITableView控件為例概说,分別介紹如何使用這三種 方案實(shí)現(xiàn)$AppClick事件的全埋點(diǎn)。

支持UITableView控件

方案一:方法交換

眾所周知萍丐,如果需要處理UITableView的點(diǎn)擊操作放典,需要先設(shè)置 UITableViewdelegate屬性,并實(shí)現(xiàn)UITableViewDelegate協(xié)議的- tableView:didSelectRowAtIndexPath:方法壳影。因此,我們也很容易想到使用 Method Swizzling交換-tableView:didSelectRowAtIndexPath:方法來實(shí)現(xiàn) UITableView控件$AppClick事件的全埋點(diǎn)

初始思路
首先根灯,我們使用Method Swizzling交換UITableView- setDelegate:方法烙肺;然后氧卧,獲取實(shí)現(xiàn)UITableViewDelegate協(xié)議的delegate對象,在得到delegate對象之后怎栽,交換delegate對象的- tableView:didSelectRowAtIndexPath:方法;最后脚祟,在交換后的方法中觸發(fā) $AppClick事件,從而實(shí)現(xiàn)UITableView控件$AppClick事件全埋點(diǎn)为黎。

新建一個(gè)UITableView的類別CountData

UItableView+CountData.m

#import "UITableView+CountData.h"
#import "NSObject+Swizzler.h"
#import <objc/message.h>
#import <objc/runtime.h>
#import "SensorsAnalyticsSDK.h"

@implementation UITableView (CountData)

+ (void)load { 
    [UITableView sensorsdata_swizzleMethod:@selector(setDelegate:) withMethod:@selector(CountData_setDelegate:)];
}

/*
 *  UITableView的delegate對象是在程序運(yùn)行時(shí)設(shè)置的铭乾,其有可能是UItableView對象本身娃循,也有可能是UIviewController或者其他對象。因此需要給delegate對象動(dòng)態(tài)地添加需要交換的方法笛质,然后與原來的tableView:didSelectRowAtIndexPath:方法進(jìn)行交換妇押。
 */

- (void)CountData_setDelegate:(id<UITableViewDelegate>)delegate {
    [self CountData_setDelegate:delegate];
    [self CountData_swizzleDidSelectRowAtIndexPathMethodWithDelegate:delegate];
}


//添加交換方法
static void CountData_tableViewDidSelectRow(id object,SEL selector,UITableView *tableView,NSIndexPath *indexPath) {
    SEL destinationSelector = NSSelectorFromString(@"CountData_tableView:didSelectRowAtIndexPath:");
    //發(fā)送消息姓迅,調(diào)用原始的tableView:didSelectRowAtIndexPath:方法實(shí)現(xiàn)   
    ((void (*)(id,SEL,id,id))objc_msgSend)(object,destinationSelector,tableView,indexPath);   
    [[SensorsAnalyticsSDK sharedInstance]AppClickWithTableView:tableView didSelectRowAtIndexPath:indexPath properties:nil];
}

#pragma mark- 私有方法,負(fù)責(zé)給delegate對象添加一個(gè)方法并進(jìn)行交換
-(void)CountData_swizzleDidSelectRowAtIndexPathMethodWithDelegate:(id)delegate {
   //獲取delegate對象的類
    Class delegateClass = [delegate class];
    NSLog(@"獲取當(dāng)前對象的類型名字為---%@",NSStringFromClass([delegate class]));
    //方法名
    SEL sourceSelector = @selector(tableView:didSelectRowAtIndexPath:);
    //當(dāng)delegate對象中沒有實(shí)現(xiàn)方法tableView:didSelectRowAtIndexPath:,直接返回
    if (![delegate respondsToSelector:sourceSelector]) {
        NSLog(@"沒有實(shí)現(xiàn)tableView:didSelectRowAtIndexPath方法");
        return;
    }
    
    SEL destinationSelector = NSSelectorFromString(@"CountData_tableView:didSelectRowAtIndexPath:");
    //當(dāng)delegate對象已經(jīng)存在了CountData_tableView:didSelectRowAtIndexPath:潭袱,說明已經(jīng)交換锋恬,可以直接返回
    if ([delegate respondsToSelector:destinationSelector]) {
        return;
    }
    
    Method sourceMethod = class_getInstanceMethod(delegateClass, sourceSelector);
    const char *encoding = method_getTypeEncoding(sourceMethod);
    //當(dāng)類中已經(jīng)存在相同的方法時(shí),則會(huì)添加方法失敗彤悔。當(dāng)時(shí)前面已經(jīng)判斷過方法是否存在晕窑。因此卵佛,此處一定會(huì)添加成功
    if (!class_addMethod([delegate class], destinationSelector,(IMP)CountData_tableViewDidSelectRow, encoding)) {
        
        return;
    }
    
    //方法添加之后,進(jìn)行方法交換
    [delegateClass sensorsdata_swizzleMethod:sourceSelector withMethod:destinationSelector];
}

@end

方案二:動(dòng)態(tài)子類

初始思路
在運(yùn)行時(shí)截汪,給實(shí)現(xiàn)了UITableViewDelegate協(xié)議的- tableView:didSelectRow-AtIndexPath:方法的類創(chuàng)建一個(gè)子類,讓該子類的對象變成我們自己創(chuàng)建的子類的對象阳柔。同時(shí)舌剂,在創(chuàng)建的子類中動(dòng)態(tài)添加- tableView:didSelectRowAtIndexPath:方法暑椰。那么,當(dāng)用戶點(diǎn)擊UITableViewCell控件時(shí)避消,就會(huì)先運(yùn)行自己創(chuàng)建的子類中的- tableView:didSelectRow-AtIndexPath:方法角虫。我們在實(shí)現(xiàn)該方法的時(shí)候戳鹅,先調(diào)用delegate原來的方法實(shí)現(xiàn),再觸發(fā)$AppClick事件妇穴,即可實(shí)現(xiàn) UITableView控件$AppClick事件全埋點(diǎn)。

創(chuàng)建一個(gè)動(dòng)態(tài)添加子類的工具類:TableViewDynamicDelegate

TableViewDynamicDelegate.h聲明如下:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface TableViewDynamicDelegate : NSObject

+ (void)proxyWithTableViewDelegate:(id <UITableViewDelegate>)delegate;

@end

NS_ASSUME_NONNULL_END

TableViewDynamicDelegate.m聲明如下:

#import "TableViewDynamicDelegate.h"
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#import "SensorsAnalyticsSDK.h"

/// Delegate 的子類前綴
static NSString *const kSensorsDelegatePrefix = @"cn.countData.";
// tableView:didSelectRowAtIndexPath: 方法指針類型
typedef void (*TableDidSelectImplementation)(id, SEL, UITableView *, NSIndexPath *);

@implementation TableViewDynamicDelegate

+ (void)proxyWithTableViewDelegate:(id <UITableViewDelegate>)delegate  {
   
    SEL originalSelector = NSSelectorFromString(@"tableView:didSelectRowAtIndexPath:");
    //當(dāng)Delegate中沒有實(shí)現(xiàn)tbaleView:didSelectRowAtIndexPath:方法時(shí)跑筝,直接返回
    if (![delegate respondsToSelector:originalSelector]) {
        NSLog(@"沒有實(shí)現(xiàn)tbaleView:didSelectRowAtIndexPath:方法");
        return;
    }
    //動(dòng)態(tài)創(chuàng)建一個(gè)新類
    Class originalClass =  object_getClass(delegate);
    NSString *originalClassName = NSStringFromClass(originalClass);
    
    //判斷這個(gè)delegate對象是否已經(jīng)動(dòng)態(tài)創(chuàng)建的類時(shí)曲梗,無須重復(fù)設(shè)置妓忍,直接返回
    if([originalClassName hasPrefix:kSensorsDelegatePrefix])  {
        return;
    }
    
    NSString *subClassName = [kSensorsDelegatePrefix stringByAppendingString:originalClassName];
    Class subclass = NSClassFromString(subClassName);
    if (!subclass) {
        //注冊一個(gè)新的子類,其父類為originalclass
        subclass = objc_allocateClassPair(originalClass, subClassName.UTF8String, 0);
        //獲取TableViewDynamicDelegate中的tableView:didSelectRowAtIndexPath指針
        Method method = class_getInstanceMethod(self, originalSelector);
        //獲取方法實(shí)現(xiàn)
        IMP methodIMP = method_getImplementation(method);
        //獲取方法類型的編碼
        const char *types = method_getTypeEncoding(method);
        //在subClass中添加 tableView:didSelectRowAtIndexPath: 方法
        if(!class_addMethod(subclass, originalSelector,methodIMP , types)) {
            NSLog(@"方法已經(jīng)存在");
        }
        
        /*刪除動(dòng)態(tài)生成的前綴定罢,動(dòng)態(tài)添加方法(sensorsdata_class)*/ 

        // 獲取 TableViewDynamicDelegate 中的 sensorsdata_class 方法指針
        Method classMethod = class_getInstanceMethod(self, @selector(sensorsdata_class));
        // 獲取方法實(shí)現(xiàn)
        IMP classIMP = method_getImplementation(classMethod);
        //獲取方法的類型編碼
        const char *classTypes = method_getTypeEncoding(classMethod);
        //在subclass中添加class方法
        if (!class_addMethod(subclass, @selector(class), classIMP, classTypes)) {
            NSLog(@"添加方法失敗");
        }
        //子類和原始類的大小必須一致祖凫,不能有更多的ivars或者屬性
        //如果不同會(huì)導(dǎo)致設(shè)置新的子類時(shí)惠况,會(huì)重新設(shè)置內(nèi)存粱年,導(dǎo)致重寫了對象的isa指針
        if (class_getInstanceSize(originalClass) != class_getInstanceSize(subclass)) {
            return;
        }
        //將delegate對象設(shè)置為新的子類對象
        objc_registerClassPair(subclass);
    }
    if (object_setClass(delegate, subclass)) {
        NSLog(@"創(chuàng)建成功");
    }
}

//刪除自動(dòng)創(chuàng)建類名的私有方法
- (Class)sensorsdata_class {
    // 獲取對象的類
    Class class = object_getClass(self);
    // 將類名前綴替換成空字符串,獲取原始類名
    NSString *className = [NSStringFromClass(class) stringByReplacingOccurrencesOfString:kSensorsDelegatePrefix withString:@""];
    // 通過字符串獲取類,并返回
    return objc_getClass([className UTF8String]);
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    //第一步:獲取原始的類
    Class cla = object_getClass(tableView.delegate);
    NSString *className = [NSStringFromClass(cla) stringByReplacingOccurrencesOfString:kSensorsDelegatePrefix withString:@""];
    Class originalClass = objc_getClass([className UTF8String]);
    
    //第二步:調(diào)用開發(fā)者自己實(shí)現(xiàn)的方法
    SEL originalSelector = NSSelectorFromString(@"tableView:didSelectRowAtIndexPath:");
    Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
    IMP originalIMP = method_getImplementation(originalMethod);
    if (originalIMP) {
       ((TableDidSelectImplementation)originalIMP)(tableView.delegate,originalSelector,tableView,indexPath);
    }
    
    //第三步:埋點(diǎn)
    [[SensorsAnalyticsSDK sharedInstance]AppClickWithTableView:tableView didSelectRowAtIndexPath:indexPath properties:@{@"$app_click":@"動(dòng)態(tài)創(chuàng)建類事件"}];
    
}

最后調(diào)用:修改UITableView+CountData.m文件中的-CountData_setDelegate:方法拉队,添加調(diào)用TableViewDynamicDelegate類的+proxyWithTableViewDelegate方法`

+ (void)load {   
    [UITableView sensorsdata_swizzleMethod:@selector(setDelegate:) withMethod:@selector(CountData_setDelegate:)];
}

- (void)CountData_setDelegate:(id<UITableViewDelegate>)delegate {
//    方案2 動(dòng)態(tài)子類   
    [self CountData_setDelegate:delegate];
    //設(shè)置delegate的動(dòng)態(tài)子類
    [TableViewDynamicDelegate proxyWithTableViewDelegate:delegate];    
}

方案三:消息轉(zhuǎn)發(fā)

在iOS應(yīng)用開發(fā)中粱快,自定義類一般需要繼承自NSObject類或者NSObject 子類叔扼。但是,NSProxy類不是繼承自NSObject類或者NSObject子類鳍咱,而是一 個(gè)實(shí)現(xiàn)了NSObject協(xié)議的抽象基類谤辜。

當(dāng)然,在大部分情況下丑念,使用NSObject類也可以實(shí)現(xiàn)消息轉(zhuǎn)發(fā),實(shí)現(xiàn) 方式與NSProxy類相同渔彰。但是推正,大部分情況下使用NSProxy類更為合適。

理由如下

  1. NSProxy類實(shí)現(xiàn)了包括NSObject協(xié)議在內(nèi)基類所需的基礎(chǔ)方法乳丰。
  2. 通過NSObject類實(shí)現(xiàn)的代理類不會(huì)自動(dòng)轉(zhuǎn)發(fā)NSObject協(xié)議中的方 法内贮。
  3. 通過NSObject類實(shí)現(xiàn)的代理類不會(huì)自動(dòng)轉(zhuǎn)發(fā)NSObject類別中的方 法,例如上面調(diào)用實(shí)例中的-valueForKey:方法什燕,如果是使用NSObject類實(shí) 現(xiàn)的代理類竞端,會(huì)拋出異常。

步驟如下:

步驟一:創(chuàng)建CountDataDelegateProxy類 (繼承自NSProxy類)技俐,實(shí)現(xiàn)UITableViewDelegate協(xié)議雕擂。然后添加一個(gè)類 方法+proxywithTableViewDelegate:贱勃。

CountDataDelegateProxy.h 聲明如下:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface CountDataDelegateProxy : NSProxy

@property(nonatomic,weak) id delegate;
+(instancetype) proxywithTableViewDelegate:(id<UITableViewDelegate>)delegate;

@end

NS_ASSUME_NONNULL_END

CountDataDelegateProxy.m 聲明如下:

#import "CountDataDelegateProxy.h"
#import "SensorsAnalyticsSDK.h"
@implementation CountDataDelegateProxy

+ (instancetype)proxywithTableViewDelegate:(id<UITableViewDelegate>)delegate {
    CountDataDelegateProxy *proxy = [CountDataDelegateProxy alloc];
    proxy.delegate = delegate;
    return  proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    //返回delegate對象的方法簽名
    return [(NSObject *)self.delegate methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {    
    [invocation invokeWithTarget:self.delegate];
    if (invocation.selector == @selector(tableView:didSelectRowAtIndexPath:)) {
        invocation.selector = NSSelectorFromString(@"countDatatableView:didSelectRowAtIndexPath:");
        [invocation invokeWithTarget:self];
    }
}

-(void)countDatatableView:(UITableView *)tableView didSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
    
    [[SensorsAnalyticsSDK sharedInstance]AppClickWithTableView:tableView didSelectRowAtIndexPath:indexPath properties:@{@"$app_click":@"NSProxy的委托代理"}];
}

@end

步驟二:為了可以同時(shí)支持UICollectionView控件贵扰,我們直接在UIScrollView中擴(kuò)展countData_delegareProxy屬性。

創(chuàng)建UIScrollView的類別CountData,并在頭文件中添加屬性聲明纹坐。

CountDataDelegateProxy.h 聲明如下:

#import <UIKit/UIKit.h>
#import "CountDataDelegateProxy.h"

NS_ASSUME_NONNULL_BEGIN

@interface UIScrollView (CountData)

@property (nonatomic,strong) CountDataDelegateProxy *countData_delegareProxy;

@end

NS_ASSUME_NONNULL_END

UIScrollView+CountData.m聲明如下:

#import "UIScrollView+CountData.h"
#import <objc/runtime.h>

@implementation UIScrollView (CountData)

- (void)setCountData_delegareProxy:(CountDataDelegateProxy *)countData_delegareProxy {
    objc_setAssociatedObject(self, @selector(setCountData_delegareProxy:), countData_delegareProxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (CountDataDelegateProxy *)countData_delegareProxy {
    return objc_getAssociatedObject(self, @selector(countData_delegareProxy));
}
@end

步驟三:修改UITableView+CountData.m文件中的-CountData_setDelegate:方法恰画,添加調(diào)用TableViewDynamicDelegate類的+proxyWithTableViewDelegate方法`

- (void)CountData_setDelegate:(id<UITableViewDelegate>)delegate {

    /*方案3 NSProxy 消息轉(zhuǎn)發(fā)*/

    self.countData_delegareProxy = nil;
    if (delegate) {
        CountDataDelegateProxy *proxy = [CountDataDelegateProxy proxywithTableViewDelegate:delegate];
        self.countData_delegareProxy = proxy;
        [self CountData_setDelegate:proxy];
    }else {
        [self CountData_setDelegate:nil];
    }
}

總結(jié)

對于UITableView控件$AppClick事件全埋點(diǎn)的三種方案,它們各有優(yōu)缺點(diǎn)跨晴,讀者可以根據(jù)實(shí) 際情況選擇相應(yīng)的方案片林。

方案一:方法交換

優(yōu)點(diǎn):簡單、易理解焕妙;Method Swizzling屬于 成熟技術(shù)弓摘,性能相對來說較高。

缺點(diǎn):對原始類有入侵末患,容易造成沖突锤窑。

方案二:動(dòng)態(tài)子類

優(yōu)點(diǎn):沒有對原始類入侵,不會(huì)修改原始類 的方法渊啰,不會(huì)和第三方庫沖突,是一種比較穩(wěn)定的方案隧膏。

缺點(diǎn):動(dòng)態(tài)創(chuàng)建子類對性能和內(nèi)存有比較大 的消耗嚷那。

方案三:消息轉(zhuǎn)發(fā)

優(yōu)點(diǎn):充分利用消息轉(zhuǎn)發(fā)機(jī)制车酣,對消息進(jìn)行 攔截索绪,性能較好。

缺點(diǎn):容易與一些同樣使用消息轉(zhuǎn)發(fā)進(jìn)行攔 截的第三方庫沖突

擴(kuò)展

獲取控件內(nèi)容

為了能獲取更復(fù)雜的UIView的顯示內(nèi)容娘摔,該方法需要修改成支持通過 遞歸遍歷獲取子控件的顯示內(nèi)容唤反。

定義UIView的分類鸭津,布局頁面等用到的控件的分類

UIView+TextContentData.h 聲明如下:

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIView (TextContentData)

@property (nonatomic,copy,readonly) NSString *elementContent;
@property (nonatomic,strong,readonly) UIViewController *myViewController;

@end

@interface UIButton (TextContentData)

@end

@interface UISwitch (TextContentData)

@end

@interface UILabel (TextContentData)

@end

NS_ASSUME_NONNULL_END

UIView+TextContentData.m 聲明如下:

#import "UIView+TextContentData.h"

@implementation UIView (TextContentData)

- (NSString *)elementContent {
    // 如果是隱藏控件逆趋,不獲取控件內(nèi)容
    if (self.isHidden || self.alpha == 0) { return nil; }
    // 初始化數(shù)組闻书,用于保存子控件的內(nèi)容
    NSMutableArray *contents = [NSMutableArray array];
    for (UIView *view in self.subviews) {
        // 獲取子控件的內(nèi)容
        // 如果子類有內(nèi)容脑慧,例如UILabel的text,獲取到的就是text屬性
        // 如果子類沒有內(nèi)容坑律,就遞歸調(diào)用該方法囊骤,獲取其子控件的內(nèi)容
        NSString *content = view.elementContent;
        if (content.length > 0) {
            // 當(dāng)該子控件有內(nèi)容時(shí),保存在數(shù)組中
            [contents addObject:content];
        }
    }
    // 當(dāng)未獲取到子控件內(nèi)容時(shí)藕各,返回nil焦除。如果獲取到多個(gè)子控件內(nèi)容時(shí),使用"-"拼接
    return contents.count == 0 ? nil : [contents componentsJoinedByString:@"-"];
}

- (UIViewController *)myViewController {
    UIResponder *responder = self;
    while ((responder = [responder nextResponder])) {
        if ([responder isKindOfClass:[UIViewController class]]) {
            return (UIViewController *)responder;
        }
    }
    return  nil;
}
@end

@implementation  UIButton (TextContentData)

- (NSString *)elementContent {
    return self.titleLabel.text ?: super.elementContent;
}

@end


@implementation UISwitch (TextContentData)

- (NSString *)elementContent {
    return self.on ? @"checked":@"unchecked";
}

@end

@implementation UILabel (TextContentData)

- (NSString *)elementContent {
    
    return self.text ?: super.elementContent;
}

@end

最后頁面采集信息增加字段$element_content乌逐。

代碼如下所示:

-(void)AppClickWithTableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)index properties:(NSDictionary<NSString *,id> *)properties  {
    
    NSMutableDictionary *event = [NSMutableDictionary dictionary];
    // 設(shè)置事件名稱
    event[@"event"] = @"TableView的點(diǎn)擊事件";
    // 設(shè)置事件發(fā)生的時(shí)間戳浙踢,單位為毫秒
    event[@"time"] = [NSNumber numberWithLong:NSDate.date.timeIntervalSince1970 * 1000];
    NSMutableDictionary *eventProperties = [NSMutableDictionary dictionary];
    // 添加預(yù)置屬性
    [eventProperties addEntriesFromDictionary:self.automaticProperties];
    // 添加自定義屬性
    [eventProperties addEntriesFromDictionary:properties];
    //判斷是否位被動(dòng)啟動(dòng)狀態(tài)
    if(self.isLaunchedPassively) {
        //添加應(yīng)用程序狀態(tài)屬性
        eventProperties[@"$app_state"] = @"background";
    }
    
    if (tableView) {
        // TODO:獲取用戶點(diǎn)擊的UITableViewCell控件對象
        UITableViewCell *cell = [tableView cellForRowAtIndexPath:index];
        // TODO:設(shè)置被用戶點(diǎn)擊的UITableViewCell控件上的內(nèi)容($element_content)
        eventProperties[@"$element_content"] = cell.elementContent;
    }
    // 設(shè)置事件屬性
    event[@"properties"] = eventProperties;
   
    [self printEvent:event];
}

最后支持UICollectionView控件和UITableView的實(shí)現(xiàn)原理相似洛波,同樣可以使用以上三種方案去實(shí)現(xiàn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蹬挤,一起剝皮案震驚了整個(gè)濱河市焰扳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扫茅,老刑警劉巖育瓜,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異停蕉,居然都是意外死亡钙态,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門蚓挤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來驻子,“玉大人,你說我怎么就攤上這事缤剧∮蚩叮” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵抵窒,是天一觀的道長叠骑。 經(jīng)常有香客問我,道長掉房,這世上最難降的妖魔是什么慰丛? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上富寿,老公的妹妹穿的比我還像新娘锣夹。我一直安慰自己银萍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布贴唇。 她就那樣靜靜地躺著戳气,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瓶您。 梳的紋絲不亂的頭發(fā)上纲仍,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天郑叠,我揣著相機(jī)與錄音,去河邊找鬼乡革。 笑死,一個(gè)胖子當(dāng)著我的面吹牛婉宰,可吹牛的內(nèi)容都是我干的推穷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蟹腾,長吁一口氣:“原來是場噩夢啊……” “哼区宇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起议谷,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赴捞,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赦政,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡恢着,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年财破,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碗淌。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抖锥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出纳像,到底是詐尸還是另有隱情,我是刑警寧澤竟趾,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布岔帽,位于F島的核電站,受9級特大地震影響犀勒,放射性物質(zhì)發(fā)生泄漏妥曲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一褂萧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧导犹,春花似錦、人聲如沸锡足。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爽蝴。三九已至,卻和暖如春蝎亚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背躺彬。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工梅惯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铣减。 一個(gè)月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓葫哗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親劣针。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354