Objective-C Runtime(二): 實(shí)踐 監(jiān)測(cè)與防護(hù)iOS Crash

上篇文章 介紹了一些runtime的基礎(chǔ)知識(shí), 這次分享一些runtime的各種黑科技玩法: 消息轉(zhuǎn)發(fā)截獲, isa-swizzling, method swizzling, associated object等等. 順便研究了野指針的問(wèn)題, 以及如何寫一個(gè)僵尸對(duì)象(Zombie).

Unrecognized Selector

消息轉(zhuǎn)發(fā)截獲

這個(gè)簡(jiǎn)單了, 首先來(lái)張圖:

objc_runtime_msgSend.jpeg

當(dāng)向?qū)ο蟀l(fā)送消息, 沿著類的繼承鏈找不到響應(yīng)的方法時(shí), runtime的消息轉(zhuǎn)發(fā)機(jī)制會(huì)依次調(diào)用這幾個(gè)方法. 這里選擇第二個(gè)forwardingTargetForSelector來(lái)操作. 該方法返回一個(gè)對(duì)象, 該對(duì)象為消息新的接受者.

這里我們選擇了第二步forwardingTargetForSelector來(lái)做文章。原因如下:

  • resolveInstanceMethod 需要在類的本身上動(dòng)態(tài)添加它本身不存在的方法稳诚,這些方法對(duì)于該類本身來(lái)說(shuō)冗余的
  • forwardInvocation可以通過(guò)NSInvocation的形式將消息轉(zhuǎn)發(fā)給多個(gè)對(duì)象舟陆,但是其開(kāi)銷較大理肺,需要?jiǎng)?chuàng)建新的NSInvocation對(duì)象胁后,并且- forwardInvocation的函數(shù)經(jīng)常被使用者調(diào)用炉菲,來(lái)做多層消息轉(zhuǎn)發(fā)選擇機(jī)制疲陕,不適合多次重寫
  • forwardingTargetForSelector可以將消息轉(zhuǎn)發(fā)給一個(gè)對(duì)象鞠值,開(kāi)銷較小,并且被重寫的概率較低竞膳,適合重寫

好了. 代碼:

id forwardingTargetForSelector(id self, SEL _cmd, SEL aSelector) {
    NSLog(@"Unrecognized selector %@ sent to %@, ***forwarding to Stub", NSStringFromSelector(aSelector), self);
    StubProxy *stub = [[StubProxy alloc] init];
    if (![stub respondsToSelector:aSelector]) {
        class_addMethod([stub class], aSelector, (IMP)someMethodIMP, "v@:");
    }
    return stub;
}

void someMethodIMP(id self, SEL _cmd) {
    NSLog(@"*** someMethodIMP prevent the crash. *** ");
}

這里方法寫成了C語(yǔ)言的格式, 其實(shí)是一樣的. 所有實(shí)例方法都隱含了self_cmd參數(shù), 最終OC形式的方法也會(huì)轉(zhuǎn)化成類似形式. 如寫成OC形式的方法, 可以調(diào)用runtime的class_getInstanceMethodmethod_getImplementation轉(zhuǎn)化為IMPclass_addMethod使用. 一樣道理~

StubProxy是一個(gè)樁類, 可認(rèn)為它僅是一個(gè)空的模板, 也可以不在代碼中定義類, 直接使用runtime的objc_allocateClassPairobjc_registerClassPair函數(shù)去動(dòng)態(tài)創(chuàng)建并注冊(cè)類, 只需把

StubProxy *stub = [[StubProxy alloc] init];

換成

Class StubProxy = objc_allocateClassPair([NSObject class], "StubProxy", 0);
objc_registerClassPair(StubProxy);
class_addMethod(StubProxy, aSelector, (IMP)someMethodIMP, "v@:");
id stub = [[StubProxy alloc] init];

