淺談iOS數(shù)據(jù)持久化

導(dǎo)語

數(shù)據(jù)持久化是一種非易失性存儲技術(shù),在重啟動計算機(jī)或設(shè)備后也不會丟失數(shù)據(jù),是將內(nèi)存中的數(shù)據(jù)模型轉(zhuǎn)換為存儲模型,以及將存儲模型轉(zhuǎn)換為內(nèi)存中的數(shù)據(jù)模型的統(tǒng)稱。數(shù)據(jù)模型可以是任何數(shù)據(jù)結(jié)構(gòu)或?qū)ο竽P?存儲模型可以是關(guān)系模型、XML、二進(jìn)制流等。持久化技術(shù)主要用于MVC模型中的model層。目前iOS平臺上主要使用如下的四種技術(shù):

一.NSUserDefaults(關(guān)鍵詞:屬性列表、xml序列化)

什么是NSUserDefaults?

在介紹NSUserDefaults之前,我們有必要先了解屬性列表的概念:屬性列表是一種基于xml序列化的數(shù)據(jù)永久存儲文件,又稱plist文件,原理是將一些基本數(shù)據(jù)類型讀寫進(jìn)plist文件(注:plist文件是xml格式文件,因為常用于存儲配置信息,所以又稱作plist格式文件)并以明文方式存儲于設(shè)備中。許多OC的基本數(shù)據(jù)類型(如NSArray、NSString等)本身提供了向plist文件讀寫的方法,但實際項目中我們用的更多的是NSUserDefaults,NSUserDefaults是蘋果基于屬性列表所封裝的一個單例類,該類提供了基本數(shù)據(jù)類型的plist文件存儲方法,因其使用方便,代碼易懂,NSUserDefaults成為了最常用的數(shù)據(jù)持久化方式之一。

NSUserDefaults常用方法
//從 NSUserDefaults 中取出 key 值所對應(yīng)的 Value
id = [[NSUserDefaults standardUserDefaults] objectForKey:(NSString *)];

//將數(shù)據(jù)對象存儲到 NSUserDefaults 中
[[NSUserDefaults standardUserDefaults] setObject:(id)
                                         forKey:(NSString *)];

//將數(shù)據(jù)對象從 NSUserDefaults 中移除
[[NSUserDefaults standardUserDefaults] removeObjectForKey(NSString *)];

//同步更新到Plist文件,當(dāng)修改了 NSUserDefaults 的數(shù)據(jù)后,必須進(jìn)行此步操作
[[NSUserDefaults standardUserDefaults] synchronize];
NSUserDefaults特點
  • NSUserDefaults常用于存儲OC基本數(shù)據(jù)類型,不適合存儲自定義對象,NSUserDefaults支持的數(shù)據(jù)類型有:NSNumber(NSInteger、float、double),NSString,NSDate,NSArray,NSDictionary,BOOL.
  • 自定義對象可以轉(zhuǎn)化成基本類型NSData后再使用NSUserDefaults進(jìn)行存儲,但并不常用。
  • 當(dāng)plist文件存儲的數(shù)據(jù)發(fā)生改變(寫操作)時,需要調(diào)用synchronize方法同步,否則數(shù)據(jù)無法同步保存。
  • Key值應(yīng)具有唯一性,重名時將覆蓋先前的key值。
  • 實際開發(fā)中,NSUserDefaults常用于存儲配置信息,優(yōu)點是簡便,缺點是所有數(shù)據(jù)都以明文存儲在plist文件中,容易被解讀導(dǎo)致安全性不高。

二.對象歸檔(關(guān)鍵詞:序列化)

什么是對象歸檔?

和屬性列表一樣,對象歸檔也是將對象寫入文件并保存在硬盤內(nèi),所以本質(zhì)上是另一種形式的序列化(儲存模型不同)。雖說任何對象都可以被序列化,但只有某些特定的對象才能放置到某個集合類(例如:NSArray、NSMutableArray、NSDictionary、NSData等)中,并使用該集合類的方法在屬性列表存儲中讀寫,一旦將包含了自定義對象的數(shù)組寫入屬性列表,程序就會報錯。歸檔與屬性列表方式不同,屬性列表只有指定的一些對象才能進(jìn)行持久化且明文存儲,而歸檔是任何實現(xiàn)了NSCopying協(xié)議的對象都可以被持久化,且歸檔后的文件是加密的。對象歸檔涉及兩個類:NSKeyedArchiver和NSKeyedUnarchiver,這兩個類是NSCoder的子類,分別用于歸檔以及解檔。下面將介紹如何對自定義對象進(jìn)行歸檔。

