Aspects 源码解析

简介

Aspects是一个轻量的面向切面编程(AOP)的第三方库,面向切面编程简单来说,就是在原来的业务流程之中的某个位置插入额外的功能,不影响之前的逻辑。举个栗子来说,比如项目的统计埋点,统计页面进入的次数,我们一般是hook@selector(viewWillAppear:)方法,然后加入埋点上报,这就相当于我们在viewWillAppear中插入了一个切面来附加额外的功能。

Aspects

先来看下头文件,头文件中只有两个方法

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                       withOptions:(AspectOptions)options
                        usingBlock:(id)block
                             error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;

+方法表示hook对类的所有实例对象生效(注意不是hook类方法),-方法表示对类的单个实例对象生效。

进入.m文件查看实现,两个方法都是调用了同一个c函数aspect_add();

static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
NSCParameterAssert(self);
NSCParameterAssert(selector);
NSCParameterAssert(block);

__block AspectIdentifier *identifier = nil;
aspect_performLocked(^{
        if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                [aspectContainer addAspect:identifier withOptions:options];
                
                aspect_prepareClassAndHookSelector(self, selector, error);
            }
        }
    });
    return identifier;
}   

aspect_performLocked函数是给block函数的执行进行加锁处理,aspect_isSelectorAllowedAndTrack()判断是否允许hook该方法。

aspect_isSelectorAllowedAndTrack

static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) {
    static NSSet *disallowedSelectorList;
    static dispatch_once_t pred;
    dispatch_once(&pred, ^{
        //设置hook黑名单
        disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
    });

    //检查黑名单,retain,release,autorelease,forwardInvocation方法不允许hook
    NSString *selectorName = NSStringFromSelector(selector);
    if ([disallowedSelectorList containsObject:selectorName]) {
        NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
        AspectError(AspectErrorSelectorBlacklisted, errorDescription);
        return NO;
    }

    //针对dealloc方法的hook有额外要求,插入的block代码只能选择在dealloc之前执行
    AspectOptions position = options&AspectPositionFilter;
    if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
        NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";
        AspectError(AspectErrorSelectorDeallocPosition, errorDesc);
        return NO;
    }
    //从![self.class instancesRespondToSelector:selector]可以看出是不能hook类方法的
    if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) {
        NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName];
        AspectError(AspectErrorDoesNotRespondToSelector, errorDesc);
        return NO;
    }

    //判断是否是元类,也即是判断self是类还是对象。若self是类(如UIViewController),进入if分支;若self是对象,则直接renturn YES。
    if (class_isMetaClass(object_getClass(self))) {
        Class klass = [self class];
        //全局字典,Class为key,value是AspectTracker对象
        NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
        Class currentClass = [self class];

        AspectTracker *tracker = swizzledClassesDict[currentClass];
        //判断子类是否已经hook了该方法,整个继承链上同一个方法只能hook一次
        if ([tracker subclassHasHookedSelectorName:selectorName]) {
            NSSet *subclassTracker = [tracker subclassTrackersHookingSelectorName:selectorName];
            NSSet *subclassNames = [subclassTracker valueForKey:@"trackedClassName"];
            NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked subclasses: %@. A method can only be hooked once per class hierarchy.", selectorName, subclassNames];
            AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
            return NO;
        }
        
        //这一步do-while作用与上面的if相同,都是为了保证一个继承链上只能有一次hook,但是两个的场景不同。上面的if判断针对的场景是:先hook子类的方法,再hook父类的同一方法,会从上面的if剔除父类的hook。do-while的场景是:先hook父类的方法,再hook子类的方法,会从这一步剔除子类的hook。注意:针对同一个类的方法hook多次是可以的
        do {
            tracker = swizzledClassesDict[currentClass];
            if ([tracker.selectorNames containsObject:selectorName]) {
                if (klass == currentClass) {
                    return YES;//可以hook同一个类的方法多次
                }
                NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(currentClass)];
                AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
                return NO;
            }
        } while ((currentClass = class_getSuperclass(currentClass)));

        currentClass = klass;
        AspectTracker *subclassTracker = nil;
        //这一步的do-while是保存selector并且遍历父类,将所有的类都进行hook标记,保证整个继承链针对一个方法只能hook一次
        do {
            tracker = swizzledClassesDict[currentClass];
            if (!tracker) {
                tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass];
                swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
            }
            if (subclassTracker) {
                [tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName];
            } else {
                [tracker.selectorNames addObject:selectorName];
            }
            subclassTracker = tracker;
        }while ((currentClass = class_getSuperclass(currentClass)));
    } else {
        //这里是对某个对象的hook,直接return YES
        return YES;
    }

    return YES;
}

