Objective-C-(二)內(nèi)存管理

由于Objective-C是基于C語言的魁淳,在了解Objective-C內(nèi)存管理前應(yīng)該先了解下C語言的內(nèi)存模型贝润。

簡單回顧下C程序的占用空間的幾個區(qū)域:

  • 程序代碼區(qū):存放程序執(zhí)行代碼的區(qū)域
  • 靜態(tài)數(shù)據(jù)區(qū):也稱全局?jǐn)?shù)據(jù)區(qū),存放程序中的全局變量完疫。例如:全局變量扔涧,靜態(tài)變量,一般常量旨椒,字符串常量。靜態(tài)數(shù)據(jù)區(qū)的內(nèi)存是由程序終止時由系統(tǒng)自動釋放堵漱。其中靜態(tài)數(shù)據(jù)區(qū)具體又分為兩塊區(qū)域:
    • BSS段(Block Started by Symbol):未初始化的全局變量
    • 數(shù)據(jù)段(data segment):已初始化的全局變量
  • 堆區(qū):由程序員手動管理分配和釋放综慎。通過malloc()、calloc怔锌、free()等函數(shù)操作的就是堆區(qū)的內(nèi)存。
  • 棧區(qū):函數(shù)的參數(shù),局部變量等存放在棧區(qū)埃元。棧區(qū)的內(nèi)存由系統(tǒng)自動分配和釋放涝涤。

在Objective-C中創(chuàng)建的對象都分配在堆區(qū),內(nèi)存管理針對的也是這塊區(qū)域岛杀。

Objective-C內(nèi)存管理的核心其實引用計數(shù)阔拳。系統(tǒng)通過對一個對象引用計數(shù)的計算來確認(rèn)是否要釋放對象回收內(nèi)存。Objective-C有兩種內(nèi)存管理機制:手動管理(MRC)和自動管理(ARC)类嗤。ARC的原理其實跟MRC是一致的糊肠,只是系統(tǒng)自動幫我們在合適的地方鍵入了內(nèi)存管理的方法,避免了手動管理帶來了麻煩和失誤遗锣。目前基本上開發(fā)用的都是ARC货裹。最開始學(xué)習(xí)iOS的時候也用過MRC,先介紹下MRC的機制精偿。

MRC

操作對象的四種方式:

  • 生成并持有對象:alloc/new/copy/mutableCopy等弧圆, retainCount :+1
  • 持有對象:retain,retainCount :+1
  • 釋放對象:release笔咽,retainCount :-1
  • 廢棄對象:dealloc搔预, 自動釋放內(nèi)存

內(nèi)存管理的四個法則:

  • 自己生成的對象,自己持有
  • 非自己生成的對象叶组,自己也能持有
  • 不再需要自己持有對象的時候釋放對象
  • 非自己持有的對象無法釋放

示例代碼:

自己生成的對象拯田,自己持有:

/**
*  以 alloc/new/copy/mutableCopy 等方法創(chuàng)建的對象歸調(diào)用者持有 
*/
id obj = [[NSObject alloc] init]; //創(chuàng)建一個NSObject對象返回給變量obj, 并且歸調(diào)用者持有

非自己生成的對象,自己也能持有:

/**
*  alloc/new/copy/mutableCopy 等方法以外的方式創(chuàng)建的對象不歸調(diào)用者持有 
*/
id obj = [NSMutableArray array]; // 非自己生成的對象甩十,該對象存在船庇,但不歸調(diào)用者持有
[obj retain]; // 如果想持有該對象,需要執(zhí)行retain方法

非自己生成的對象枣氧,且該對象存在是通過autorelease來實現(xiàn)的溢十。autorelease提供了一種使得對象在超出生命周期后能正確的被釋放(通過調(diào)用release方法)機制,以便于將對象返回給調(diào)用者达吞,讓調(diào)用者持有后再釋放對象张弛。否則對象還沒來得及被調(diào)用者持有就被系統(tǒng)釋放了。調(diào)用autorelease后對象不會立刻被釋放酪劫,而是被注冊到autoreleasepool中吞鸭,然后當(dāng)autoreleasepool結(jié)束被銷毀的時候,才會調(diào)用對象的release方法釋放對象覆糟。

不再需要自己持有對象的時候釋放對象:

id obj = [[NSObject alloc] init];
[obj release]; // 釋放自己生成并持有的對象