對象歸檔示例

現(xiàn)在,我們有一個自定義的Person類,該類有name,age,height三個屬性,其.h文件如下

//Person.h
#import<Foundation/foundation.h>
@interface Person:NSObject<NSCoding>
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)int age;
@property(nonatomic,assign)double height;

在.m文件中,我們要實現(xiàn)NSCoding中的兩個協(xié)議方法,這兩個方法分別在歸檔和解檔時會被調(diào)用,Person類的.m文件如下

//Person.m
#import"Person.h"
@implementation Person
/*
* 歸檔時調(diào)用該方法,該方法說明哪些數(shù)據(jù)需要儲存,怎樣儲存
*/
- (void)encodeWithCoder:(NSCoder *)encoder
{
    [encoder encodeObject:_name forKey:@"name"];
    [encoder encodeInt:_age forKey:@"age"];
    [encoder encodeDouble:_name forKey:@"height"];
}

/*
* 歸檔時調(diào)用該方法,該方法說明哪些數(shù)據(jù)需要解析,怎樣解析
*/
-(id)initWithCoder:(NSCoder *)decode
{
    if (self = [super init]) {
        _name = [decode decodeObjectForKey:@"name"];
        _age = [decode decodeIntForKey:@"age"];
        _height = [decode decodeDoubleForKey:@"height"];
    }
    return self;
}
@end

這個Person類就具有了歸檔與解檔能力,當(dāng)你需要對一個Person類的實力對象進(jìn)行儲存或者解析時,在你自己的方法中只要鍵入如下代碼即可,下面兩個方法對應(yīng)兩個按鈕的回調(diào),點擊按鈕時分別執(zhí)行person對象的歸檔和解檔。

//寫操作
- (IBAction)Write {
    Person *p = [[Person alloc]init];
    p.name = @"jin";
    p.age = 10;
    p.height = 176.0;

    //設(shè)置歸檔后文件路徑
    NSString *path = @"/Users/macbookair/Desktop/person.data";
    //歸檔
    [NSKeyedArchiver archiveRootObject:p toFile:path];
}

//讀操作
- (IBAction)read {
    
    //設(shè)置路徑
    NSString *path = @"/Users/macbookair/Desktop/person.data";
    
    //解檔
    Person *p = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    NSLog(@"%@--%d---%f",p.name ,p.age ,p.height);
    
}
對象歸檔特點
  • 可以將自定義對象寫入文件或從文件中讀出。
  • 由于歸檔時進(jìn)行了加密處理,因此安全性高于屬性列表。

三.CoreData(關(guān)鍵詞:集成化)

什么是CoreData?

當(dāng)你的應(yīng)用程序需要在本地存儲大量的關(guān)系型數(shù)據(jù)模型時,顯然上述方法并不適合,因為不論對象歸檔還是數(shù)據(jù)列表,一旦數(shù)據(jù)模型之間存在依賴關(guān)系,問題就將變得復(fù)雜。而此時iPhone自帶的輕量級數(shù)據(jù)庫Sqlite便成為我們的首選,如果你熟悉數(shù)據(jù)庫,那么恭喜,CoreData也將不再神秘,你可以理解為它是蘋果對Sqlite封裝的一個框架,你可以在Xcode中進(jìn)行Sqlite數(shù)據(jù)庫的可視化操作。倘若你對Sqlite感到陌生,那么本文最后也將對Sqlite進(jìn)行簡單的講解。

如何使用CoreData?

1.新建自帶CoreData文件的工程項目,當(dāng)你創(chuàng)建成功后,左邊文件列表里將看到xxx.xcdatamodeld。并且在AppDelegate類中會自動生成一系列相關(guān)屬性和方法用于管理CoreData。當(dāng)然你也可以為已有項目添加CoreData文件,不過AppDelegate類則需要你重新構(gòu)造相關(guān)方法。

