iOS之從MRC到ARC內存管理詳解

概述

在iOS中開發中,我們或多或少都聽說過內存管理。iOS的內存管理一般指的是OC對象的內存管理,因為OC對象分配在堆內存,堆內存需要程序員自己去動態分配和回收;基礎數據類型(非OC對象)則分配在棧內存中,超過作用域就會由系統檢測回收。如果我們在開發過程中,對內存管理得不到位,就有可能造成內存泄露。

我們通常講的內存管理,實際上從發展的角度來說,分為兩個階段:MRC和ARC。MRC指的是手動內存管理,在開發過程中需要開發者手動去編寫內存管理的代碼;ARC指的是自動內存管理,在此內存管理模式下由LLVM編譯器和OC運行時庫生成相應內存管理的代碼。

通篇主要介紹關于內存管理的原理及ARC和MRC環境下編寫代碼實現的差異。

提綱

一 引用計數
二 MRC操作對象的方法
  1. alloc/new/copy/mutableCopy
  2. retain
  3. release
  4. autorelease
  5. autorelease pool
三 ARC操作對象的修飾符
  1. __strong
    1.1 strong與變量
    1.2 strong與屬性
    1.3 strong的實現
  2. __weak
    2.1 weak和循環引用
    2.2 weak和變量
    2.3 weak的實現
    ? 2.3.1 weak和賦值
    ? 2.3.2 weak和訪問
  3. __unsafe_unretained
  4. _autoreleasing

具體介紹

一、引用計數

在OC中,使用引用計數來進行內存管理。每個對象都有一個與其相對應的引用計數器,當持有一個對象,這個對象的引用計數就會遞增;當這個對象的某個持有被釋放,這個對象的引用計數就會遞減。當這個對象的引用計數變為0,那么這個對象就會被系統回收。

當一個對象使用完沒有釋放,此時其引用計數永遠大于1。該對象就會一直占用其分配在堆內存的空間,就會導致內存泄露。內存泄露到一定程度有可能導致內存溢出,進而導致程序崩潰。

二、MRC操作對象的方法

1.alloc/new/copy/mutableCopy

1.1 持有調用者自己的對象

在蘋果規定中,使用alloc/new/copy/mutableCopy創建返回的對象歸調用者所有,例如以下MRC代碼:

    NSMutableArray *array = [[NSMutableArray alloc] init];/*NSMutableArray類對象A*/
    
    NSLog(@"%p", array);
    
    [array release];//釋放

由于對象A由alloc生成,符合蘋果規定,指針變量array指向并持有對象A,引用計數器會加1。另外,array在使用完對象A后需要對其進行釋放。當調用release后,釋放了其對對象A的引用,計數器減1。對象A此時引用計數值為零,所以對象A被回收。不能訪問已經被回收的對象,會發生崩潰。

1.2 持有非調用者擁有的對象

當持有非調用者自己擁有的對象的時候,例如以下代碼:

    id obj = [Person person];
    
    [obj retain];
    
    /*do something*/
    
    [obj release];

此時obj變量獲得但不持有Person類對象,可以通過retain進行持有該對象。當我們使用完該對象,應該調用release方法釋放該對象。

注意:按照蘋果的命名規則,必須是alloc/new/copy/mutableCopy開頭,并且是符合駝峰命名規則生成的對象才歸調用者所有。例如以下的方法,生成的對象不歸調用者所有:

- (id)newarray;
- (id)allocwithInfo;
- (id)coPySomething;
- (id)mutablecopyItem;

2.retain

2.1 retain和屬性

我們可以通過屬性來保存對象,如果一個屬性為強引用,我們就可以通過屬性的實例變量和存取方法來對某個對象進行操作,例如某個屬性的setter方法如下:

- (void)setPerson:(Person *)person {

    [person retain];
    
    [_person release];
    
    _person = person;
    
}

我們通過retain新值,release舊值,再給實例變量更新值。需要注意的一點是:需要先retain新值,再release新值。因為如果新舊值是同一個對象的話,先release就有可能導致該對象被系統回收,再去retain就沒有任何意義了。例如下面這個例子:

#import "ViewController.h"
#import "Person.h"

@interface ViewController ()

@property (nonatomic, strong)Person *person;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
  
    //實例變量持有Person類對象(P對象)。這樣賦值不會調用set方法
    _person = [[Person alloc] init];
    
    self.person = _person;//調用set方法
    
}

- (void)setPerson:(Person *)person {
    //release釋放對P對象的引用,P對象引用計數值變為零,則P對象被系統回收
    [_person release];

    //由于P對象已經被回收,再去retain就容易出問題
    [person retain];
    
    _person = person;
    
}

@end

由于P對象被回收,對應其所分配的內存被置于“可用內存池”中。如果該內存未被覆寫,那么P對象依然有效;如果內存被覆寫,那么實例變量_person就會指向一個被覆寫的未知對象的指針,那么實例變量就變成一個“懸掛指針”。

2.2 retain和數組

如果我們把一個對象加入到一個數組中,那么該數組的addObject方法會對該對象調用retain方法。例如以下代碼:

    //person獲得并持有P對象,P對象引用計數為1
    Person *person = [[Person alloc] init];//Person類對象生成的P對象
    
    NSMutableArray *array = [NSMutableArray array];
    
    //person被加入到數組,對象P引用計數值為2
    [array addObject:person];

此時,對象P被person和array兩個變量同時持有。

3.release

3.1 自己持有的對象自己釋放

當我們持有一個對象,如果在不需要繼續使用該對象,我們需要對其進行釋放(release)。例如以下代碼:

    //array獲得并持有NSArray類對象
    NSArray *array = [[NSArray alloc] init];
    
    /*當不再需要使用該對象時,需要釋放*/
    [array release];
    
    //obj獲得但不持有該對象
    id obj = [NSArray array];

    //持有對象
    [obj retain];    

    /*當不在需要使用該對象時,需要釋放*/
    [obj release];
3.2 非自己持有的對象不要釋放

當我們不持有某個對象,卻對該對象進行釋放,應用程序就會崩潰。

    //獲得并持有A對象
    Person *p = [[Person alloc] init];//Person類對象A
    
    //p釋放對象A的強引用,對象A所有者不存在
    //對象A引用計數為零,所以對象A被回收
    [p release];

    [p release];//釋放非自己持有的對象

另外,我們也不能訪問某個已經被釋放的對象,該對象所占的堆空間如果被覆寫就會發生崩潰的情況。

4.autorelease

autorelease指的是自動釋放,當一個對象收到autorelease的時候,該對象就會被注冊到當前處于棧頂的自動釋放池(autorelease pool)。如果沒有主動生成自動釋放池,則當前自動釋放池對應的是主運行循環的自動釋放池。在當前線程的RunLoop進入休眠前,就會對被注冊到該自動釋放池的所有對象進行一次release操作。

autorelease和release的區別是:release是馬上釋放對某個對象的強引用;autorelease是延遲釋放某個對象的生命周期。

autorelease通常運用在當調用某個方法需要返回對象的情況下,例如以下代碼:

    //外部調用
    Person *p = [Person person];
    NSLog(@"%p", p);//使用無須retain

    //持有則需要retain
    [p retain];
    _person = p;
    [_person release];

    //Person類內部定義
+ (id)person {

    //創建的Person類對象由person獲得并持有
    Person *person = [[Person alloc] init];
   
    //[person release];

    [person autorelease];
    
    return person;
}

在外部調用,從方法名person知道,創建的對象由p指針變量獲得但不持有。在函數內部,person獲得并持有了Person類對象,所返回的person對象的引用計數加1。換句話說,調用者需要額外處理這多出來的一個持有操作。另外,我們不能在函數內部調用release,不然對象還沒返回就已經被系統回收。這時候使用autorelease就能很好地解決這個問題。

只要把要返回的對象調用autorelease方法,注冊到自動釋放池就能延長person對象的生命周期,使其在autorelease pool銷毀(drain)前依然能夠存活。

另外,person對象在返回時調用了autorelease方法。該對象已經在自動釋放池中,我們可以直接使用對象p,無須再通過[p retain]訪問;不過,如果要用實例變量持有該對象,則需要對變量p進行一次retain操作,示例變量使用完該對象需要釋放該對象。