非自己持有的對象無法釋放:

id obj = [NSMutableArray array]; 
[obj release];  //由于當(dāng)前的調(diào)用者并不持有改對象刻剥,不能進行釋放操作,否則導(dǎo)致程序崩潰滩字。如果要釋放該對象造虏,需要先對對象進行retain操作御吞。
/**
以上方法在Xcode9中經(jīng)測試發(fā)現(xiàn)如果返回給obj的是NSMutableArray對象,會導(dǎo)致程序崩潰漓藕,但是如果是NSArray就不會陶珠。
*/

MRC下要注意屬性的引用計數(shù)情況。雖然retainCount在獲取引用計數(shù)的時候有時候不準(zhǔn)確享钞,但是也可以用來調(diào)試參考揍诽。例如我們給一個屬性賦值如下:

@interface MemoryRefenceVC ()
@property (nonatomic, copy) NSArray *array;
@end

@implementation MemoryRefenceVC

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.array = [[NSArray alloc] initWithObjects:@1, nil];
    NSLog(@"array.retainCount = %ld", _array.retainCount);
}
@end

打印如下:

2018-12-12 17:25:57.607777+0800 XXX[9889:341414] array.retainCount = 2

我們創(chuàng)建了一個對象并且返回給調(diào)用者持有,為什么此時對象的引用計數(shù)是2呢栗竖?

因為在屬性的賦值setter方法中暑脆,會對當(dāng)前的對象多進行一次引用。

- (void)setArray:(NSArray *)array {
    [array retain];  //進行了一次retain操作
    _array = array;
}

所以此時對象的內(nèi)存引用情況是:alloc創(chuàng)建時retainCount為1狐肢,setter方法中retain了一次引用計數(shù)加1添吗,所以此時retainCount變?yōu)榱?。

類似于如下操作:

NSArray *temp = [[NSArray alloc] initWithObjects:@1, nil]; 引用計數(shù)+1
self.array = temp; 引用計數(shù)+1

所以一般在使用屬性賦值的時候一般這么寫:

self.array = [[[NSArray alloc] initWithObjects:@2, nil] autorelease]; //用autorelease抵消一次retain操作

或者:

NSArray *temp = [[NSArray alloc] initWithObjects:@1, nil]; 
self.array = temp; 
[temp release];

ARC

ARC是蘋果引入的一種自動管理內(nèi)存的機制处坪,實現(xiàn)的方式就是在編譯的時候在代碼合適的位置自動鍵入內(nèi)存管理的代碼根资。

ARC下內(nèi)存管理思想跟MRC一樣,同樣遵守上面的四個法則同窘。只是ARC下已經(jīng)沒有了上面的retain玄帕、release、autorelease等直接操作對象內(nèi)存管理的方法想邦。ARC下Objective-C采用所有權(quán)修飾符來管理對對象的引用情況裤纹。

  • __strong :變量的默認(rèn)修飾符,默認(rèn)不指定的話就是__strong丧没。__strong表明了一種強引用的關(guān)系鹰椒,表示當(dāng)前修飾的變量持有對象,類似于MRC下的retain呕童。
  • __weak:與__strong相反漆际,__weak表明一種弱引用的關(guān)系,表示當(dāng)前修飾的變量并不會持有該對象夺饲,當(dāng)對象被系統(tǒng)釋放后奸汇,__weak變量會自動置為nil,比較安全往声,常用于解決循環(huán)引用的情況擂找。
  • __unsafe_unretained:同__weak一樣,該修飾符同樣不會持有對象浩销,但是不同的是贯涎,當(dāng)變量指向的對象被系統(tǒng)釋放后,變量不會自動置為nil慢洋,該指針會變?yōu)橐爸羔樚瘤ǎ绻俅卧L問該變量陆盘,會導(dǎo)致野指針訪問錯誤。現(xiàn)在很少會用到該修飾符败明。
  • __autoreleasing:用于修飾引用傳值的參數(shù)(id *, NSObject **)礁遣,類似于調(diào)用autorelease方法,在函數(shù)返回該值時會被自動釋放掉肩刃。常見于NSError的傳遞中:例如:error:(NSError *__autoreleasing *)error,傳遞error變量的引用杏头,這樣的話才可以在函數(shù)內(nèi)部對error進行重新賦值然后返回給調(diào)用者盈包,同時將內(nèi)部的創(chuàng)建的error對象注冊到Autorelease Pool中稍后釋放。

