網(wǎng)絡(luò)層緩存的思考(成長中的XDNetworking)

前言

最近一直在思考一個(gè)問題,網(wǎng)絡(luò)層的要如何設(shè)計(jì)后豫?

純粹的網(wǎng)絡(luò)層

何為純粹呢涌穆?就是服務(wù)器返回什么數(shù)據(jù),回調(diào)給業(yè)務(wù)層就是什么數(shù)據(jù)独旷。因?yàn)槲野l(fā)現(xiàn)有些基于AFNetworking封裝的網(wǎng)絡(luò)框架耦合太多業(yè)務(wù)的東西署穗,例如他們返回的數(shù)據(jù)都是經(jīng)過處理寥裂,很多人都直接轉(zhuǎn)化為模型數(shù)據(jù)返回。
設(shè)計(jì)一個(gè)App整體架構(gòu)的時(shí)候案疲,我傾向于各層次分明封恰,網(wǎng)絡(luò)層就純粹做網(wǎng)絡(luò)請求,持久層就純粹做強(qiáng)業(yè)務(wù)的數(shù)據(jù)的增刪查改褐啡,業(yè)務(wù)層就純粹做與業(yè)務(wù)相關(guān)的東西诺舔,各層之間通信不是直接耦合,而是通過構(gòu)建中間層來實(shí)現(xiàn)备畦,例如業(yè)務(wù)層與網(wǎng)絡(luò)層之間夾著Service層低飒,業(yè)務(wù)層與持久層之間夾著DataManager層。說回到我們的網(wǎng)絡(luò)層的設(shè)計(jì)懂盐,我的思路就是He gives me what逸嘀,I give you what。

緩存問題

引用iOS程序犭袁的一篇文章iOS網(wǎng)絡(luò)緩存掃盲篇--使用兩行代碼就能完成80%的緩存需求的觀點(diǎn)允粤,緩存按功能可以分為兩種:優(yōu)化型緩存和功能型緩存。優(yōu)化型緩存翼岁,可以類比瀏覽器的緩存类垫,它的好處就是減少相應(yīng)的延遲、減少網(wǎng)絡(luò)的帶寬消耗琅坡、更好的用戶體驗(yàn)(用戶不用空等菊花轉(zhuǎn))悉患,這種緩存是網(wǎng)絡(luò)層設(shè)計(jì)需要考慮的;而功能型緩存主要做離線緩存榆俺,這是持久層設(shè)計(jì)需要考慮售躁。
其實(shí)蘋果已經(jīng)幫我們提供一個(gè)緩存解決方案:NSURLCache,他依賴于HTTP的緩存機(jī)制茴晋,但是他并不是100%完美的:
1陪捷、只支持GET請求的緩存,有時(shí)候我們也會用POST請求獲取數(shù)據(jù)诺擅,然而這部分?jǐn)?shù)據(jù)就無法使用NSURLCache進(jìn)行緩存市袖;
2、不夠靈活烁涌,所有緩存都存儲在同一個(gè)文件(記得好像是sql文件苍碟,如果有錯(cuò),大家指正一下)撮执,不能分別指定每一個(gè)文件緩存的位置微峰;
3、不能指定緩存淘汰策略抒钱;

重復(fù)請求問題

為了刷新數(shù)據(jù)或者加載更多數(shù)據(jù)蜓肆,用戶會觸發(fā)上下拉刷新颜凯,當(dāng)網(wǎng)絡(luò)狀態(tài)不好的時(shí)候,用戶可能會不斷地去刷新症杏,這樣會觸發(fā)很多個(gè)重復(fù)的網(wǎng)絡(luò)請求装获,回調(diào)邏輯會觸發(fā)很多次,不作處理的話厉颤,不僅會浪費(fèi)用戶流量穴豫,還會造成數(shù)據(jù)錯(cuò)亂(數(shù)據(jù)列表存在很多重復(fù)的數(shù)據(jù))。

