AFNetworking源碼分析

說到AFNetwokring這個強大第三方網(wǎng)絡請求庫,大家應該都不陌生吧,ios開發(fā)、mac開發(fā)都經(jīng)常用,主要是他使用起來簡單、方便。下面我們看看他的源碼,來探討一下吧。

首先,我們一起來看一下它的框架的組成部分吧。


上面的圖片我從AFNetworking的文件中接的圖,我們可以看出,包含5個部分,其實AFHTTPSessionManager是AFURLSessionManager的子類,所以說它的組成是四個部分:

網(wǎng)絡通信模塊(AFHTTPSessionmanager、AFURLSessionManager)

網(wǎng)絡狀態(tài)監(jiān)聽模塊(AFNetworkReachabilityManager)

網(wǎng)絡通信信息序列化反序列化策略模塊(AFURLRequestSErialization、AFURLResponseSerialization)

網(wǎng)絡通信安全策略模塊(AFSecurityPolicy)

對ios UIKit庫的拓展

其核心當然就是網(wǎng)絡通信模塊AFURLSessionManager,這個類是對NSURLSession的進一步的封裝

其他模塊均是配合網(wǎng)絡通信或?qū)σ延蠻IKIt的擴展

一、初始化方法

最終都會到- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration方法中來了。

這個方法主要做了什么事情呢?

1、調(diào)父類的- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration 這個方法里面設置了

隊列為非主線程隊列,隊列的并發(fā)數(shù)為1,

初始化了session,設置了代理,

初始化了網(wǎng)絡通信監(jiān)聽、網(wǎng)絡安全策略

,初始化了保存網(wǎng)絡請求任務、對應的任務代理到字典中?

遍歷session中的任務,將任務對應的任務代理設為nil,請求代理

2、設置默認AFURLRequestSErialization、AFURLResponseSerialization,分別是請求序列對象和響應序列對象,這個兩個東西會在NSURLRequest中設置請求頭、請求體等一些信息中用到

上面的分析,可以看到,主要的東西還在父類中做的,里面初始化方法里便利session,將session中任務的代理清空是一種防御性編程。

然后來看GET請求

- (NSURLSessionDataTask *)GET:(NSString *)URLString參數(shù):(ID)參數(shù)進度:(void(^)(NSProgress * _Nonnull))downloadProgress成功:(無效(^)(NSURLSessionDataTask * _Nonnull,id _Nullable))成功失敗:(void(^)(NSURLSessionDataTask * _Nullable,NSError * _Nonnull))失敗

{//生成一個任務

NSURLSessionDataTask * dataTask = [self dataTaskWithHTTPMethod:@“GET”URLString:URLString參數(shù):參數(shù)上傳進度:無downloadProgress:downloadProgress成功:成功失敗:失敗];

//開始網(wǎng)絡請求[dataTask resume];返回dataTask;}

這里主要生成一個NSURLSessionDataTask來進行網(wǎng)絡請求

我們繼續(xù)往父類里看,看看這個方法到底做了什么:

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)方法URLString:(NSString *)URLString參數(shù):(ID)參數(shù)uploadProgress:(可為空(void)(^)(NSProgress * uploadProgress))uploadProgressdownloadProgress :(可空(void)(^)(NSProgress * downloadProgress))downloadProgress成功:(void(^)(NSURLSessionDataTask *,id))成功失敗:(void(^)(NSURLSessionDataTask *,NSError *))失敗{NSError * serializationError = nil;

//把參數(shù),還有各種東西轉(zhuǎn)化為一個請求

NSMutableURLRequest * request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];