具體用法就不舉例了醇王,平時寫代碼用的都是這些修飾符呢燥,不過大部分情況用的是__strong,默認(rèn)省略了這個修飾符而已寓娩。

Autorelease Pool

MRC下叛氨,我們要使用自動釋放池需要手動創(chuàng)建NSAutoreleasepool,并且要執(zhí)行對象的autorelease方法和NSAutoreleasepooldrain方法銷毀自動釋放池棘伴。ARC下我們只需要使用@autoreleasepool語法就可以代替MRC下的NSAutoreleasepool寞埠。Autorelease Pool就是提供了一種延遲給對象發(fā)送release消息的機制。當(dāng)你想放棄一個對象的所有權(quán)焊夸,但是又不想這個對象立刻被釋放掉仁连,就可以使用Autorelease Pool

ARC下使用Autorelease Pool的場景:當(dāng)在循環(huán)遍歷中創(chuàng)建大量臨時對象的時候阱穗,為了避免內(nèi)存峰值可以使用Autorelease Pool來避免饭冬。例如:

for (int i = 0; i < 100; i++) {
    @autoreleasepool {
        NSData *data = UIImageJPEGRepresentation(image, 0.7f);
        UIImage *image = [UIImage imageWithData:data];
    }
}

如果不使用@autoreleasepool,for循環(huán)內(nèi)部創(chuàng)建出的大量UIImage對象需要等到循環(huán)結(jié)束時才能釋放揪阶,這樣會導(dǎo)致內(nèi)存暴漲昌抠。當(dāng)指定了@autoreleasepool后,每次循環(huán)結(jié)束的時候?qū)ο缶蜁会尫诺袈沉牛苊饬藘?nèi)存峰值炊苫。

或者在方法中執(zhí)行一段非常消耗資源的操作時,可以用@autoreleasepool及時釋放掉資源蕴茴。例如SDWebImage中對圖像進行的解碼預(yù)渲染操作劝评。

//摘自SDWebImage
- (nullable UIImage *)sd_decompressedImageWithImage:(nullable UIImage *)image {
    if (![[self class] shouldDecodeImage:image]) {
        return image;
    }
    
    // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
    // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
    @autoreleasepool{
        
        CGImageRef imageRef = image.CGImage;
        // device color space
        CGColorSpaceRef colorspaceRef = SDCGColorSpaceGetDeviceRGB();
        BOOL hasAlpha = SDCGImageRefContainsAlpha(imageRef);
        // iOS display alpha info (BRGA8888/BGRX8888)
        CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
        bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
        
        size_t width = CGImageGetWidth(imageRef);
        size_t height = CGImageGetHeight(imageRef);
        
        // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
        // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
        // to create bitmap graphics contexts without alpha info.
        CGContextRef context = CGBitmapContextCreate(NULL,
                                                     width,
                                                     height,
                                                     kBitsPerComponent,
                                                     0,
                                                     colorspaceRef,
                                                     bitmapInfo);
        if (context == NULL) {
            return image;
        }
        
        // Draw the image into the context and retrieve the new bitmap image without alpha
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
        CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
        UIImage *imageWithoutAlpha = [[UIImage alloc] initWithCGImage:imageRefWithoutAlpha scale:image.scale orientation:image.imageOrientation];
        CGContextRelease(context);
        CGImageRelease(imageRefWithoutAlpha);
        
        return imageWithoutAlpha;
    }
}

循環(huán)引用

循環(huán)引用是指幾個對象(至少兩個對象)之間互相持有強引用形成了一個閉環(huán),導(dǎo)致在超出對象的生命周期后誰都釋放不掉的情況倦淀。

導(dǎo)致循環(huán)引用的可能情況:

  • 使用Block互相持有
  • NSTimer強引用Target目標(biāo)對象
  • 使用delegate

解決循環(huán)引用的方法:

  • 使用弱引用weak(__weak)
  • 當(dāng)持有的實例完成任務(wù)后賦值為nil

僵尸對象

最近又看了下《Effective Objective-C 2.0》蒋畜,關(guān)于僵尸對象的具體實現(xiàn)仔細(xì)研究了下,做個筆記記錄下撞叽。