原有的AFNetworking沒有提供直接的方法解決上面的問題逼友,所以我在AFNetworking3.0的基礎(chǔ)上做了一層封裝精肃,XDNetworking就此誕生。

成長中的XDNetworking

XDNetworking是集約型的網(wǎng)絡(luò)框架帜乞,發(fā)起網(wǎng)絡(luò)請求集中在一個(gè)類上司抱,統(tǒng)一管理,適合中小型的項(xiàng)目黎烈,需要對網(wǎng)絡(luò)請求進(jìn)行更加細(xì)致的配置和管理习柠,這個(gè)網(wǎng)絡(luò)框架可能不太適合,這種需求需要一個(gè)離散型的網(wǎng)絡(luò)框架做支撐照棋。下一版的計(jì)劃就是將XDNetworking轉(zhuǎn)化為一個(gè)離散型的網(wǎng)絡(luò)框资溃。大家敬請期待。

框架結(jié)構(gòu)

框架結(jié)構(gòu)
框架結(jié)構(gòu)

XDNetworking:提供調(diào)用的API
RequestManager目錄:存放請求管理相關(guān)的類
Cache目錄:存放緩存管理相關(guān)的類
AFSourceCore目錄:存放AFNetworking的源碼

API設(shè)計(jì)

API面向業(yè)務(wù)更加友好烈炭,回調(diào)方式采用block溶锭,基礎(chǔ)功能包括GET、POST符隙、下載趴捅、單文件上傳、多文件上傳霹疫、請求管理拱绑、緩存管理

GET

+ (XDURLSessionTask *)getWithUrl:(NSString *)url
                  refreshRequest:(BOOL)refresh
                           cache:(BOOL)cache
                          params:(NSDictionary *)params
                   progressBlock:(XDGetProgress)progressBlock
                    successBlock:(XDResponseSuccessBlock)successBlock
                       failBlock:(XDResponseFailBlock)failBlock;

POST

+ (XDURLSessionTask *)postWithUrl:(NSString *)url
                   refreshRequest:(BOOL)refresh
                            cache:(BOOL)cache
                           params:(NSDictionary *)params
                    progressBlock:(XDPostProgress)progressBlock
                     successBlock:(XDResponseSuccessBlock)successBlock
                        failBlock:(XDResponseFailBlock)failBlock;

Download

+ (XDURLSessionTask *)downloadWithUrl:(NSString *)url
                        progressBlock:(XDDownloadProgress)progressBlock
                         successBlock:(XDDownloadSuccessBlock)successBlock
                            failBlock:(XDDownloadFailBlock)failBlock;

Upload

+ (XDURLSessionTask *)uploadFileWithUrl:(NSString *)url
                               fileData:(NSData *)data
                                   type:(NSString *)type
                                   name:(NSString *)name
                               mimeType:(NSString *)mimeType
                          progressBlock:(XDUploadProgressBlock)progressBlock
                           successBlock:(XDResponseSuccessBlock)successBlock
                              failBlock:(XDResponseFailBlock)failBlock;

請求相關(guān)

/**
 *  正在運(yùn)行的網(wǎng)絡(luò)任務(wù)
 *
 *  @return 
 */
+ (NSArray *)currentRunningTasks;

/**
 *  取消GET請求
 */
+ (void)cancelRequestWithURL:(NSString *)url;

/**
 *  取消所有請求
 */
+ (void)cancleAllRequest;

緩存相關(guān)

@interface XDNetworking (cache)

/**
 *  獲取緩存目錄路徑
 *
 *  @return 緩存目錄路徑
 */
+ (NSString *)getCacheDiretoryPath;

/**
 *  獲取下載目錄路徑
 *
 *  @return 下載目錄路徑
 */
+ (NSString *)getDownDirectoryPath;

/**
 *  獲取緩存大小
 *
 *  @return 緩存大小
 */