5. autorelease pool

5.1 autorelease pool和RunLoop(運行循環)

每條線程都包含一個與其對應的自動釋放池,當某條線程被終止的時候,對應該線程的自動釋放池會被銷毀。同時,處于該自動釋放池的對象將會進行一次release操作。

當應用程序啟動,系統默認會開啟一條線程,該線程就是“主線程”。主線程也有一個與之對應的自動釋放池,例如我們常見的ARC下的main.h文件:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

該自動釋放池用來釋放在主線程下注冊到該自動釋放池的對象。需要注意的是,當我們開啟一條子線程,并且在該線程開啟RunLoop的時候,需要為其增加一個autorelease pool,這樣有助于保證內存的安全。

5.2 autorelease pool和降低內存峰值

當我們執行一些復雜的操作,特別是如果這些復雜的操作要被循環執行,那么中間會免不了會產生一些臨時變量。當被加到主線程自動釋放池的對象越來越來多,卻沒有得到及時釋放,就會導致內存溢出。這個時候,我們可以手動添加自動釋放池來解決這個問題。如以下例子所示:

for (int i = 0; i < largeNumber; i++) {
        
        //創建自動釋放池
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        
        //產生許多被注冊到自動釋放池的臨時對象
        id obj = [Person personWithComplexOperation];
        
        //釋放池中對象
        [pool drain];
        
    }

如上述例子所示,我們執行的循環次數是一個非常大的數字。并且調用personWithComplexOperation方法的過程中會產生許多臨時對象,所產生的臨時對象有可能會被注冊到自動釋放池中。我們通過手動生成一個自動釋放池,并且在每次循環結束前把該自動釋放池的對象執行release操作釋放掉,這樣就能有效地降低內存的峰值了。

三 ARC操作對象的修飾符

1. __strong

1.1 __strong與變量

在ARC模式下,id類型和OC對象的所有權修飾符默認是__strong。當一個變量通過__strong修飾符來修飾,當該變量超出其所在作用域后,該變量就會被廢棄。同時,賦值給該變量的對象也會被釋放。例如:

{
    //變量p持有Person對象的強引用
    Person *p = [Person person];
    
    //__strong修飾符可以省略
    //Person __strong *p = [Person person];
}
    //變量p超出作用域,釋放對Person類對象的強引用
    //Person類對象持有者不存在,該對象被釋放

上述例子對應的MRC代碼如下:

{
    Person *p = [Person person];
    
    [p retain];
    
    [p release];
}

可以看出,ARC和MRC是對應的。只不過在ARC下,對象“持有”和“釋放”的內存管理代碼交由系統去調用罷了。

1.2 strong與屬性

如果一個屬性的修飾符是strong,對于其實例變量所持有的對象,編譯器會在該實例變量所屬類的dealloc方法為其添加釋放對象的方法。在MRC下,我們的dealloc方法有如:

- (void)dealloc {

    [p release];//ARC無效
    
    [super dealloc];//ARC無效
}

在ARC下,我們就無須再去寫這樣的代碼了。另外,在dealloc方法中,ARC只能幫我們處理OC對象。如果實例變量持有類似CoreFoundation等非OC對象,則需要我們手動回收:

- (void)dealloc {

    CFRelease(_cfObject);
}

在ARC下,dealloc方法一般用來執行兩個任務。第一個就是手動釋放非OC對象;第二個是接觸監聽。另外,在ARC下我們不能主動調用dealloc方法。因為一旦調用dealloc,對象就不再有效。該方法運行期系統會在合適的時機自動去調用。

1.3 strong的實現

在ARC中,除了會自動調用“保留”和“釋放”方法外,還進行了優化。例如某個對象執行了多次“保留”和“釋放”,那么ARC針對特殊情況有可能會將該對象的“保留”和“釋放”成對地移除。例如:

+ (id)person {

    Person *tmp = [[Person alloc] init];//引用計數為1
    [tmp autorelease];//注冊到自動釋放池(ARC無效)
    return tmp;
}