僵尸對象是iOS開發(fā)中常用的內(nèi)存管理調(diào)試功能姻成。當(dāng)我們給一個已經(jīng)釋放的對象發(fā)送消息的時候插龄,通過僵尸對象能夠很方便的了解到這個消息的相關(guān)信息,包括當(dāng)前調(diào)用對象所屬的類科展,消息名稱等信息均牢,便于我們查找問題的根源。

僵尸對象的原理:在runtime期間當(dāng)一個對象被釋放后才睹,它不會真正的被系統(tǒng)回收徘跪,而是被轉(zhuǎn)化成一個特殊的僵尸對象。這個對象所占用的內(nèi)存不會被釋放琅攘,當(dāng)再次給這個對象發(fā)送消息時垮庐,僵尸對象會拋出異常,并且描述出當(dāng)前消息的相關(guān)內(nèi)容坞琴。例如:

*** -[__NSArrayI indexOfObject:]: message sent to deallocated instance 0x60000022b500

僵尸對象是如何實現(xiàn)的呢哨查?具體來講分以下幾個步驟:首先runtime會替換掉基類NSObject的dealloc方法,在dealloc方法中進行下面步驟:

  • 獲取當(dāng)前的類名ClassName

  • 通過拼接_NSZombie_前綴創(chuàng)建一個新的僵尸類名:_NSZombie_ClassName (后綴ClassName為當(dāng)前的類名)

  • 通過_NSZombie_類拷貝出一個新的類剧辐,并且類名命名為_NSZombie_ClassName

  • 銷毀當(dāng)前的對象寒亥,但是不釋放內(nèi)存(不調(diào)用free()方法)

  • 將當(dāng)前對象的isa指針指向新創(chuàng)建的僵尸類(變更對象所屬的類)

_NSZombie_類以及從其拷貝出來的新的僵尸類都沒有實現(xiàn)任何方法,所以當(dāng)給僵尸對象發(fā)送消息后荧关,會進入消息轉(zhuǎn)發(fā)流程溉奕。___forwarding___函數(shù)是實現(xiàn)消息轉(zhuǎn)發(fā)流程的核心函數(shù),在這個函數(shù)中先檢測當(dāng)前接收消息的對象所屬的類名忍啤,如果類名的前綴是_NSZombie_腐宋,表明當(dāng)前的消息發(fā)送的對象是僵尸對象,然后就會做特殊處理:先打印出當(dāng)前消息的相關(guān)信息檀轨,然后終止程序拋出異常胸竞。

偽代碼如下:

dealloc方法中創(chuàng)建僵尸類

- (void)createNSZombie {
 //獲取當(dāng)前對象的類名
 const char *className = object_getClassName(self);
 //創(chuàng)建新的僵尸對象類名
 const char *zombieClassName = strcat("_NSZombie_", className);
 //根據(jù)僵尸對象類名獲取僵尸對象類(`objc_lookUpClass` 相比 `objc_getClass`,當(dāng)類沒有注冊時不會去調(diào)用類處理回調(diào))
 Class zombieClass = objc_lookUpClass(zombieClassName);

 //如果不存在参萄,先創(chuàng)建僵尸對象類
 if (!zombieClass) {
 //獲取_NSZombie_類
 Class baseZombieClass = objc_lookUpClass("_NSZombie_");
 //這里使用的是`objc_duplicateClass`創(chuàng)建新的類卫枝,`objc_duplicateClass`是直接拷貝目標(biāo)類生成新的類然后賦予新的類名,新的類和_NSZombie_類結(jié)構(gòu)相同讹挎,本類的父類校赤,實例變量和方法都和復(fù)制前一樣。
 zombieClass = objc_duplicateClass(baseZombieClass, zombieClassName, 0);
 }

 //銷毀對象筒溃,但是不釋放對象占用的內(nèi)存
 objc_destructInstance(self);

 //重新設(shè)置當(dāng)前對象所屬的類马篮,讓其指向新創(chuàng)建的僵尸類
 object_setClass(self, zombieClass);
}

消息轉(zhuǎn)發(fā)的實現(xiàn)怜奖,我把整個___forwarding___函數(shù)的實現(xiàn)都摘錄了浑测,順便回顧下消息轉(zhuǎn)發(fā)的流程

以下代碼摘自:Objective-C 消息發(fā)送與轉(zhuǎn)發(fā)機制原理