+ (NSUInteger)totalCacheSize;

/**
 *  清除所有緩存
 */
+ (void)clearTotalCache;

/**
 *  獲取所有下載數(shù)據(jù)大小
 *
 *  @return 下載數(shù)據(jù)大小
 */
+ (NSUInteger)totalDownloadDataSize;

/**
 *  清除下載數(shù)據(jù)
 */
+ (void)clearDownloadData;

@end

重復(fù)請求管理

大家會發(fā)現(xiàn)GET和POST的API有refresh參數(shù),這個(gè)參數(shù)的主要目的是用于刷新請求丽蝎,當(dāng)遇到重復(fù)請求時(shí)欺栗,若為YES,則會取消舊的請求征峦,用新的請求迟几,若為NO,則忽略新請求栏笆,用舊請求类腮,大家針對自己的業(yè)務(wù)需求自己取舍。下面我給大家分析是如何判斷重復(fù)和刷新請求的蛉加。
大家可以點(diǎn)進(jìn)GET請求或者POST請求的源碼查看他的判斷方法:

if ([self haveSameRequestInTasksPool:session] && !refresh) {
        //取消新請求
        [session cancel];
        return session;
    }else {
        //無論是否有舊請求蚜枢,先執(zhí)行取消舊請求缸逃,反正都需要刷新請求
        XDURLSessionTask *oldTask = [self cancleSameRequestInTasksPool:session];
        if (oldTask) [[self allTasks] removeObject:oldTask];
        if (session) [[self allTasks] addObject:session];
        [session resume];
        return session;
    }

判斷的相關(guān)邏輯在XDNetworking+requestManager.h這個(gè)分類文件中,大家可以看下它提供的API厂抽,注釋在代碼中:

/**
 *  判斷網(wǎng)絡(luò)請求池中是否有相同的請求
 *
 *  @param task 網(wǎng)絡(luò)請求任務(wù)
 *
 *  @return
 */
+ (BOOL)haveSameRequestInTasksPool:(XDURLSessionTask *)task;

/**
 *  如果有舊請求則取消舊請求
 *
 *  @param task 新請求
 *
 *  @return 舊請求
 */
+ (XDURLSessionTask *)cancleSameRequestInTasksPool:(XDURLSessionTask *)task;

判斷一個(gè)請求是否重復(fù)需频,也就是判斷新來的請求和舊的請求是否一樣,判斷的依據(jù)有以下幾點(diǎn):
1筷凤、請求的方法是否相同昭殉,是否同為GET或者同為POST;
2藐守、請求的url是否相同挪丢,如果是GET請求,到這一步就可以做出判斷了卢厂,如果是POST請求乾蓬,則還需進(jìn)行下一步的驗(yàn)證;
3慎恒、請求體的內(nèi)容是否相同(POST請求的參數(shù)放在HTTP body里)任内;
于是我為NSURLRequest拓展一個(gè)分類用于判斷請求異同:

@implementation NSURLRequest (decide)

- (BOOL)isTheSameRequest:(NSURLRequest *)request {
    if ([self.HTTPMethod isEqualToString:request.HTTPMethod]) {
        if ([self.URL.absoluteString isEqualToString:request.URL.absoluteString]) {
            if ([self.HTTPMethod isEqualToString:@"GET"]||[self.HTTPBody isEqualToData:request.HTTPBody]) {
                return YES;
            }
        }
    }
    return NO;
}

@end

于是乎股缸,我們可以遍歷XDNetworking當(dāng)前的運(yùn)行任務(wù)(調(diào)用currentRunningTasks獲取當(dāng)前的運(yùn)行任務(wù))强挫,根據(jù)任務(wù)源請求判斷新來的請求,是否已經(jīng)有相同的請求正在執(zhí)行當(dāng)中:

+ (BOOL)haveSameRequestInTasksPool:(XDURLSessionTask *)task {
    __block BOOL isSame = NO;
    [[self currentRunningTasks] enumerateObjectsUsingBlock:^(XDURLSessionTask *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([task.originalRequest isTheSameRequest:obj.originalRequest]) {
            isSame  = YES;
            *stop = YES;
        }
    }];
    return isSame;
}

取消舊請求的邏輯也很容易實(shí)現(xiàn)二庵,遍歷獲取重復(fù)的請求丹鸿,調(diào)用它的cancle方法就取消舊請求了。

緩存方案的設(shè)計(jì)

先說說如何啟動我們的緩存機(jī)制棚品,GET和POST的API有一個(gè)cache參數(shù)靠欢,它用于給大家決定是否開啟緩存機(jī)制,大家針對自己的業(yè)務(wù)數(shù)據(jù)的特征來決定是否開啟cache,即時(shí)性或時(shí)效性的數(shù)據(jù)建議不開啟緩存铜跑,一般建議開啟门怪,開啟緩存后會回調(diào)兩次,第一次獲取是緩存數(shù)據(jù)锅纺,第二次獲取的是最新的網(wǎng)絡(luò)數(shù)據(jù)掷空。
在上面我們已經(jīng)分析了NSURLCache的局限性,而且基于HTTP緩存機(jī)制做的緩存需要客戶端和服務(wù)器雙邊配合囤锉。所以這里我自己設(shè)計(jì)了一個(gè)網(wǎng)絡(luò)的緩存方案坦弟,思路來源SDWebImage。
我的緩存方案分兩級緩存:內(nèi)存緩存和磁盤緩存官地,緩存的過程我給大家簡單的梳理下:第一次請求獲取響應(yīng)數(shù)據(jù)酿傍,先緩存到內(nèi)存,再緩存到磁盤驱入,下一次再發(fā)起相同的請求時(shí)赤炒,會先查找內(nèi)存之中會不會有相應(yīng)的緩存氯析,如果有則返回緩存數(shù)據(jù),如果沒有莺褒,則向磁盤查找掩缓,如果磁盤存在緩存則返回,否則發(fā)起網(wǎng)絡(luò)請求獲取數(shù)據(jù)遵岩。
上面就是緩存的整一個(gè)過程你辣,思路還是比較清晰,除此之外旷余,緩存的設(shè)計(jì)還需要考慮兩個(gè)問題:
1绢记、緩存的淘汰策略
2、緩存的過期機(jī)制
針對以上那些內(nèi)容正卧,我通過解析源碼的方式給大家過一遍:
緩存相關(guān)的類放在Cache這個(gè)目錄下:

Cache
Cache

XDCacheManager是一個(gè)緩存管理類蠢熄,暴露出簡單的API給XDNetworking進(jìn)行緩存的存取,底層是使用XDMemoryCache(NSCache)進(jìn)行內(nèi)存緩存炉旷,使用XDDiskCache(NSFileManager)進(jìn)行磁盤緩存签孔,緩存淘汰策略采用LRU算法(XD_LRUManager)。它是一個(gè)單例窘行,通過一個(gè)全局入口統(tǒng)一訪問:

+ (XDCacheManager *)shareManager;

默認(rèn)是磁盤大小是40MB饥追,有效期是7天,如果想自定義設(shè)置罐盔,可以通過以下方法設(shè)置:

- (void)setCacheTime:(NSTimeInterval) time diskCapacity:(NSUInteger) capacity;

API:

存緩存的調(diào)用棧:

- [XDCacheManager cacheResponseObject:requestUrl:params:]
    - [XDMemoryCache writeData:forKey:]
    - [XDDiskCache writeData:toDir:filename:]
    - [XD_LRUManager addFileNode:]

取緩存的調(diào)用棧:

- [XDCacheManager getCacheResponseObjectWithRequestUrl:params:]
    - [XDMemoryCache readDataWithKey:]
    - [XDDiskCache readDataFromDir:filename:]
    - [XD_LRUManager refreshIndexOfFileNode:]