额外补充一点,区分[self class]和object_getClass(self)的不同。如果self是对象,则这两个返回值一样,即self的对应的类;如果self是类,[self class]返回的还是这个类本身也就是self,而object_getClass(self)返回的是它的元类。

回到aspect_add()函数,继续往下看。

if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
        AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
        identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
    if (identifier) {
        [aspectContainer addAspect:identifier withOptions:options];

        // Modify the class to allow message interception.
        aspect_prepareClassAndHookSelector(self, selector, error);
    }
}

AspectsContainer是一个容器类,AspectIdentifier类用来整合hook信息的,比如hook的selector,传递进来的block,hook的对象等,然后将identifier保存在aspectContainer中,根据我们传递的参数options,存放在内部不同的数组中。这里重点注意这个方法

+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {
    NSCParameterAssert(block);
    NSCParameterAssert(selector);
    NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); //生成block的签名
    if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
        return nil;
    }

    AspectIdentifier *identifier = nil;
    if (blockSignature) {
        identifier = [AspectIdentifier new];
        identifier.selector = selector;
        identifier.block = block;
        identifier.blockSignature = blockSignature;
        identifier.options = options;
        identifier.object = object; // weak
    }
    return identifier;
}

aspect_blockMethodSignature()将我们传递进来的block生成方法签名。

aspect_blockMethodSignature

Aspects仿照OC自己定义了block,如下:

typedef struct _AspectBlock {
    __unused Class isa;
    AspectBlockFlags flags;
    __unused int reserved;
    void (__unused *invoke)(struct _AspectBlock *block, ...);
    struct {
        unsigned long int reserved;
        unsigned long int size;
        // requires AspectBlockFlagsHasCopyDisposeHelpers
        void (*copy)(void *dst, const void *src);
        void (*dispose)(const void *);
        // requires AspectBlockFlagsHasSignature
        const char *signature;
        const char *layout;
    } *descriptor;
    // imported variables
} *AspectBlockRef;

若想看系统中的block结构可以通过clang编译源文件,具体步骤可以看我之前的文章。可以看到结构体中有个变量const char *signature,这个就是block的签名串。

static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
    AspectBlockRef layout = (__bridge void *)block;
    if (!(layout->flags & AspectBlockFlagsHasSignature)) {
        NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
        AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }
    void *desc = layout->descriptor;
    desc += 2 * sizeof(unsigned long int);
    if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
        desc += 2 * sizeof(void *);
    }
    if (!desc) {
        NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
        AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }
    const char *signature = (*(const char **)desc);
    return [NSMethodSignature signatureWithObjCTypes:signature];
}

上面的代码主要就是拿到signature,主要思路是从block中的descriptor地址偏移字节取到signature。
生成block签名之后还要检测签名的完整性,主要是检测block的返回值,参数跟hook的方法的返回值和参数保持一致。

static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
    NSCParameterAssert(blockSignature);
    NSCParameterAssert(object);
    NSCParameterAssert(selector);

    BOOL signaturesMatch = YES;
    //对入参的selector生成方法签名
    NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
    
    //判断block和selector的参数,block的参数个数可以小于等于selector的参数个数
    if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
        signaturesMatch = NO;
    }else {
        if (blockSignature.numberOfArguments > 1) {
            const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
            if (blockType[0] != '@') {
                signaturesMatch = NO;
            }
        }
        //从第三个参数开始比对block和selector的参数是否一致
        if (signaturesMatch) {
            for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) {
                const char *methodType = [methodSignature getArgumentTypeAtIndex:idx];
                const char *blockType = [blockSignature getArgumentTypeAtIndex:idx];
                // Only compare parameter, not the optional type data.
                if (!methodType || !blockType || methodType[0] != blockType[0]) {
                    signaturesMatch = NO; break;
                }
            }
        }
    }

    if (!signaturesMatch) {
        NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature];
        AspectError(AspectErrorIncompatibleBlockSignature, description);
        return NO;
    }
    return YES;
}