{
    //ARC
    _p = [Person person];//_p是強引用屬性對應的實例變量
    
    //實現展示
    Person *p = [Person person];//Person類對象引用計數為1
    _p = [p retain];//遞增為2
    [_p release];//遞減為1
}
    //清空自動釋放池,Person類對象遞減為0,釋放該對象

在上述代碼中,ARC對應MRC的具體實現展示。在上面的展示中,+(id)person方法內部會調用一次autorelease操作來延遲其返回對象的生命周期,并且稍后在自動釋放池進行release操作。_p通過retain來持有該對象,使用完就執行release操作。從而看出retain和autorelease是多余的,完全可以簡化成以下代碼:

+ (id)person {

    Person *tmp = [[Person alloc] init];//引用計數為1
    return tmp;
}

{
    //ARC
    _p = [Person person];//_p是強引用屬性對應的實例變量
    
    //實現展示
    _p = [Person person];//Person類對象引用計數為1
    [_p release];//遞減為0,Person類對象被回收
}

那么ARC是如何判斷是否移除這種成對的操作呢?其實在ARC中,并不是直接執行retain和autorelease操作的,而是通過以下兩個方法:

    objc_autoreleaseReturnValue(obj);//對應autorelease
    objc_retainAutoreleasedReturnValue(obj);//對應retain

以下為兩個方法對應的偽代碼:

id objc_autoreleaseReturnValue(id obj) {

    if ("返回對象obj后面的那段代碼是否執行retain") {
        //是
        set_flag(obj);//設置標志位
        return obj;
    } else {
        return [obj autorelease];
    }
}

id objc_retainAutoreleasedReturnValue(obj) {

    if (get_flag(obj)) {
        //有標志位
        return obj;
    } else {
        return [obj retain];
    }
}

通過以上兩段偽代碼,我們重新梳理一下代碼:

+ (id)person {

    Person *tmp = [[Person alloc] init];//引用計數為1
    
    return objc_autoreleaseReturnValue(id tmp);
}

{
    //ARC
    _p = [Person person];//_p是強引用屬性對應的實例變量
    
    //實現展示
    Person *p = [Person person];//Person類對象引用計數為1
    _p = objc_retainAutoreleasedReturnValue(p);//遞增為1
    [_p release];//遞減為0
}

從上述示例代碼可以看出:

  1. 當我們用變量p獲取person方法返回的對象前,person方法內部會執行objc_autoreleaseReturnValue方法。該方法會檢測返回對象之后即將執行的那段代碼,如果那段代碼要向所返回的對象執行retain方法,則為該對象設置一個全局數據結構中的標志位,并把對象直接返回。反之,在返回之前把該對象注冊到自動釋放池。

  2. 當我們對Person類對象執行retain操作的時候,會執行objc_retainAutoreleasedReturnValue方法。該方法會檢測對應的對象是否已經設置過標志位,如果是,則直接把該對象返回;反之,會向該對象執行一次retain操作再返回。

在ARC中,通過設置和檢測標志位可以移除多余的成對(“保留”&“釋放”)操作,優化程序的性能。

2. __weak

2.1 weak和循環引用

__weak與我們上述所提到的__strong相對應,__strong對某個對象具有強引用。那么,__weak則指的是對某個對象具有弱引用。一般weak用來解決我們開發中遇到的循環引用問題,例如以下代碼:

/*Man類*/
#import <Foundation/Foundation.h>

@class Woman;

@interface Man : NSObject

@property (nonatomic, strong)Woman *person;

@end

/*Woman類*/
#import <Foundation/Foundation.h>

@class Man;

@interface Woman : NSObject

@property (nonatomic, strong)Man *person;

@end

/*調用*/
- (void)viewDidLoad {
    [super viewDidLoad];

    Man *man = [[Man alloc] init];
    
    Woman *woman = [[Woman alloc] init];
    
    man.person = woman;
    woman.person = man;
}

從上述代碼可以看出,Man類對象和Woman類對象分別有一個所有權修飾符為strong的person屬性。兩個類之間互相通過實例變量進行強引用,Man類對象如果要釋放,則需要Woman類對象向其發送release消息。然而Woman類對象要執行dealloc方法向Man類對象發送release消息的話,又需要Man類對象向其發送release消息。雙方實例變量互相強引用類對象,所以造成循環引用,如下圖所示:

循環引用.png

這時候weak修飾符就派上用場了,因為weak只通過弱引用來引用某個對象,并不會真正意義上的持有該對象。所以我們只需要把上述兩個類對象的其中一個屬性用weak來修飾,就可以解決循環引用的問題。例如我們把Woman類的person屬性用weak來修飾,分析代碼如下:

/*調用*/
- (void)viewDidLoad {
    [super viewDidLoad];

    Man *man = [[Man alloc] init];//Man對象引用計數為1
    
    Woman *woman = [[Woman alloc] init];//Woman對象引用計數為1
    
    man.person = woman;//強引用,Woman對象引用計數為2

    woman.person = man;//弱引用,Man對象引用計數為1
}
    //變量man超出作用域,對Man類對象的強引用失效
    //Man類對象持有者不存在,Man類對象被回收
    //Man類對象被回收, woman.person = nil;
    //Man調用dealloc方法,Woman類對象引用計數遞減為1
    //變量woman超出作用域,對Woman類對象強引用失效
    //Woman類對象持有者不存在,Woman類對象被回收

從上述示例代碼可以看出,我們只需要把其中一個強引用修改為弱引用就可以打破循環引用。類似的循環引用常見的有block、NSTimer、delegate等,感興趣的自行查閱相關資料,這里不作一一介紹。

另外,基于運行時庫,如果變量或屬性使用weak來修飾,當其所指向的對象被回收,那么會自動為該變量或屬性賦值為nil。這一操作可以有效地避免程序出現野指針操作而導致崩潰,不過強烈不建議使用一個已經被回收的對象的弱引用,這本身對于程序設計而言就是一個bug。

2.2 weak和變量

如果一個變量被__weak修飾,代表該變量對所指向的對象具有弱引用。例如以下代碼:

    Person __weak *weakPerson = nil;
    
    if (1) {
        
        Person *person = [[Person alloc] init];
        weakPerson = person;
        
        NSLog(@"%@", weakPerson);//weakPerson弱引用
    }
    
    NSLog(@"%@", weakPerson);

輸出結果如下:

[1198:73648] <Person: 0x600000018120>
[1198:73648] (null)

從上述輸出結果可以分析,當超出作用域后,person變量對Person對象的強引用失效。Person對象持有者不存在,所以該對象被回收。同時,weakPerson變量對Person的弱引用失效,weakPerson變量被賦值為nil。

另外需要注意的是,如果一個變量被weak修飾,那么這個變量不能持有對象示例,編譯器會發出警告。例如以下代碼:

Person __weak *weakPerson = [[Person alloc] init];

因為weakPerson被__weak修飾,不能持有生成的Person類對象。所以Person類對象創建完立即被釋放,所以編譯器會給出相應的警告:

Assigning retained object to weak variable; object will be released after assignment

2.3 weak的實現
2.3.1 weak和賦值

要解釋weak賦值的實現,我們先看以下示例代碼:

    Person *person = [[Person alloc] init];
    
    Person __weak *p = person;

上述對應的模擬代碼如下:

    Person *person = [[Person alloc] init];
    
    Person *p;
    objc_initWeak(&p, person);
    objc_destroyWeak(&p, 0);

上述兩個函數分別用來對變量p的初始化和釋放,它們都調用同一個函數。如下:

    id p;

    p = 0;

    objc_storeWeak(&p, person);//對應objc_initWeak

    objc_storeWeak(&p, 0);//對應objc_destroyWeak

根據上述代碼我們進行分析:
1.初始化變量
當我們使用變量弱引用指向一個對象時,通過傳入變量的地址和賦值對象兩個參數來調用objc_storeWeak方法。該方法內部會將對象的地址&person作為鍵值,把變量p的地址&p注冊到weak表中。

2.釋放變量
當超出作用域,Person類對象被回收,此時調用objc_storeWeak(&p, 0)把變量的地址從weak表中刪除。 變量地址從weak表刪除前,利用被回收對象的地址作為鍵值進行檢索,把對應的變量地址賦值為nil。