刪除LRU緩存的調(diào)用棧:

- [XDCacheManager clearLRUCache]
    - [XD_LRUManager removeLRUFileNodeWithCacheTime:]
    - [XDDiskCache deleteCache:]

每一份緩存都有一個(gè)唯一的索引建但绕,從方法的參數(shù)可以看出這個(gè)鍵是由請求的url和請求參數(shù)決定,大家不用擔(dān)心接口和參數(shù)的暴露問題惶看,鍵不是直接url加參數(shù)捏顺,而是兩者共同作用的哈希值(采用MD5哈希算法)。
這里重點(diǎn)講下XD_LRUManager做了哪些處理纬黎。

XD_LRUManager

XD_LRUManager是一個(gè)基于LRU(最近最少使用算法)實(shí)現(xiàn)的緩存數(shù)據(jù)管理類幅骄,它底層是由一個(gè)動態(tài)數(shù)組實(shí)現(xiàn)的隊(duì)列,數(shù)組的元素是字典本今,字典包含兩個(gè)鍵:fileName(緩存文件名字)和date(緩存文文件最近的訪問時(shí)間)拆座,這個(gè)隊(duì)列保存在NSUserDefault里。
LRU算法的實(shí)現(xiàn):
創(chuàng)建一個(gè)隊(duì)列冠息,新加的結(jié)點(diǎn)添加在隊(duì)列的尾部挪凑;命中緩存時(shí),調(diào)整結(jié)點(diǎn)的位置逛艰,將其放在隊(duì)列的尾部岖赋;要淘汰緩存時(shí),刪除隊(duì)列的頭部結(jié)點(diǎn)瓮孙。
應(yīng)用情景:
1唐断、當(dāng)有數(shù)據(jù)緩存時(shí)选脊,會調(diào)用XD_LRUManager的addFileNode方法在LRU隊(duì)列上記錄一個(gè)文件結(jié)點(diǎn),文件結(jié)點(diǎn)也就是上面解釋的字典脸甘,記錄文件名和此時(shí)的訪問時(shí)間恳啥,先判斷隊(duì)列是否已經(jīng)存在同文件名的結(jié)點(diǎn),如果有則將結(jié)點(diǎn)取出并插入到隊(duì)列的尾部丹诀,沒有則直接插入到尾部钝的。
在遍歷隊(duì)列查找同文件名的結(jié)點(diǎn)的時(shí)候我做了遍歷優(yōu)化,先將隊(duì)列逆序铆遭,再查找硝桩,因?yàn)樵谖膊康慕Y(jié)點(diǎn)被重用的概率會大一些,從尾部查找會減少遍歷的次數(shù):

//優(yōu)化遍歷
    NSArray *reverseArray = [[array reverseObjectEnumerator] allObjects];
    
    [reverseArray enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj[@"fileName"] isEqualToString:filename]) {
            [operationQueue removeObjectAtIndex:idx];
            *stop = YES;
        }

    }];

2枚荣、當(dāng)使用緩存的時(shí)候碗脊,說明緩存文件被訪問,所有應(yīng)修改LRU隊(duì)列對應(yīng)文件結(jié)點(diǎn)的最近訪問并它插入LRU隊(duì)列的尾部橄妆,我們通過調(diào)用
XD_LRUManager的refreshIndexOfFileNode方法實(shí)現(xiàn)衙伶,它的實(shí)現(xiàn)原理跟addFileNode一樣。
3害碾、當(dāng)刪除LRU緩存的時(shí)候矢劲,調(diào)用XD_LRUManager的removeLRUFileNodeWithCacheTime方法,他需要傳一個(gè)有效期的參數(shù)慌随,這個(gè)參數(shù)由上層的XDCacheManager提供芬沉。遍歷LRU隊(duì)列,從頭部開始刪除阁猜,刪掉已經(jīng)過期的文件結(jié)點(diǎn)丸逸,用一個(gè)數(shù)組保存刪除的文件結(jié)點(diǎn)里的文件名,用于回調(diào)給上層通過文件名刪除真正的磁盤緩存蹦漠。如果發(fā)現(xiàn)沒有文件過期,則刪除頭結(jié)點(diǎn)车海,它對應(yīng)著最近最少使用的文件:

NSArray *tmpArray = [operationQueue copy];
        
        [tmpArray enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL * _Nonnull stop) {
            NSDate *date = obj[@"date"];
            NSDate *newDate = [date dateByAddingTimeInterval:time];
            if ([[NSDate date] compare:newDate] == NSOrderedDescending) {
                [result addObject:obj[@"fileName"]];
                [operationQueue removeObjectAtIndex:idx];
            }
        }];

這里有個(gè)注意點(diǎn),就是每次操作完LRU隊(duì)列笛园,無論是增刪查改,都要強(qiáng)制刷新LRU隊(duì)列在NSUserDefault的緩存侍芝。

源碼地址

https://github.com/caixindong/XDNetworking
大家在使用的過程中出現(xiàn)什么問題或者有什么地方不清楚研铆,歡迎來github issue 我。如果大家覺得不錯(cuò)州叠,give me a star棵红。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市咧栗,隨后出現(xiàn)的幾起案子逆甜,更是在濱河造成了極大的恐慌虱肄,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件交煞,死亡現(xiàn)場離奇詭異咏窿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)素征,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門集嵌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人御毅,你說我怎么就攤上這事根欧。” “怎么了端蛆?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵凤粗,是天一觀的道長。 經(jīng)常有香客問我欺税,道長侈沪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任晚凿,我火速辦了婚禮亭罪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘歼秽。我一直安慰自己应役,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布燥筷。 她就那樣靜靜地躺著箩祥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肆氓。 梳的紋絲不亂的頭發(fā)上袍祖,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音谢揪,去河邊找鬼蕉陋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拨扶,可吹牛的內(nèi)容都是我干的凳鬓。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼患民,長吁一口氣:“原來是場噩夢啊……” “哼缩举!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤仅孩,失蹤者是張志新(化名)和其女友劉穎托猩,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杠氢,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡站刑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鼻百。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绞旅。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖温艇,靈堂內(nèi)的尸體忽然破棺而出因悲,到底是詐尸還是另有隱情,我是刑警寧澤勺爱,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布晃琳,位于F島的核電站,受9級特大地震影響琐鲁,放射性物質(zhì)發(fā)生泄漏卫旱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一围段、第九天 我趴在偏房一處隱蔽的房頂上張望顾翼。 院中可真熱鬧,春花似錦奈泪、人聲如沸适贸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拜姿。三九已至,卻和暖如春冯遂,著一層夾襖步出監(jiān)牢的瞬間蕊肥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工蛤肌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留壁却,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓寻定,卻偏偏與公主長得像儒洛,于是被迫代替她去往敵國和親精耐。 傳聞我的和親對象是個(gè)殘疾皇子狼速,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,502評論 25 707
  • AFHTTPRequestOperationManager 網(wǎng)絡(luò)傳輸協(xié)議UDP、TCP卦停、Http向胡、Socket恼蓬、X...
    Carden閱讀 4,322評論 0 12
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)僵芹,斷路器处硬,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 木心公益書屋共同成長之《我讀我心》 第6天打卡(2017.04.23) 姓名:花緣過客花藝館 分享內(nèi)容:《易經(jīng)雜說...
    花緣過客閱讀 2,454評論 1 2
  • 來簡書的原因是因?yàn)樵谖⑿趴吹揭槐楦韶浳年P(guān)于教我們怎么很輕松地寫上一千字的文章。本來收藏得好好地拇派,又不見...
    lisalisa閱讀 145評論 0 1