然后再APP開(kāi)始運(yùn)行的地方(如AppDeleage的didFinishLaunchingWithOptions回調(diào))加上代碼:

//get target class
id targetClass = objc_getClass("MyViewController");

//override the forwardingTargetForSelector method of NSObject
class_addMethod([targetClass class], @selector(forwardingTargetForSelector:), (IMP)forwardingTargetForSelector, "@@:@");

這里"MyViewController"是一個(gè)你需要加上unrecognized selector 崩潰防護(hù)的類, 這里使用class_addMethod函數(shù)動(dòng)態(tài)為該類添加上forwardingTargetForSelector方法, 該方法把無(wú)法識(shí)別的selector消息轉(zhuǎn)發(fā)至一個(gè)Stub類的對(duì)象, 該對(duì)象為這個(gè)selector動(dòng)態(tài)添加一個(gè)函數(shù)實(shí)現(xiàn), 這個(gè)函數(shù)怎么實(shí)現(xiàn)就自定義了, 可以為空, 返回0, 或者打印個(gè)日志, 隨你所好. 該函數(shù)對(duì)應(yīng)Demo里的void someMethodIMP().

由此, 當(dāng)出現(xiàn)Unrecognized selector時(shí), 原本的Crash

CrashCrusher[65488:28134311] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', 
  reason:-[MyViewController someMethod]: unrecognized selector sent to instance 0x7fd81a50db20'
  ...(call stack)
libc++abi.dylib: terminating with uncaught exception of type NSException

將變成了友好的

CrashCrusher[65353:28112308] Unrecognized selector someMethod sent to <MyViewController: 0x7fd4ece0f390>, ***forwarding to Stub
CrashCrusher[65353:28112308] *** someMethodIMP prevent the crash. *** 

順著這種思路, 可以封裝一下API, 接受一個(gè)NSString類型的類名, 對(duì)相應(yīng)的類進(jìn)行unrecognized selector的crash防護(hù). 這里僅作簡(jiǎn)單的Demo, 就不封裝了.

Zombie

當(dāng)遇到野指針訪問(wèn)不恰當(dāng)內(nèi)存時(shí)航瞭,系統(tǒng)發(fā)送SIGSEGV信號(hào),出現(xiàn)EXC_BAD_ACCESS錯(cuò)誤而崩潰坦辟。

Xcode在Debug模式下可開(kāi)啟NSZombieEnabled刊侯,當(dāng)對(duì)象被釋放時(shí),runtime系統(tǒng)通過(guò)isa-swizzling把該對(duì)象替換成一個(gè)Zombie對(duì)象锉走,當(dāng)往該對(duì)象發(fā)送消息時(shí)滨彻,Zombie對(duì)象將輸出一個(gè)message sent to deallocated instance的log,隨后發(fā)送SIGKILL信號(hào)終止程序挪蹭。Log(Message from debugger: Terminated due to signal 9)亭饵。

可以看出在開(kāi)啟Zombie情況下,比起令人頭大的EXC_BAD_ACCESS野指針崩潰梁厉,Zombie給開(kāi)發(fā)者提供了更友好的“崩潰”方式辜羊,并且提供相關(guān)日志來(lái)追溯bug。

由于僵尸對(duì)象的存在導(dǎo)致內(nèi)存的過(guò)度消耗的問(wèn)題词顾,蘋果并不在Release模式下提供該功能八秃。

這并不能阻止我們自己去實(shí)現(xiàn)一個(gè)Zombie啊~lol

下面利用runtime寫一個(gè)自定義的zombie對(duì)象

首先,

isa-swizzling

什么是isa-swizzling? 先看下

typedef struct objc_object {
    Class isa;
} *id;

每個(gè)OC對(duì)象結(jié)構(gòu)里的第一項(xiàng), 就是一個(gè)名為isa的Class類型變量, Class為類對(duì)象結(jié)構(gòu)體的指針類型.