1.新建帶CoreData的工程項目

2.點擊xxx.xcdatamodeld文件,你會看到一個CoreData文件相當(dāng)于一個數(shù)據(jù)庫,在這里你可以進(jìn)行建表、添加屬性、連接表與表之間的依賴關(guān)系等可視化操作。但是不能進(jìn)行數(shù)據(jù)的增刪改查,增刪改查需要代碼中自行實現(xiàn)。這里我們創(chuàng)建了名為User(用戶)和Department(部門)兩張表

2.相關(guān)設(shè)置

3.設(shè)置完表的屬性以及關(guān)系后,為每張表創(chuàng)建model。如圖所示,創(chuàng)建完成后左邊文件列表中會多每張表對應(yīng)的兩個文件(iOS7.0以后每張表對應(yīng)四個文件)


3.創(chuàng)建表的model文件

4.至此,CoreData基本配置已經(jīng)完成,User表和Department表之間也已經(jīng)建立起聯(lián)系,點擊style可以看到每張表的Attributes及表之間Relationships。

4.表的關(guān)系圖

5.如果你熟悉Sqlite,那么接下來我們要做的就是鍵入代碼進(jìn)行數(shù)據(jù)的增刪改查,實現(xiàn)增刪改查之前,我們需要先認(rèn)識幾個核心類。

NSManagedObjectContext: (管理對象上下文) 負(fù)責(zé)應(yīng)用和數(shù)據(jù)庫之間的交互

NSPersistentStoreCoordinator: (持久化存儲協(xié)調(diào)器)處理數(shù)據(jù)存儲的連接

NSManagedObjectModel: (對象模型) 代表CoreData模型文件,相當(dāng)于實體

NSEntityDescription: (實體結(jié)構(gòu)) 用來描述實體

NSPredicate: (查詢條件) 相當(dāng)于Sqlite中的Sql語句

NSFetchRequest: (數(shù)據(jù)請求) 可以給request設(shè)置請求的條件

如何對已經(jīng)建好實體表進(jìn)行增刪改查?最好的方法是自己再封裝一個單利類,該類能夠提供表名查詢方法,傳入某張表名后,我們便可以利用上述的幾個核心類提供的一系列方法創(chuàng)建數(shù)據(jù)的實例變量并讀寫進(jìn)表內(nèi),如果你是新建的工程項目并勾選了Use Core Data選項,那么AppDelegate類會自動生成一些CoreData的管理方法(這些方法在你自己封裝過程中可以借鑒)供我們直接使用。現(xiàn)僅以User表舉例實現(xiàn)增刪改查,以下.m文件里包含了四個xib創(chuàng)建的Button的回調(diào),當(dāng)你點擊按鈕后,分別會向User表增刪改查某個用戶的個人信息。

//ViewController.m

#import "ViewController.h"
#import "User.h"
#import "AppDelegate.h"
#import <CoreData/CoreData.h>

@interface ViewController()

@property(nonatomic,strong)AppDelegate *app;


@end



@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.app = [UIApplication sharedApplication].delegate;   
}

//insert增
- (IBAction)coreDataInsert {
    //1.初始化一個user數(shù)據(jù)
    User *user = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:self.app.managedObjectContext];
    user.name = @"lcc";
    user.sex= @"boy";
    user.age = @15;
    
    //2.保存
    [self.app.managedObjectContext save:nil];
}

//delete刪
- (IBAction) coreDataDelete {
    //1.讀取所有用戶
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:self.app.managedObjectContext];
    
    //2.建立請求
    NSFetchRequest *request = [[NSFetchRequest alloc]init];
    [request setEntity:entity];
    
    //3.設(shè)置查找條件
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name =%@",@"lcc"];
    [request setPredicate:predicate];
    
    //4.遍歷user表,找到該用戶后,刪除對象
    NSArray *array = [self.app.managedObjectContext executeFetchRequest:request error:nil];
    if(array.count){
        for(User *newUser in array){
            //刪除該用戶
            [self.app.managedObjectContext deleteObject:newUser];
        }
        //保存結(jié)果
        [self.app.managedObjectContext save:nil];
        NSLog(@"刪除成功");
    }else{
        NSLog(@"未找到該用戶數(shù)據(jù)");
    }
    
}