oc中的方法调用底层都是通过objc_msgSend()方法调用,这个方法默认传递两个参数,一个是调用的对象object(self),还有一个是@selector,任何一个OC的方法都有这两个参数,所以block和selector的比较从index = 2的参数开始。比较参数比较的是参数的类型,如int类型就是i,SEL类型是:,OC对象是@,block是@?,更多的类型可以看苹果官方文档中关于typeEncoding的介绍。另外关于block的签名只有一个默认参数,第一个参数(index=0)是block本身,第二个参数Aspects约定是id<AspectInfo>类型的参数(为了和selector的签名参数保持一致),所以上面判断了block中第二个参数是否是id类型。

接下来aspect_prepareClassAndHookSelector()方法就是Aspects核心代码了。

核心代码aspect_prepareClassAndHookSelector

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
    NSCParameterAssert(selector);
    Class klass = aspect_hookClass(self, error);
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
        // Make a method alias for the existing method implementation, it not already copied.
        const char *typeEncoding = method_getTypeEncoding(targetMethod);
        SEL aliasSelector = aspect_aliasForSelector(selector);
        if (![klass instancesRespondToSelector:aliasSelector]) {
            __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
            NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
        }

        // We use forwardInvocation to hook in.
        class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
        AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
    }
}

aspect_hookClass()函数返回要hook类,针对类和对象的hook返回值不同。

static Class aspect_hookClass(NSObject *self, NSError **error) {
    NSCParameterAssert(self);
    Class statedClass = self.class;
    Class baseClass = object_getClass(self);
    NSString *className = NSStringFromClass(baseClass);

    //判断类是否是_Aspects_前缀开头,对单个实例对象hook,会生成以_Aspects_为前缀中间类
    if ([className hasSuffix:AspectsSubclassSuffix]) {
        return baseClass;

    //表明这里是hook类而不是单个对象
    }else if (class_isMetaClass(baseClass)) {
        return aspect_swizzleClassInPlace((Class)self);
    
    //[self classs]和object_getClass(self)不相等,表明可能是已经KVO过的实例对象,KVO的实现也是生成一个中间类,改变对象的isa指针指向这个中间类
    }else if (statedClass != baseClass) {
        return aspect_swizzleClassInPlace(baseClass);
    }

    //进入这里表明self是一个实例对象,并没之前没有hook任何方法
    //生成一个以_Aspects_前缀的类名
    const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
    //获取类对象,如果为空,则通过runtime动态生成子类继承
    Class subclass = objc_getClass(subclassName);

    if (subclass == nil) {
        //runtime创建子类
        subclass = objc_allocateClassPair(baseClass, subclassName, 0);
        if (subclass == nil) {
            NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
            AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
            return nil;
        }
        
        aspect_swizzleForwardInvocation(subclass);
        //更改_Aspects_子类的- (Class)class;方法的返回值,返回的是原来的类(即_Aspects_的父类)
        aspect_hookedGetClass(subclass, statedClass);
        //同上,更改_Aspects_子类的元类为原来的类的元类
        aspect_hookedGetClass(object_getClass(subclass), statedClass);
        objc_registerClassPair(subclass);
    }
    //将self的isa指针指向_Aspects_开头的子类
    object_setClass(self, subclass);
    return subclass;
}

从上可以看到,如果是hook类,直接返回这个类;如果是hook对象,会动态生成_Aspects_前缀的中间类并将对象的isa设置为这个中间类(类似KVO),如果对象是已经KVO过的,则直接返回KVO生成的中间类。拿到要hook的类之后,就要将原始方法IMP实现进行替换了,替换是在aspect_swizzleForwardInvocation()函数进行。

static void aspect_swizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);

    IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
    if (originalImplementation) {
        class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
    }
    AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}

class_replaceMethod()将forwardInvocation的实现替换成__ASPECTS_ARE_BEING_CALLED__()函数的实现,如果替换成功(说明子类重写了该方法)会返回替换之前的IMP,然后为该类添加一个@selector(__aspects_forwardInvocation:)方法。我们知道forwardInvocation方法是runtime消息转发机制中的最后一步转发,Aspects是如何将消息转发到这的呢?继续往下看就知道了。
回到aspect_prepareClassAndHookSelector()函数,接来下通过aspect_isMsgForwardIMP()判断原始selector的IMP是否是_objc_msgForward或者_objc_msgForward_stret,如果不是就将selector的IMP替换成_objc_msgForward或者_objc_msgForward_stret。另外通过runtime添加一个了aspects_为前缀的方法,IMP为原始selector的IMP。对于_objc_msgForward()这个函数,它是直接触发消息转发的,这里就对应了前面的替换@selector(forwardInvocation:)IMP的用意。当我们给hook的selector发送消息时,先执行_objc_msgForward函数,然后触发消息转发来到forwardInvocation:。