typedef struct objc_class {
    Class isa                             ;
    Class super_class                     ;
    const char *name                      ;
    long version                          ;
    long info                             ;
    long instance_size                    ;
    struct objc_ivar_list *ivars          ;
    struct objc_method_list **methodLists ;
    struct objc_cache *cache              ;
    struct objc_protocol_list *protocols  ;
} *Class;

對(duì)象的isa指針指向它的類對(duì)象.

從代碼的定義可以看出, Class類型也是id類型的一個(gè)特例. (認(rèn)識(shí)到這點(diǎn)很重要, 不要理所當(dāng)然得認(rèn)為Class就只是類類型, id就只是對(duì)象類型)

Class類型強(qiáng)制轉(zhuǎn)換為id類型將損失"精度"(或者說(shuō),可見(jiàn)度? 明白我意思就行??).

id類型里, 僅對(duì)變量isa可見(jiàn).

所謂isa-swizzling, 就是把一個(gè)對(duì)象的isa改為指向另外一個(gè)類!

可供操作的runtime方法是:

Class object_setClass(id obj, Class cls);

obj為被swizzled的對(duì)象, cls為新的isa值.

method swizzling

我們要在對(duì)象被回收時(shí)把它置換成另一個(gè)對(duì)象,想到了method swizzling掉NSObject的dealloc方法肉盹。

關(guān)于dealloc

當(dāng)對(duì)象的引用計(jì)數(shù)降為0時(shí), 系統(tǒng)向被釋放的對(duì)象發(fā)送-dealloc消息.
dealloc方法做了三件事:

    1. 調(diào)用objc_destructInstance()釋放對(duì)象的所有實(shí)例變量和關(guān)聯(lián)對(duì)象(該方法并未回收對(duì)象本身內(nèi)存).
    1. isa-swizzling將該對(duì)象的類置為一個(gè)空的類對(duì)象.
    1. 調(diào)用free()回收該對(duì)象的內(nèi)存.

它的最終代碼是這樣的

static id _object_dispose(id anObject) 
{
    if (anObject==nil) return nil;

    objc_destructInstance(anObject);
    
    anObject->initIsa(_objc_getFreedObjectClass ()); 

    free(anObject);
    return nil;
}

關(guān)于dealloc更詳細(xì)的分析可看大神的這篇文章.

我們的目的是把原對(duì)象isa-swizzle成一個(gè)Zombie對(duì)象, 這個(gè)Zombie仍保留于內(nèi)存中, 以監(jiān)測(cè)野指針. 所以用來(lái)swizzle的dealloc方法是這樣的:

- (void)my_dealloc {
    //after method swizzling, the `self` here refers to the Object to be dealloc-ed but not the CCZombie instance itself
    
    //if the class of object-to-be-dealloced is not enabled to be a zombie, call the original dealloc
    CCZombie *zombie = [CCZombie sharedZombie];
    if (![zombie->_classesThatEnablesZombie containsObject:[self class]]) {
        return [self my_dealloc];
    }
    
    //release all instance variables and associated objects the object references
    objc_destructInstance(self);
    
    //store the isa's original name
    NSString *originClassName = NSStringFromClass([self class]);
    objc_setAssociatedObject(self, OrigClassNameKey, originClassName, OBJC_ASSOCIATION_COPY_NONATOMIC);
    
    //isa-swizzling
    Class zombieClass = objc_getClass("CCZombie");
    object_setClass(self, zombieClass);
    
    //TODO: set a more customized class name, like CCZombie_<OrigClassName>?
    
    //TODO: implement a cache mechanism for the zombies
    
    //no free() called here
}

思路:

  • 一開(kāi)始先判斷對(duì)象是否加入了Zombie防護(hù)機(jī)制, 如果未加入, 則調(diào)用原始的dealloc方法. 如果是, 下一步;
  • 調(diào)用objc_destructInstance析構(gòu)對(duì)象;
  • 使用associated object函數(shù)把原類名存儲(chǔ)于對(duì)象中;
  • isa-swizzling把對(duì)象類設(shè)置為Zombie