//upDate改
- (IBAction) coreDataUpdate {
    //1.讀取所有用戶
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:self.app.managedObjectContext];
    
    //2.建立請求
    NSFetchRequest *request = [[NSFetchRequest alloc]init];
    [request setEntity:entity];
    
    //3.設(shè)置查找條件
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@",@"lcc"];
    [request setPredicate:predicate];
    
    //4.遍歷user表,找到該用戶后,修改對象
    NSArray *array = [self.app.managedObjectContext executeFetchRequest:request error:nil];
    if(array.count){
        for(User *newUser in array){
            //修改該用戶
            newUser.name = @"lcc2";
        }
        //保存結(jié)果
        [self.app.managedObjectContext save:nil];
        NSLog(@"修改成功");
    }else{
        NSLog(@"未找到該用戶數(shù)據(jù)");
    }
}

//select查
- (IBAction) coreDataSelect {
    //1.讀取User表
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"User"inManagedObjectContext:self.app.managedObjectContext];
    
    //2.建立請求
    NSFetchRequest *request = [[NSFetchRequest alloc]init];
    [request setEntity:entity];
    
    //3.遍歷所有用戶,取出相關(guān)用戶
    NSArray *array = [self.app.managedObjectContext executeFetchRequest:request error:nil];
    for (User *user in array){
        NSLog(@"%@",user.name);
    }
}
@end

如此一來,我們便可以實現(xiàn)已經(jīng)實體化的User表的增刪改查,當(dāng)然,這里的增刪改查相當(dāng)簡便,實際項目中要進(jìn)行各種情況判斷,數(shù)據(jù)庫操作成功失敗會有返回信息,該信息應(yīng)該反饋給用戶。此外,因為CoreData并不是線程安全的,如果你希望自己封裝一個單利類,那么必須要考慮到數(shù)據(jù)庫的并發(fā)操作。

為什么要使用CoreData?
  • CoreData脫離了Sql語句,集成化更高。實際上,一個成熟的工程中一定是對數(shù)據(jù)持久化進(jìn)行了封裝的,應(yīng)該避免在業(yè)務(wù)邏輯中直接編寫Sql語句。
  • CoreData對版本遷移支持的較好,App升級之后數(shù)據(jù)庫字段或者表有更改會導(dǎo)致crash,CoreData的版本管理和數(shù)據(jù)遷移變得非常有用,手動寫sql語句操作相對麻煩一些。
  • CoreData不光能操縱SQLite,CoreData和iCloud的結(jié)合也很好,如果有這方面需求的話優(yōu)先考慮CoreData。
  • CoreData是支持多線程的,但需要thread confinement的方式實現(xiàn),使用了多線程之后可以最大化的防止阻塞主線程。

四.Sqlite(關(guān)鍵詞:靈活)

什么是Sqlite?

Sqlite是iPhone自帶的的數(shù)據(jù)庫管理系統(tǒng)。如果你對數(shù)據(jù)庫和Sql語句不陌生,那么在介紹完CoreData后,你一定不滿足CoreData,作為一個程序員,也許你更希望能夠直接操作數(shù)據(jù)庫。既然蘋果選擇Sqlite作為數(shù)據(jù)庫,自然也提供了一系列可以直接操作它的函數(shù)(C語言函數(shù)),利用這些函數(shù)你完全能夠自己封裝一個sqlite數(shù)據(jù)庫框架,但同時你必須熟悉sql語句以及C語言語法。我在gitHub上上傳了一個自己封裝的輕量級Sqlite框架LCCSqliteManager 。目前版本只實現(xiàn)了一些基本的數(shù)據(jù)庫功能,如果感興趣你可以參考一下。

如何直接操作Sqlite?

libsqlite3.0這個函數(shù)庫提供了許多C語言函數(shù)用于直接操作Sqlite,你的項目中導(dǎo)入該函數(shù)庫即可,具體導(dǎo)入方法LCCSqliteManager的文檔中有說明,這里不在過多詳述,僅介紹幾個核心函數(shù)。

/*
** 打開或創(chuàng)建數(shù)據(jù)庫
*  char * filePath : 文件路徑 (UTF-8) 
*  sqlite3 **ppDb  : 指向數(shù)據(jù)庫文件指針的指針 
*/
int sqlite3_open(const char* filePath, sqlite3** ppDb);