int __forwarding__(void *frameStackPointer, int isStret) {
 id receiver = *(id *)frameStackPointer;
 SEL sel = *(SEL *)(frameStackPointer + 8);
 const char *selName = sel_getName(sel);
 Class receiverClass = object_getClass(receiver);
?
 // 調(diào)用 forwardingTargetForSelector:
 if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
 id forwardingTarget = [receiver forwardingTargetForSelector:sel];
 if (forwardingTarget && forwarding != receiver) {
 if (isStret == 1) {
 int ret;
 objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
 return ret;
 }
 return objc_msgSend(forwardingTarget, sel, ...);
 }
 }
?
 // 僵尸對象
 const char *className = class_getName(receiverClass);
 const char *zombiePrefix = "_NSZombie_";
 size_t prefixLen = strlen(zombiePrefix); // 0xa
 if (strncmp(className, zombiePrefix, prefixLen) == 0) {
 CFLog(kCFLogLevelError,
 @"*** -[%s %s]: message sent to deallocated instance %p",
 className + prefixLen,
 selName,
 receiver);
 <breakpoint-interrupt>
 }
?
 // 調(diào)用 methodSignatureForSelector 獲取方法簽名后再調(diào)用 forwardInvocation
 if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
 NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
 if (methodSignature) {
 BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
 if (signatureIsStret != isStret) {
 CFLog(kCFLogLevelWarning ,
 @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.",
 selName,
 signatureIsStret ? "" : not,
 isStret ? "" : not);
 }
 if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
 NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
?
 [receiver forwardInvocation:invocation];
?
 void *returnValue = NULL;
 [invocation getReturnValue:&value];
 return returnValue;
 } else {
 CFLog(kCFLogLevelWarning ,
 @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
 receiver,
 className);
 return 0;
 }
 }
 }
?
 SEL *registeredSel = sel_getUid(selName);
?
 // selector 是否已經(jīng)在 Runtime 注冊過
 if (sel != registeredSel) {
 CFLog(kCFLogLevelWarning ,
 @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
 sel,
 selName,
 registeredSel);
 } // doesNotRecognizeSelector
 else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
 [receiver doesNotRecognizeSelector:sel];
 } 
 else {
 CFLog(kCFLogLevelWarning ,
 @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
 receiver,
 className);
 }
?
 // The point of no return.
 kill(getpid(), 9);
}
?```

這就是整個僵尸對象的實現(xiàn)過程。

OC內(nèi)存管理大概就是這些,要想更深入的理解迁央,可以了解下內(nèi)存管理方法是如何實現(xiàn)的掷匠。下一篇寫下OC的內(nèi)存管理的實現(xiàn)原理。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末岖圈,一起剝皮案震驚了整個濱河市讹语,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蜂科,老刑警劉巖顽决,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異导匣,居然都是意外死亡擎值,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門逐抑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人屹蚊,你說我怎么就攤上這事厕氨。” “怎么了汹粤?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵命斧,是天一觀的道長。 經(jīng)常有香客問我嘱兼,道長国葬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任芹壕,我火速辦了婚禮汇四,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘踢涌。我一直安慰自己通孽,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布睁壁。 她就那樣靜靜地躺著背苦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪潘明。 梳的紋絲不亂的頭發(fā)上行剂,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音钳降,去河邊找鬼厚宰。 笑死,一個胖子當(dāng)著我的面吹牛遂填,可吹牛的內(nèi)容都是我干的固阁。 我是一名探鬼主播壤躲,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼备燃!你這毒婦竟也來了碉克?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤并齐,失蹤者是張志新(化名)和其女友劉穎漏麦,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體况褪,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡撕贞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了测垛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捏膨。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖食侮,靈堂內(nèi)的尸體忽然破棺而出号涯,到底是詐尸還是另有隱情,我是刑警寧澤锯七,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布链快,位于F島的核電站,受9級特大地震影響眉尸,放射性物質(zhì)發(fā)生泄漏域蜗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一噪猾、第九天 我趴在偏房一處隱蔽的房頂上張望霉祸。 院中可真熱鬧,春花似錦袱蜡、人聲如沸脉执。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽半夷。三九已至,卻和暖如春迅细,著一層夾襖步出監(jiān)牢的瞬間巫橄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工茵典, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留湘换,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像彩倚,于是被迫代替她去往敵國和親筹我。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

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