可見(jiàn), 除了存儲(chǔ)類名以便后來(lái)的識(shí)別外, 這個(gè)自定義的dealloc方法與原來(lái)的dealloc不同之處則在于少了free()回收內(nèi)存的一步. (畢竟只是想把被釋放的對(duì)象變成僵尸嘛)

注意這里被isa-swizzled的dealloc是[NSObject dealloc], 因?yàn)槿魏蔚?code>dealloc調(diào)用最終都會(huì)調(diào)用到根類(即NSObject)的dealloc.

回想在MRC情況下, 所有重寫的dealloc最終都得寫上[super dealloc]; 而ARC下, 編譯器自動(dòng)插入了這一步.

回到method swizzling來(lái).
可定義一個(gè)開(kāi)啟zombie的方法:

- (void)enableZombie {
    if (!_isZombieEnabled) {
        //add the swizzled method to NSObject before swizzling, since CCZombie is not a category of NSObject
        Method myDeallocMethod = class_getInstanceMethod([self class], @selector(my_dealloc));
        BOOL result = class_addMethod([NSObject class], @selector(my_dealloc), method_getImplementation(myDeallocMethod), method_getTypeEncoding(myDeallocMethod));
        if (result) {
            //method swizzling in NSObject
            Method myDeallocMethod = class_getInstanceMethod([NSObject class], @selector(my_dealloc));
            Method origDeallocMethod = class_getInstanceMethod([NSObject class], @selector(dealloc));
            method_exchangeImplementations(origDeallocMethod, myDeallocMethod);
        }
    }
}

注意: 由于這里不是在method swizzling的常見(jiàn)場(chǎng)景Category中, 所以需要一開(kāi)始先把my_dealloc方法加入到NSObject類里, 然后再進(jìn)行swizzling. 否則, 被釋放的對(duì)象將會(huì)由于找不到my_deallocSEL而報(bào)錯(cuò).

再調(diào)用- enableZombie方法后開(kāi)啟Zombie機(jī)制后, 所有對(duì)象的dealloc方法都會(huì)最終跳到這個(gè)my_dealloc中來(lái); 在my_dealloc中在判斷對(duì)象是走原dealloc還是被置換后的dealloc; 被置換的dealloc最終不會(huì)調(diào)用free()釋放內(nèi)存; 由此實(shí)現(xiàn)Zombie.

CCZombie

CCZombie是一個(gè)自定義的僵尸類, 可設(shè)置一些開(kāi)啟僵死服務(wù)的接口:

@interface CCZombie : NSObject
+ (void)enableZombie;
+ (void)addClassToZombieService:(NSString *)className;
@end

static void* OrigClassNameKey = "OrigClassNameKey";
@implementation CCZombie {
    BOOL _isZombieEnabled;
    NSMutableArray<Class> *_classesThatEnablesZombie;
}