//你可以這樣調(diào)用該函數(shù)
sqlite3 *_sqlite;
NSString *filePath = [NSHomeDirectory()stringByAppendingFormat: @"/Documents/%@.sqlite",filename];

int result = sqlite3_open([filePath UTF8String], &_sqlite);
if (result != SQLITE_OK) {
    NSLog(@"error:數(shù)據(jù)庫%@打開失敗",filename)
}
/*
** 關(guān)閉數(shù)據(jù)庫
sqlite3* : 指向該數(shù)據(jù)庫的指針
*/
int sqlite3_close(sqlite3*);
/*
** 創(chuàng)建一張表
*/
SQLITE_API int SQLITE_STDCALL sqlite3_exec(
  sqlite3*,                                  /* 非空數(shù)據(jù)庫指針 */
  const char *sql,                           /* 用于創(chuàng)建表的SQL語句 */
  int (*callback)(void*,int,char**,char**),  /* 回調(diào)函數(shù) */
  void *,                                    /* 第一個參數(shù)回調(diào) */
  char **errmsg                              /* 錯誤信息 */
);

//第三、四個參數(shù)一般傳入NULL即可。你可以這樣調(diào)用該函數(shù)
char *error = NULL;
sqlite3 *_sqlite;
NSString *targetSql = @"CREATE TABLE User(name TEXT,age INT,SEX TEXT)"
int result = sqlite3_exec(_sqlite, [targetSql UTF8String], NULL, NULL, &error);
if (result != SQLITE_OK) {
    NSLog(@"創(chuàng)建表失敗:%s", error);
    return NO;
}
/*
** 預(yù)編譯sql語句
*/
  int sqlite3_prepare_v2(
  sqlite3 *db,            /* 非空數(shù)據(jù)庫指針 */
  const char *zSql,       /* 要執(zhí)行的SQL語句 */
  int nByte,              /* zSql字節(jié)的最大長度 */
  sqlite3_stmt **ppStmt,  /* 能夠使用sqlite3_step()執(zhí)行的編譯好的準(zhǔn)備語句的指針 */ 
  const char **pzTail     /* 超過zSql最大長度的的剩余部分*/
);

/*
** 執(zhí)行預(yù)編譯好的sql語句。
*/
int sqlite3_step(sqlite3_stmt*);

/*
** 釋放內(nèi)存。
*/
int sqlite3_finalize(sqlite3_stmt *pStmt);


上述三個方法配套使用,一般在含有查找操作的sql語句時都需要預(yù)編譯后再執(zhí)行,并且手動釋放內(nèi)存。

上面的方法比較常用,表的刪除和修改操作因為都需要查找操作,所以需要sqlite3_prepare_v2 和sqlite3_step配合使用,sqlite3_exec同樣可以執(zhí)行sql語句,因為增加和查找數(shù)據(jù)一般不需要預(yù)編譯。兩者具體的區(qū)別這里不在過多闡述,讀者可以根據(jù)參數(shù)的不同自行理解,或者查找網(wǎng)上有關(guān)libsqlite3.0函數(shù)庫的相關(guān)技術(shù)博客,該函數(shù)庫的大部分函數(shù)以及參數(shù)作用都有詳細(xì)解釋。

Sqlite和CoreData相比的優(yōu)劣

  • 直接操作Sqlite更容易理解數(shù)據(jù)的存儲方式,靈活度更高。
  • 在大量數(shù)據(jù)的批量讀寫速度上,Sqlite占有優(yōu)勢。
  • Sqlite需要自己寫Sql語句,且多線程、批量操作等都需要代碼實現(xiàn)。

總結(jié)

本文大致介紹了iOS的四種數(shù)據(jù)持久化方式,且對他們之間的關(guān)系進(jìn)行了一定講解,筆者在封裝過LCCSqliteManager后,反而更傾向于使用CoreData,因為其更穩(wěn)定便捷,而大部分項目中,NSUSerDefaults與CoreData基本可以滿足數(shù)據(jù)持久化需求,所以筆者較為推薦。

最后感謝閱讀,也歡迎提出寶貴意見或糾正錯誤。

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

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