2.3.2 weak和訪問

當我們訪問一個被__weak修飾過的變量所指向的對象時,其內部是如何實現的?我們先看以下示例代碼:

    Person *person = [[Person alloc] init];

    Person __weak *p = person;
    
    NSLog(@"%@", p);

上述代碼對應的模擬代碼如下:

    Person *person = [[Person alloc] init];
    
    Person *p;
    objc_initWeak(&p, person);
    id tmp = objc_loadWeakRetained(&p);
    objc_autorelease(tmp);
    NSLog(@"%@", tmp);
    objc_destroyWeak(&p);

通過上述代碼可以看出,訪問一個被__weak修飾的變量,相對于賦值多了兩個步驟:

  1. objc_loadWeakRetained,通過該函數取出對應變量所引用的對象并retain;
  2. 把變量指向的對象注冊到自動釋放池。

這樣一來,weak變量引用的對象就被加入到自動釋放池。在自動釋放池結束前都能安全使用該對象。需要注意的是,如果大量訪問weak變量,就會導致weak變量所引用的對象被多次加入到自動釋放池,從而導致影響性能。如果需要大量訪問,我們通過__strong修飾的變量來解決,例如:

    Person __weak *p = obj;
    
    Person *tmp = p;//p引用的對象被注冊到自動釋放池
    
    NSLog(@"%@", tmp);
    NSLog(@"%@", tmp);
    NSLog(@"%@", tmp);
    NSLog(@"%@", tmp);
    NSLog(@"%@", tmp);

通過利用__strong中間變量的使用,p引用的對象僅注冊1次到自動釋放池中,有效地減少了自動釋放池中的對象。

3. __unsafe_unretained

首先我們需要明確一點,如果一個變量被__unsafe_unretained修飾,那么該變量不屬于編輯器的內存管理對象。該修飾符表明不保留值,即對其所指向的對象既不強引用,也不弱引用。例如以下代碼:

__unsafe_unretained.png

根據上述代碼分析:當超出作用域,變量p對Person類對象的強引用失效。Person類對象的持有者不存在,該對象被回收。當我們再次使用該變量的時候,因為其所表示的對象已經被回收,所以就會發生野指針崩潰。這一點區別于__weak,當被__weak修飾變量所指向的對象被回收,該變量會賦值為nil。

另外,被__unsafe_unretained修飾的變量跟__weak一樣,不能持有對象實例。因為__unsafe_unretained修飾的變量不會對生成的對象實例進行保留操作,所以對象創建完立馬被回收,編譯器會給出相應的警告。

當我們給被__unsafe_unretained修飾的變量賦值時,必須保證賦值對象確實存在,不然程序就會發生崩潰。

4. __autoreleasing

在ARC和MRC中,autorelease作用一致,只是兩者的表現方式有所不同。
1.如果返回的對象歸對用者所有,如下:

    @autoreleasepool {
        
        Person __autoreleasing *p = [[Person alloc] init];
    }

上述代碼模擬代碼如下:

    id pool = objc_autoreleasePoolPush();
    Person *p = [[Person alloc] init];//持有
    objc_autorelease(p);
    objc_autoreleasePoolPop(pool);

2.如果返回的對象不歸調用者所有,如下:

    @autoreleasepool {
        
        Person __autoreleasing *p = [Person person];
    }

上述代碼模擬代碼如下:

    id pool = objc_autoreleasePoolPush();
    Person *p = [Person person];
    objc_retainAutoreleasedReturnValue(p);//持有(優化后的retain方法)
    objc_autorelease(p);
    objc_autoreleasePoolPop(pool);

從上面兩個例子可以看出,__autoreleasing的實現都是通過objc_autorelease()函數,只不過是持有方法有所不同而已。

至此,內存管理就介紹完畢了。由于技術有限,難免會存在紕漏,歡迎指正。轉載請注明出處,萬分感謝。

參考資料:
Objective-C 高級編程 iOS和OS X多線程和內存管理
Effective Objective 2.0

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

推薦閱讀更多精彩內容