+ (instancetype)sharedZombie {
    static CCZombie* sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

- (instancetype)init {
    if (self = [super init]) {
        _isZombieEnabled = NO;
        _classesThatEnablesZombie = [[NSMutableArray alloc] init];
        return self;
    }
    return nil;
}

+ (void)enableZombie {
    [[self sharedZombie] enableZombie];
}

+ (void)addClassToZombieService:(NSString *)className {
    Class cls = objc_getClass([className UTF8String]);
    CCZombie *zombie = [self sharedZombie];
    [zombie->_classesThatEnablesZombie addObject:cls];
}
...

@end

剛才的- enableZombiemy_dealloc方法也定義在該類中.

這樣對(duì)某個(gè)類開(kāi)啟zombie就變得很簡(jiǎn)便了, 例如:

[CCZombie enableZombie];
[CCZombie addClassToZombieService:@"Son"];
[CCZombie addClassToZombieService:@"UIView"];

這些代碼可寫在App啟動(dòng)時(shí), 如AppDelegate的didFinishLaunching回調(diào)里.

向野指針發(fā)送消息示例

在VC里定義一個(gè)點(diǎn)擊事件:

- (IBAction)onBtnTestWildPointer:(id)sender {
    Son *__strong strongSon = [[Son alloc] init];
    Son *__unsafe_unretained son = strongSon;
    NSLog(@"release %@", son);
    strongSon = nil;
    [son performSelector:@selector(isMarried)];
    [son performSelector:@selector(someMethodThatExist)];
    [son performSelector:@selector(someMethodThatDoesNotExist)];
    
    UIView *__unsafe_unretained view = [[UIView alloc] init];
    [view setNeedsDisplay];
}

strongSon = nil;后, Son對(duì)象被釋放, 調(diào)用被swizzled的dealloc方法, son被isa-swizzle成僵尸對(duì)象. 同理View對(duì)象; 向其發(fā)送的所有消息, 都將發(fā)送CCZombie對(duì)象中.

因此, 在CCZombie類中又重寫forwardingTargetForSelector方法, 截獲該消息, 并轉(zhuǎn)發(fā)給一個(gè)樁類:

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"[%@ %@] message sent to deallocated instance %@", objc_getAssociatedObject(self, OrigClassNameKey), NSStringFromSelector(aSelector), self);
    StubProxy *stub = [[[StubProxy alloc] init] autorelease];
    if (![stub respondsToSelector:aSelector]) {
        Method method = class_getInstanceMethod([stub class], sel_registerName("someMethodUsedToPreventCrash"));
        class_addMethod([stub class], aSelector, method_getImplementation(method), method_getTypeEncoding(method));
    }
    return stub;
}

跟上面unrecognized selector處理是一樣道理.

例如向一個(gè)被釋放的UIView對(duì)象發(fā)送setNeedsDisplay方法, 由原來(lái)的EXC_BAD_ACCESSCrash變成了友好的提示:

CrashCrusher[67769:28599054] [UIView setNeedsDisplay] message sent to deallocated instance <CCZombie: 0x7fbce7c083a0>
CrashCrusher[67769:28599054] *** <StubProxy: 0x60800000ef40> prevent the crash. *** 

這就達(dá)到了野指針?lè)雷o(hù)的目的.

kvo

另外一中特殊的野指針情況, KVO.

如果observer先于被觀察對(duì)象釋放了的時(shí), 被觀察對(duì)象對(duì)Observer的不安全弱引用變成了野指針. 是的昔驱,EXC_BAD_ACCESS如果被發(fā)送了KVO消息.
這種情況也可用到剛才的Zombie機(jī)制來(lái)防護(hù).

[CCZombie addClassToZombieService:@"Observer"];

例如在VC里定義一個(gè)屬性, 并KVO它

- (void)viewDidLoad {
    [super viewDidLoad];
    //...
    Observer *observer = [[Observer alloc] init];
    self.someProperty = @"orignal value";
    [self addObserver:observer forKeyPath:@"someProperty" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
}

-viewDidLoad方法結(jié)束后, observer對(duì)象被釋放, 變成CCZombie對(duì)象,
這時(shí)如果發(fā)生一個(gè)點(diǎn)擊事件觸發(fā)了KVO

- (IBAction)triggerKVO:(id)sender {
    self.someProperty = @"new value";
}

這時(shí), 一個(gè)kvo消息observeValueForKeyPath:ofObject:change:context:將發(fā)送至CCZombie對(duì)象. 然后

CrashCrusher[68012:28642070] name:NSInternalInconsistencyException, 
reson:<CCZombie: 0x60000001d6d0>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.

居然 SIGABRT Crash掉了!

這是為何, 為何該消息不像[UIView setNeedsDisplay]之類的消息一樣被Zombie轉(zhuǎn)發(fā)并處理掉了呢?

原因很簡(jiǎn)單:

因?yàn)檫@里定義的CCZombie類是繼承于NSObject類的. 它自然也是擁有了observeValueForKeyPath:ofObject:change:context:等NSObject的方法. 所以不會(huì)進(jìn)入到消息轉(zhuǎn)發(fā)流程.