为什么这里分为_objc_msgForward或者_objc_msgForward_stret,原因是函数的返回值是存放在寄存器中,寄存器的大小有限,当返回值是某些很大的结构体的时候,寄存器存放不下,就会改成另外一种方式。申请一段内存,把指针保存在寄存器上,返回值往这个指针指向的内存写数据,这时就是用_objc_msgForward_stret方法了。

最后,我们只剩下__ASPECTS_ARE_BEING_CALLED__()函数没有分析了。在前面的操作中有将forwardInvocation方法的IMP替换成这个函数,所以hook之后的消息最终会转发到这个函数里面执行,下面看看这个函数做了哪些事情。

__ASPECTS_ARE_BEING_CALLED__

static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
    NSCParameterAssert(self);
    NSCParameterAssert(invocation);
    //原始selector
    SEL originalSelector = invocation.selector;
    //aspects_前缀的selector
    SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
    //替换为aspects_前缀的selector,alisaSelector的IMP是原始selector的IMP,也就是表明[invocation invoke]时调用了原始方法
    invocation.selector = aliasSelector;
    AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
    AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
    AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
    NSArray *aspectsToRemove = nil;

    //这里执行我们入参时传递的block,对应的option是AspectPositionBefore,表明block执行时机在原始方法之前
    aspect_invoke(classContainer.beforeAspects, info);
    aspect_invoke(objectContainer.beforeAspects, info);

    //这里对应的option是AspectPositionInstead,表明用传递的block替换原来的方法
    BOOL respondsToAlias = YES;
    if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
        //这里表明替换原始方法
        aspect_invoke(classContainer.insteadAspects, info);
        aspect_invoke(objectContainer.insteadAspects, info);
    }else {
        Class klass = object_getClass(invocation.target);
        do {
            if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
                //这里表明执行原始方法
                [invocation invoke];
                break;
            }
        }while (!respondsToAlias && (klass = class_getSuperclass(klass)));
    }

    //这里对应的option是AspectPositionAfter,表明block的执行时机在原始方法之后
    aspect_invoke(classContainer.afterAspects, info);
    aspect_invoke(objectContainer.afterAspects, info);


    //如果该方法没有被hook,直接执行原始方法
    if (!respondsToAlias) {
        invocation.selector = originalSelector;
        SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
        if ([self respondsToSelector:originalForwardInvocationSEL]) {
            ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
        }else {
            [self doesNotRecognizeSelector:invocation.selector];
        }
    }

    [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}

主要根据入参时的option来插入block的执行时机,至此Aspects实现思路大致已经解析完毕。最后为了有个更清晰的理解,来个简单的总结。

总结

针对单个实例对象hook(.h文件中的减方法),生成一个动态子类(_Aspects_为后缀),以类似kvo的方式生成一个中间类, 设置hook对象的isa为这个中间类,并且重写中间类的class方法,返回原始类。然后对于要hook的@selector,更改其IMP为_objc
_Msgforward / _objc_Msgforward_stret,某些情况当@selector的返回值是结构体的时候通过_objc_Msgforward_stret来触发消息转发。更改中间类的@selector(forwardInvocation:)的IMP为__ASPECTS_ARE_BEING_CALLED__,给中间类添加一个以aspects__为前缀的方法,IMP为原始@selector的IMP。

针对类的所有对象都生效的hook(.h中的加方法),是直接对该类进行处理,不会生成中间类,处理过程同上。

消息触发的流程为:原始@selector -> _objc
_Msgforward / _objc_Msgforward_stret -> 触发消息转发@selector(forwardInvocation:) -> __ASPECTS_ARE_BEING_CALLED__(函数内部执行以aspects__为前缀的方法,也就是原始的@selecotr的IMP,根据传入的flag参数,确定插入的代码执行的时机)

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

推荐阅读更多精彩内容