If(serializationError){如果(失敗)

{#pragma clang診斷推送#pragma clang診斷忽略“-WgnU”//如果解析錯誤,直接返回dispatch_async(self.completionQueue?:dispatch_get_main_queue(),^ {失敗(nil,serializationError);});

#pragma clang診斷流行}返回零;}__block NSURLSessionDataTask * dataTask = nil;dataTask = [self dataTaskWithRequest:request上傳進度:上傳進度downloadProgress:downloadProgresscompletionHandler:^(NSURLResponse * __unused response,id responseObject,NSError * error){如果(錯誤){如果(失敗){失敗(dataTask,錯誤);}} else {如果(成功){成功(dataTask,responseObject);}}}];返回dataTask;}

這個

這個方法做了兩件事:

1、用self.requestSerializer和各種參數(shù)去獲取一個最終網(wǎng)絡請求需要的NSMutableURLRequest

2、調(diào)用另外一個方法dataTaskWithRequest去拿到我們需要NSURLSessionDataTask,并且在回掉中調(diào)用成功或失敗的回掉

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)方法URLString:(NSString *)URLString參數(shù):(ID)參數(shù)uploadProgress:(可為空(void)(^)(NSProgress * uploadProgress))uploadProgressdownloadProgress :(可空(void)(^)(NSProgress * downloadProgress))downloadProgress成功:(void(^)(NSURLSessionDataTask *,id))成功失敗:(void(^)(NSURLSessionDataTask *,NSError *))失敗{NSError * serializationError = nil;//把參數(shù),還有各種東西轉(zhuǎn)化為一個請求NSMutableURLRequest * request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];if(serializationError){如果(失敗){#pragma clang診斷推送#pragma clang診斷忽略“-WgnU”//如果解析錯誤,直接返回dispatch_async(self.completionQueue?:dispatch_get_main_queue(),^ {失敗(nil,serializationError);});#pragma clang診斷流行}返回零;}__block NSURLSessionDataTask * dataTask = nil;dataTask = [self dataTaskWithRequest:request上傳進度:上傳進度downloadProgress:downloadProgresscompletionHandler:^(NSURLResponse * __unused response,id responseObject,NSError * error){如果(錯誤){如果(失敗){失敗(dataTask,錯誤);}} else {如果(成功){成功(dataTask,responseObject);}}}];返回dataTask;}

我們繼續(xù)往下看:當解析錯誤,我們直接調(diào)用failuer失敗模塊回去,里面有個self.completionQueue,這是我們自定義的,這個是gcd隊列,如果設置就從這個隊列中回掉了,不從主隊列中回掉了

實際上這個隊列還是很有用,有些公司有自己的一套數(shù)據(jù)加密解密解析模式,所以我們回掉過來的數(shù)據(jù)并不想在主隊列,我們可以在這個隊列對數(shù)據(jù)進行解析,然后在回到主線程中。

(NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)請求uploadProgress:(可為空(void)(^)(NSProgress * uploadProgress))uploadProgressBlockdownloadProgress:(可空(void)(^)(NSProgress * downloadProgress))downloadProgressBlockcompletionHandler:(可空(void)(^)(NSURLResponse *響應,id _Nullable響應對象,NSError * _Nullable錯誤))completionHandler {__block NSURLSessionDataTask * dataTask = nil;//第一件事,創(chuàng)建NSURLSessionDataTask,里面適配了iOS8上以下taskIdentifiers,函數(shù)創(chuàng)建任務對象。//其實現(xiàn)應該是因為iOS 8.0以下版本中會并發(fā)地創(chuàng)建多個任務對象,而同步有沒有好,導致taskIdentifiers不唯一...這邊做了一個串行處理url_session_manager_create_task_safely(^ {dataTask = [self.session dataTaskWithRequest:request];});[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];返回dataTask;}

我們注意到這個方法非常簡單,就調(diào)用了一個url_session_manager_create_task_safely()函數(shù),傳了一個塊進去座里就是iOS的原生生成dataTask的方法。此外,還調(diào)用了一個addDelegateForDataTask的方法。

我們到這先到這個函數(shù)里去看看:

static void url_session_manager_create_task_safely(dispatch_block_t block){if(NSFoundationVersionNumber

方法非常簡單,關鍵是理解這么做的目的:為什么我們不直接去調(diào)用

dataTask = [self.session dataTaskWithRequest:request];

非要繞這么一圈,我們點進去錯誤日志里看看,原來這是為了適配iOS8上的以下,創(chuàng)建會話的時候,偶發(fā)的情況會出現(xiàn)會話的屬性taskIdentifier這個值不唯一,而這個taskIdentifier是我們后面來映射代表的關鍵,所以它必須是唯一的。

具體原因應該是NSURLSession內(nèi)部去生成任務的時候是用多線程并發(fā)去執(zhí)行的。想通了這一點,我們就很好解決了,我們只需要在iOS8上以下同步串行的去生成任務就可以防止這一問題發(fā)生(如果還是不理解同步串行的原因,可以看看注釋)。

題外話:很多同學都會抱怨為什么同步我從來用不到,看,有用到的地方了吧,很多東西不是沒用,而只是你想不到怎么用。

我們接著看到:

[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

調(diào)用到:

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTaskuploadProgress:(可為空(void)(^)(NSProgress * uploadProgress))uploadProgressBlockdownloadProgress:(可空(void)(^)(NSProgress * downloadProgress))downloadProgressBlockcompletionHandler:(void(^)(NSURLResponse * response,id responseObject,NSError * error))completionHandler{AFURLSessionManagerTaskDelegate * delegate = [[AFURLSessionManagerTaskDelegate alloc] init];// AFURLSessionManagerTaskDelegate與AFURLSessionManager建立相互的關系delegate.manager = self;delegate.completionHandler = completionHandler;//這個taskDescriptionForSessionTasks用來發(fā)送開始和掛起通知的時候會用到,就是用這個值來張貼通知,來兩者對應dataTask.taskDescription = self.taskDescriptionForSessionTasks;// *****將AF委托對象與dataTask建立關系[self setDelegate:delegate forTask:dataTask];//設置AF委托的上傳進度,下載進度塊。delegate.uploadProgressBlock = uploadProgressBlock;delegate.downloadProgressBlock = downloadProgressBlock;}

總結(jié)一下:

1)這個方法,生成了一個AFURLSessionManagerTaskDelegate,這個其實就是AF的自定義代理。我們請求傳來的參數(shù),都賦值給這個AF的代理了。

2)delegate.manager = self;代理把AFURLSessionManager這個類作為屬性了,我們可以看到:

@屬性(非原子,弱)AFURLSessionManager *管理器;

這個屬性是弱引用的,所以不會存在循環(huán)引用的問題。

3)我們調(diào)用了[self setDelegate:delegate forTask:dataTask];

我們進去看看這個方法做了什么:

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)委托forTask:(NSURLSessionTask *)任務{(diào)//斷言,如果沒有這個參數(shù),調(diào)試下墜毀在這NSParameterAssert(任務);NSParameterAssert(代表);//加鎖保證字典線程安全[self.lock lock];//將AF委托放入以taskIdentifier標記的詞典中(同一個NSURLSession中的taskIdentifier是唯一的)self.mutableTaskDelegatesKeyedByTaskIdentifier [@(task.taskIdentifier)] = delegate;//為AF代表設置任務的進度監(jiān)聽[委托setupProgressForTask:任務];//添加任務開始和暫停的通知[self addNotificationObserverForTask:task];[self.lock解鎖];}

這個方法主要就是把AF代理和任務建立映射,存在了一個我們事先聲明好的字典里。

而要加鎖的原因是因為本身我們這個字典屬性是可變的,是線程不安全的。而我們對這些方法的調(diào)用,確實是會在復雜的多線程環(huán)境中,后面會仔細提到線程問題。

還有個[delegate setupProgressForTask:task];我們到方法里去看看:

- (void)setupProgressForTask:(NSURLSessionTask *)task {__weak __typeof __(task)weakTask = task;//拿到上傳下載期望的數(shù)據(jù)大小self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;//將上傳與下載進度和任務綁定在一起,直接取消掛起恢復進度條,可以取消...任務[self.uploadProgress setCaslable:YES];[self.uploadProgress setCancellationHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strong任務取消];}];[self.uploadProgress setPausable:YES];[self.uploadProgress setPausingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask暫停];}];如果([self.uploadProgress respondsToSelector:@selector(setResumingHandler :)]){[self.uploadProgress setResumingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask簡歷];}];}[self.downloadProgress setCancellable:YES];[self.downloadProgress setCancellationHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strong任務取消];}];[self.downloadProgress setPausable:YES];[self.downloadProgress setPausingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask暫停];}];如果([self.downloadProgress respondsToSelector:@selector(setResumingHandler :)]){[self.downloadProgress setResumingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask簡歷];}];}//觀察任務的這些屬性[任務addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))選項:NSKeyValueObservingOptionNew上下文:NULL];[任務addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))選項:NSKeyValueObservingOptionNew上下文:NULL];[任務addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesSent))選項:NSKeyValueObservingOptionNew上下文:NULL];[任務addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))選項:NSKeyValueObservingOptionNew上下文:NULL];//觀察進度這兩個屬性[self.downloadProgress addObserver:selfforKeyPath:NSStringFromSelector(@selector(fractionCompleted))選項:NSKeyValueObservingOptionNew上下文:NULL];[self.uploadProgress addObserver:selfforKeyPath:NSStringFromSelector(@selector(fractionCompleted))選項:NSKeyValueObservingOptionNew上下文:NULL];}

這個方法也非常簡單,主要做了以下幾件事:

1)設置downloadProgress與uploadProgress的一些屬性,并且把兩個和任務的任務狀態(tài)綁定在了一起。注意這兩個都是NSProgress的實例對象,(這里可能又一群小伙楞在這了,這是個什么...)簡單來說,這就是iOS7引進的一個用來管理進度的類,可以開始,暫停,取消,完整的對應了任務的各種狀態(tài),當進度進行各種操作的時候,任務也會引發(fā)對應操作。

2)給的任務和進度的各個屬及添加志愿監(jiān)聽,至于監(jiān)聽了干什么用,我們接著往下看:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {//是任務如果([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]){//給進度條賦新值如果([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]){self.downloadProgress.completedUnitCount = [更改[NSKeyValueChangeNewKey] longLongValue];} else if([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]){self.downloadProgress.totalUnitCount = [change [NSKeyValueChangeNewKey] longLongValue];} else if([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]){self.uploadProgress.completedUnitCount = [更改[NSKeyValueChangeNewKey] longLongValue];} else if([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]){self.uploadProgress.totalUnitCount = [change [NSKeyValueChangeNewKey] longLongValue];}}//上面的賦新值會觸發(fā)這兩個,調(diào)用塊回調(diào),用戶拿到進度else if([object isEqual:self.downloadProgress]){if(self.downloadProgressBlock){self.downloadProgressBlock(對象);}}else if([object isEqual:self.uploadProgress]){if(self.uploadProgressBlock){self.uploadProgressBlock(對象);}}}

方法非常簡單直觀,主要就是如果任務觸發(fā)志愿,則給進度進度賦值,應為賦值了,所以會觸發(fā)進步的志愿,也會調(diào)用到這里,然后去執(zhí)行我們傳進來的downloadProgressBlock和uploadProgressBlock。主要的作用就是為了讓進度實時的傳遞。

主要是觀摩一下大神的寫代碼的結(jié)構(gòu),這個解耦的編程思想,不愧是大神...

還有一點需要注意:我們之前的setProgress和這個志愿監(jiān)聽,都是在我們AF自定義的委托內(nèi)的,是有一個任務就會有一個代表的所以說我們是每個任務都會去監(jiān)聽這些屬性,分別在各自的AF代理內(nèi)。看到這,可能有些小伙伴會有點亂,沒關系。等整個講完之后我們還會詳細的去講捋一捋經(jīng)理,任務,還有AF自定義代理三者之前的對應關系。

到這里我們整個對任務的處理就完成了。

2,HTTPS認證- (void)URLSession:(NSURLSession *)會話didReceiveChallenge:(NSURLAuthenticationChallenge *)挑戰(zhàn)completionHandler:(void(^)(NSURLSessionAuthChallengeDisposition disposition,NSURLCredential * credential))completionHandler{//挑戰(zhàn)處理類型為默認/ *NSURLSessionAuthChallengePerformDefaultHandling:默認方式處理NSURLSessionAuthChallengeUseCredential:使用指定的證書NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰(zhàn)* /NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;__block NSURLCredential * credential = nil;// sessionDidReceiveAuthenticationChallenge是自定義方法,用來如何應對服務器端的認證挑戰(zhàn)如果(self.sessionDidReceiveAuthenticationChallenge){disposition = self.sessionDidReceiveAuthenticationChallenge(session,challenge,&credential);} else {//此處服務器要求客戶端的接收認證挑戰(zhàn)方法是NSURLAuthenticationMethodServerTrust//也就是說服務端需要客戶端返回一個根據(jù)認證挑戰(zhàn)的保護空間提供的信任(即challenge.protectionSpace.serverTrust)產(chǎn)生的挑戰(zhàn)證書。//而這個證書就需要使用credentialForTrust:來創(chuàng)建一個NSURLCredential對象如果([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){//基于客戶端的安全策略來決定是否信任該服務器,不信任的話,也就沒必要響應挑戰(zhàn)如果([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]){//創(chuàng)建挑戰(zhàn)證書(注:挑戰(zhàn)方式為UseCredential和PerformDefaultHandling都需要新建挑戰(zhàn)證書)credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];//確定挑戰(zhàn)的方式如果(憑證){//證書挑戰(zhàn)處置= NSURLSessionAuthChallengeUseCredential;} else {//默認挑戰(zhàn)唯一區(qū)別,下面少了這一步!處置= NSURLSessionAuthChallengePerformDefaultHandling;}} else {//取消挑戰(zhàn)處置= NSURLSessionAuthChallengeCancelAuthenticationChallenge;}} else {//默認挑戰(zhàn)方式處置= NSURLSessionAuthChallengePerformDefaultHandling;}}//完成挑戰(zhàn)if(completionHandler){completionHandler(處置,憑證);}

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

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