所以, 只需要在Zombie里重寫該方法就搞定了:

// CCZombie.m

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"KVO message sent to deallocated instance %@(%@)", objc_getAssociatedObject(self, OrigClassNameKey), self);
    NSLog(@"Observe keypath [%@] change in %@, old:%@, new:%@", keyPath, object, change[NSKeyValueChangeOldKey], change[NSKeyValueChangeNewKey]);
}

這樣, 當(dāng)向已被釋放的觀察者發(fā)送KVO時(shí), 也會(huì)給出相當(dāng)?shù)?友情提示"了.

CrashCrusher[68119:28657439] KVO message sent to deallocated instance Observer(<CCZombie: 0x600000013260>)
CrashCrusher[68119:28657439] Observe keypath [someProperty] change in <ViewController: 0x7ff5fec0e250>, old:orignal value, new:new value

Demo

待上傳

寫在最后

這篇主要從runtime的角度探究Crash防護(hù)的問(wèn)題, 順便研究和學(xué)習(xí)了一些常見(jiàn)的runtime實(shí)踐. 還研究了一下僵尸對(duì)象的問(wèn)題.

隨著zombie的增長(zhǎng)必定消耗越來(lái)越多的內(nèi)存, 這里沒(méi)有說(shuō)到關(guān)于zombie緩存的問(wèn)題, 這個(gè)問(wèn)題回頭有空研究研究, 再封裝一下這個(gè)Zombie. 待更.

除了野指針之外, Crash還有很多其它原因.

參考:

ARC下dealloc過(guò)程及.cxx_destruct的探究
大白健康系統(tǒng)--iOS APP運(yùn)行時(shí)Crash自動(dòng)修復(fù)系統(tǒng)
Clang 5 documentation OBJECTIVE-C AUTOMATIC REFERENCE COUNTING (ARC)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市上忍,隨后出現(xiàn)的幾起案子骤肛,更是在濱河造成了極大的恐慌,老刑警劉巖睡雇,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萌衬,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡它抱,警方通過(guò)查閱死者的電腦和手機(jī)秕豫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)观蓄,“玉大人混移,你說(shuō)我怎么就攤上這事∥甏” “怎么了歌径?”我有些...
    開(kāi)封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)亲茅。 經(jīng)常有香客問(wèn)我回铛,道長(zhǎng)狗准,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任茵肃,我火速辦了婚禮腔长,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘验残。我一直安慰自己捞附,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布您没。 她就那樣靜靜地躺著鸟召,像睡著了一般。 火紅的嫁衣襯著肌膚如雪氨鹏。 梳的紋絲不亂的頭發(fā)上欧募,一...
    開(kāi)封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音仆抵,去河邊找鬼槽片。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肢础,可吹牛的內(nèi)容都是我干的还栓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼传轰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼剩盒!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起慨蛙,我...
    開(kāi)封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤辽聊,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后期贫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體跟匆,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年通砍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了玛臂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡封孙,死狀恐怖迹冤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情虎忌,我是刑警寧澤泡徙,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站膜蠢,受9級(jí)特大地震影響堪藐,放射性物質(zhì)發(fā)生泄漏莉兰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一礁竞、第九天 我趴在偏房一處隱蔽的房頂上張望贮勃。 院中可真熱鬧,春花似錦苏章、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至硼端,卻和暖如春并淋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背珍昨。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工县耽, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人镣典。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓兔毙,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親兄春。 傳聞我的和親對(duì)象是個(gè)殘疾皇子澎剥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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