YTKNetwork集成教程以及相關(guān)問題思考

placeholder

YTKNetwork介紹

YTKNetwork 是猿題庫 iOS 研發(fā)團隊基于 AFNetworking 封裝的 iOS 網(wǎng)絡(luò)庫匣吊,其實現(xiàn)了一套 High Level 的 API,提供了更高層次的網(wǎng)絡(luò)訪問抽象蛋辈。目前在 GitHub 上已有 3600+ star 桶错,是 Network 中的新星航唆。

YTKNetwork提供的主要功能

  • 支持按時間緩存和版本號緩存網(wǎng)絡(luò)請求內(nèi)容
  • 支持統(tǒng)一設(shè)置服務(wù)器和 CDN 的地址
  • 支持檢查返回 JSON 內(nèi)容的合法性
  • 支持 blockdelegate 兩種模式的回調(diào)方式
  • 支持批量的網(wǎng)絡(luò)請求發(fā)送,并統(tǒng)一設(shè)置它們的回調(diào)(實現(xiàn)在 YTKBatchRequest 類中)
  • 支持方便地設(shè)置有相互依賴的網(wǎng)絡(luò)請求的發(fā)送院刁,例如:發(fā)送請求 A糯钙,根據(jù)請求 A 的結(jié)果,選擇性的發(fā)送請求 B 和 C退腥,再根據(jù) B 和 C 的結(jié)果任岸,選擇性的發(fā)送請求 D。(實現(xiàn)在 YTKChainRequest 類中)
  • 支持網(wǎng)絡(luò)請求 URL 的 filter狡刘,可以統(tǒng)一為網(wǎng)絡(luò)請求加上一些參數(shù)享潜,或者修改一些路徑。
  • 定義了一套插件機制嗅蔬,可以很方便地為 YTKNetwork 增加功能剑按。猿題庫官方現(xiàn)在提供了一個插件,可以在某些網(wǎng)絡(luò)請求發(fā)起時澜术,在界面上顯示“正在加載”的 HUD艺蝴。

YTKNetwork 的基本思想

YTKNetwork 的基本的思想是把每一個網(wǎng)絡(luò)請求封裝成對象。所以使用 YTKNetwork鸟废,你的每一個請求都需要繼承 YTKRequest 類猜敢,通過覆蓋父類的一些方法來構(gòu)造指定的網(wǎng)絡(luò)請求。

把每一個網(wǎng)絡(luò)請求封裝成對象其實是使用了設(shè)計模式中的 Command 模式,它有以下好處:

  • 將網(wǎng)絡(luò)請求與具體的第三方庫依賴隔離锣枝,方便以后更換底層的網(wǎng)絡(luò)庫厢拭。
  • 方便在基類中處理公共邏輯,例如猿題庫的數(shù)據(jù)版本號信息就統(tǒng)一在基類中處理撇叁。
  • 方便在基類中處理緩存邏輯供鸠,以及其它一些公共邏輯。
    方便做對象的持久化陨闹。

當然楞捂,如果說它有什么不好,那就是如果你的工程非常簡單趋厉,這么寫會顯得沒有直接用 AFNetworking 將請求邏輯寫在 Controller 中方便寨闹,所以 YTKNetwork 并不合適特別簡單的項目。

關(guān)于集約式和離散式

集約式

介紹:即項目中的每個請求都會走統(tǒng)一的入口君账,對外暴露了請求的 URL 和 Param 以及請求方式繁堡,入口一般都是通過單例 來實現(xiàn),AFNetworking 的官方 demo 就是采用的集約式的方式對網(wǎng)絡(luò)請求進行的封裝乡数,也是目前比較流行的網(wǎng)絡(luò)請求方式椭蹄。

優(yōu)點

  • 使用便捷,能實現(xiàn)快速開發(fā)

缺點

  • 對每個請求的定制型不夠強
  • 不方便后期業(yè)務(wù)拓展

離散式

介紹:即每個網(wǎng)絡(luò)請求類都是一個對象净赴,它的 URL 以及請求方式和響應(yīng)方式 均不暴露給外部調(diào)用绳矩。只能內(nèi)部通過 重載或?qū)崿F(xiàn)協(xié)議 的方式來指定,外部調(diào)用只需要傳 Param 即可玖翅,YTKNetwork就是采用的這種網(wǎng)絡(luò)請求方式翼馆。

優(yōu)點

  • URL 以及請求和響應(yīng)方式不暴露給外部,避免外部調(diào)用的時候?qū)戝e
  • 業(yè)務(wù)方使用起來較簡單金度,業(yè)務(wù)使用者不需要去關(guān)心它的內(nèi)部實現(xiàn)
  • 可定制性強应媚,可以為每個請求指定請求的超時時間以及緩存的周期

缺點

  • 網(wǎng)絡(luò)層需要業(yè)務(wù)實現(xiàn)方去寫,變相的增加了部分工作量
  • 文件增多审姓,程序包會變大[倒也不是特別大]

在微脈的iOS客戶端珍特,由于最初人員較少,且業(yè)務(wù)變更較頻繁魔吐。故使用的就是集約式請求扎筒。不過考慮到為實現(xiàn)業(yè)務(wù)便捷性以及可拓展性,故增加了 RequestHeader 請求頭酬姆,以及 WMHttpHelper 網(wǎng)絡(luò)操作工具類嗜桌。基本上已滿足于目前的開發(fā)模式
不過長遠來看辞色,轉(zhuǎn)成離散式的網(wǎng)絡(luò)請求也是有必要的骨宠。

安裝

你可以在 Podfile 中加入下面一行代碼來使用 YTKNetwork

pod 'YTKNetwork'

集成至項目

項目文件介紹

YTKNetwork源碼

YTKBaseRequest:為請求的基類,內(nèi)部聲明了請求的常用 API :
比如請求方式,請求解析方式层亿,響應(yīng)解析方式桦卒,請求參數(shù)等等。它的用意是讓子類去實現(xiàn)的匿又,本身不做實現(xiàn)方灾。

YTKRequest:是 YTKBaseRequest 的子類,在其基礎(chǔ)上支持了緩存碌更,并且提供了豐富的緩存策略裕偿。基本上項目中使用都是繼承于 YTKRequest 去寫業(yè)務(wù)的 Request痛单。

YTKNetworkAgent:真正做網(wǎng)絡(luò)請求的類嘿棘,在內(nèi)部跟 AFNetworking 直接交互,調(diào)用了 AFNetworking 提供的各種請求旭绒,當然鸟妙,如果底層想切換其他第三方,在這個類中替換掉就行了挥吵。

YTKNetworkConfig:該文件為網(wǎng)絡(luò)請求的統(tǒng)一配置類圆仔,提供了設(shè)置 baseUrl cdnUrl 等基礎(chǔ)請求路徑,可以給所有的請求增加參數(shù)等等蔫劣。

YTKBatchRequest:為批量進行網(wǎng)絡(luò)請求而生,提供了代理和 block 兩種方式給外部使用

YTKChainRequest:當多個請求之間有關(guān)聯(lián)的時候采用此類去實現(xiàn)非常方便个从,即下一個請求可能要根據(jù)上個請求返回的數(shù)據(jù)進行請求脉幢。

YTKBatchRequestAgent,YTKChainRequestAgent:分別是 YTKBatchRequest嗦锐,YTKChainRequest 的操作類嫌松,不需要也無妨主動調(diào)用

集成文件介紹

My Project Table

這是 Demo 工程的我新增的文件,一般情況下奕污,不建議直接繼承于 YTKRequest 類去寫業(yè)務(wù)萎羔,需要自己寫請求的基類,具體業(yè)務(wù)請求再繼承于改項目基類碳默,避免因新版本 YTKRequest 中修改了部分實現(xiàn)的默認值導(dǎo)致的程序需要做大量的修改贾陷。其中:ZCBaseRequestZCBatchRequest嘱根,ZCChainRequest 就是 demo 項目的基類髓废。ZCJSONModel 是 JSON 轉(zhuǎn) Model 的基類,而 ZCHTTPError 是用于自定義錯誤信息的

single http example

這是 Demo 工程中具體某個請求的實例该抒。這種展現(xiàn)方式很清晰慌洪,ZCGetInfoParam 是請求的入?yún)㈩悾?code>ZCMeGetInfoManger是具體的請求操作類, ZCGetInfoModel是出參類。不過如果入?yún)⒑统鰠⒑苌俑缘梢灾挥幸粋€manger類

相關(guān)問題思考

我這里不想介紹 YTKNetwork 的基礎(chǔ)和高級使用教程涌攻。如果想了解基礎(chǔ)以及高級使用教程可以看這里
YTKNetwork 使用基礎(chǔ)教程
YTKNetwork 使用高級教程
這篇文章重在介紹集成以及使用過程中遇到一些問題以及解決方案

1>JSON轉(zhuǎn)Model的問題

對于稍微復(fù)雜的項目,可能某些接口返回數(shù)據(jù)有十多個频伤,使用的時候不可能從字典中一個一個讀取出來恳谎,然后再做 <null> 空處理,一般都是采用轉(zhuǎn) Model 的方式 轉(zhuǎn)換成具體的業(yè)務(wù)模型剂买,從業(yè)務(wù)模型中獲取具體數(shù)據(jù)惠爽,常見的有 JSONModelMantle瞬哼,MJExtension 等第三方庫婚肆,本文以JSONModel為例,來實現(xiàn)框架內(nèi)部解析成 Model

  1. YTKBaseRequest 新增 JSONModel 屬性
/// JsonModel類
@property (nonatomic, strong, readonly, nullable) id   responseJSONModel;
  1. YTKBaseRequest 新增 modelClass 函數(shù)坐慰,用于子類去實現(xiàn)较性,表明要轉(zhuǎn)換的具體 model 類的類名
YTKBaseRequest.h

/// model對應(yīng)的類,子類實現(xiàn)的話會直接映射到該model類并進行初始化操作 
- (Class)modelClass;

 YTKBaseRequest.m
 
- (Class)modelClass
{
    return nil;
}
  1. 查看源碼不難發(fā)現(xiàn)结胀,真正處理網(wǎng)絡(luò)請求成功和失敗的地方是 YTKNetworkAgent 類赞咙,在 - (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error- (void)requestDidSucceedWithRequest:(YTKBaseRequest *)request- (void)requestDidFailWithRequest:(YTKBaseRequest *)request error:(NSError *)error 這三個方法糟港。

具體操作為

- (void)requestDidSucceedWithRequest:(YTKBaseRequest *)request {
   @autoreleasepool {
       [request requestCompletePreprocessor];

       [self JSONConvertModel:request];
   }
   dispatch_async(dispatch_get_main_queue(), ^{
       [request toggleAccessoriesWillStopCallBack];
       [request requestCompleteFilter];

       if (request.delegate != nil) {
           [request.delegate requestFinished:request];
       }
       if (request.successCompletionBlock) {
           request.successCompletionBlock(request);
       }
       [request toggleAccessoriesDidStopCallBack];
   });
}
///json轉(zhuǎn)model的具體方法
- (void)JSONConvertModel:(YTKBaseRequest*)request
{
   Class modelClass = [request modelClass];
   if (!modelClass) {
       return;
   }
   
   NSError * error = nil;
   
   if ([request.responseJSONObject isKindOfClass:[NSDictionary class]]) {
       
       request.responseJSONModel = [[modelClass alloc] initWithDictionary:request.responseJSONObject error:&error];
       
   }else if ([request.responseJSONObject isKindOfClass:[NSArray class]]){
       
       request.responseJSONModel = [modelClass arrayOfModelsFromDictionaries:request.responseJSONObject error:&error];
   }else if {
       //這里不做處理攀操,因為AFNetworking如果返回的數(shù)據(jù)為null的時候會調(diào)用失敗的回調(diào)
   }
   if (error) {
       YTKLog(@"Request JSON---JSONModel Failed =%@",error);
   }
}
  1. YTKRequest 類中也需要新增緩存類的model,具體代碼為
YTKRequest.m

@property (nonatomic, strong) id cacheJSONModel;///TTT

- (id)responseJSONModel {
   if (_cacheJSONModel) {
       return _cacheJSONModel;
   }
   return [super responseJSONModel];
}

- (BOOL)loadCacheData {
   NSString *path = [self cacheFilePath];
   NSFileManager *fileManager = [NSFileManager defaultManager];
   NSError *error = nil;

   if ([fileManager fileExistsAtPath:path isDirectory:nil]) {
       NSData *data = [NSData dataWithContentsOfFile:path];
       _cacheData = data;
       _cacheString = [[NSString alloc] initWithData:_cacheData encoding:self.cacheMetadata.stringEncoding];
       switch (self.responseSerializerType) {
           case YTKResponseSerializerTypeHTTP:
               // Do nothing.
               return YES;
           case YTKResponseSerializerTypeJSON:
               _cacheJSON = [NSJSONSerialization JSONObjectWithData:_cacheData options:(NSJSONReadingOptions)0 error:&error];
               if (!error) {
                   [self JSONConvertModel:_cacheJSON];
               }
               return error == nil;
           case YTKResponseSerializerTypeXMLParser:
               _cacheXML = [[NSXMLParser alloc] initWithData:_cacheData];
               return YES;
       }
   }
   return NO;
}

- (void)JSONConvertModel:(YTKBaseRequest*)request
{
    ///跟第二步的實現(xiàn)方式一樣
}
  1. 到這里基本上已經(jīng)實現(xiàn)了 json-model秸抚,具體的業(yè)務(wù)代碼為:
 - (void)loadCacheData {
   NSString *userId = @"1";
   GetUserInfoApi *api = [[GetUserInfoApi alloc] initWithUserId:userId];
   if ([api loadCacheWithError:nil]) {
       NSDictionary *json = [api responseJSONObject];
       NSLog(@"json = %@", json);
       // show cached data
       
       YTKJSONModel * model = [api responseJSONModel];
       
       NSLog(@"jsonmodelllll=%@---%@",model.nick,model.level);
   }

   api.animatingText = @"正在加載";
   api.animatingView = self.view;

   [api startWithCompletionBlockWithSuccess:^(YTKBaseRequest *request) {
       NSLog(@"update ui=%@",[api responseJSONModel]);
   } failure:^(YTKBaseRequest *request) {
       NSLog(@"failed");
   }];
}

大致步驟如此速和,只是這樣實現(xiàn)的話需要修改源代碼,細節(jié)可以參考 demo剥汤,地址:https://github.com/albertjson/YTKNetwork

2>token引發(fā)的問題

一般情況下颠放,網(wǎng)絡(luò)請求客戶端都要帶 token,用于服務(wù)端驗證用戶的登陸有效性吭敢。那么 token 失效可能需要做一些處理碰凶,在 demo 中這部分驗證是寫在 ZCBaseRequest 類中實現(xiàn)的。這樣避免業(yè)務(wù)代碼在各處進行處理 token 失效的情況


- (void)requestFailedFilter
{
   [super requestFailedFilter];
   
   if (error.code==TokenTimeOut) {
       ......
   }
}

當然鹿驼,這樣處理之后欲低,如果子類需要在錯誤的時候做特殊處理,那么在重寫 requestFailedFilter 方法的時候一定要調(diào)用 [super requestFailedFilter]

3>錯誤解析

YTKNetwork 調(diào)用 HTTP 返回錯誤的類為 NSError蠢沿。而自己的項目一般都需要定制錯誤信息伸头,或者根據(jù)某一類型的錯誤進行特殊的操作。這一步可以在自己定義的請求基類的錯誤回調(diào)中處理舷蟀。我們先來看一段 YTKNetwork 的源碼:

- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {
    //這里只留下關(guān)鍵性代碼
    NSError * __autoreleasing serializationError = nil;
    NSError * __autoreleasing validationError = nil;

    NSError *requestError = nil;
    BOOL succeed = NO;

    request.responseObject = responseObject;
    if ([request.responseObject isKindOfClass:[NSData class]]) {
        request.responseData = responseObject;
        request.responseString = [[NSString alloc] initWithData:responseObject encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];

        switch (request.responseSerializerType) {
            case YTKResponseSerializerTypeHTTP:
                // Default serializer. Do nothing.
                break;
            case YTKResponseSerializerTypeJSON:
                request.responseObject = [self.jsonResponseSerializer responseObjectForResponse:task.response data:request.responseData error:&serializationError];
                request.responseJSONObject = request.responseObject;
                break;
            case YTKResponseSerializerTypeXMLParser:
                request.responseObject = [self.xmlParserResponseSerialzier responseObjectForResponse:task.response data:request.responseData error:&serializationError];
                break;
        }
    }
    if (error) {
        succeed = NO;
        requestError = error;
    } else if (serializationError) {
        succeed = NO;
        requestError = serializationError;
    } else {
        succeed = [self validateResult:request error:&validationError];
        requestError = validationError;
    }
    //只留關(guān)鍵性代碼
}

不難發(fā)現(xiàn)恤磷,這里的錯誤其實分三類

  1. requestError:請求錯誤面哼,為 AFNetworking 進行網(wǎng)絡(luò)請求的請求錯誤,比如說沒網(wǎng)絡(luò)扫步。
  2. serializationError:響應(yīng)錯誤魔策,為 AFNetworking 響應(yīng)錯誤,比如返回的json數(shù)據(jù)你卻用了xml解析河胎,還有很多情況等等闯袒。
  3. validationError:校驗 json 錯誤,這里包括 [request statusCodeValidator][request jsonValidator 兩種類型的錯誤游岳,前者為返回的 statusCode 不在你指定的成功請求區(qū)間內(nèi)政敢,后者為返回的 json 數(shù)據(jù) 跟你重載的 jsonValidator 函數(shù)中存在字段不一致的情況。
  4. [可選] 如果你用 JSONModel 還會有 JSONModel 解析錯誤產(chǎn)生的錯誤胚迫。

處理方式如下:


//這里暫不考慮JSONModel解析錯誤的問題

- (void)requestFailedPreprocessor
{
    //note:子類如需繼承喷户,必須必須調(diào)用 [super requestFailedPreprocessor];
    [super requestFailedPreprocessor];
    
    NSError * error = self.error;
    
    if ([error.domain isEqualToString:AFURLResponseSerializationErrorDomain])
    {
        //AFNetworking處理過的錯誤
        
    }else if ([error.domain isEqualToString:YTKRequestValidationErrorDomain])
    {
        //猿題庫處理過的錯誤
        
    }else{
        //系統(tǒng)級別的domain錯誤,無網(wǎng)絡(luò)等[NSURLErrorDomain]
        //根據(jù)error的code去定義顯示的信息访锻,保證顯示的內(nèi)容可以便捷的控制
    }
}

這里還有一種特殊情況褪尝,就是服務(wù)端返回的錯誤不一定是以 錯誤 的方式給你∑谌可能請求狀態(tài)碼依然是200OK河哑,那么這個時候需要重寫 YTK 提供的成功和失敗的block和重寫代理

4>loading動畫以及錯誤彈出機制

YTK自帶了一套插件機制,用于處理 YTKBaseRequest龟虎,YTKBatchRequest璃谨,YTKChainRequest 這幾種請求的loading展示機制,只需要傳入 animatingViewanimatingText 即可鲤妥。對于彈出統(tǒng)一的錯誤提示睬罗,可以在 ZCBaseRequest 的失敗主線程回調(diào)中進行。即:

///  Called on the main thread when request failed.
- (void)requestFailedFilter
{
    [super requestFailedFilter];
    
    if (![self isHideErrorToast]) {
        UIWindow * window = [[UIApplication sharedApplication] keyWindow];
        
        UIViewController * controller = [self findBestViewController:window.rootViewController];
        
        [WMHUDUntil showFailWithMessage:self.error.localizedDescription toView:controller.view];
    }

}

其中 [self isHideErrorToast] 用于表示是否隱藏錯誤提示旭斥。該方法由具體的子類去實現(xiàn)。

5>網(wǎng)絡(luò)請求的終止

YTK給出網(wǎng)絡(luò)請求關(guān)閉方案:在dealloc中調(diào)用:

 Remove self from request queue and cancel the request.
- (void)stop;

所以古涧,建議每個網(wǎng)絡(luò)請求都在controller寫成全局的變量垂券。
下面展示一下具體某個請求的代碼:

ZCTYKTestViewController.m

@interface ZCTYKTestViewController ()

@property (nonatomic,strong) ZCMeGetInfoManger * infoManger;

@end

@implementation ZCTYKTestViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupData];
}
- (void)setupData
{
    self.infoManger = [[ZCMeGetInfoManger alloc] init];
    self.infoManger.animatingView = self.view;
}

- (IBAction)buttonAction:(UIButton*)sender
{
    [self clearTextView];
    
    ZCGetInfoParam * param = [[ZCGetInfoParam alloc] init];
    param.userId = @"0";
    param.token = @"222222";
    _infoManger.param = param;
    
    [_infoManger startWithCompletionBlockWithSuccess:^(__kindof YTKBaseRequest * _Nonnull request) {
        NSLog(@"responseJSONObject=%@",_infoManger.responseJSONObject);
        
        ZCGetInfoModel * infoModel = [[ZCGetInfoModel alloc] initWithDictionary:_infoManger.responseJSONObject error:nil];
        
        [self updateTextViewWithLog:[NSString stringWithFormat:@"讀取數(shù)據(jù):\n%@",infoModel]];
        
    } failure:^(__kindof YTKBaseRequest * _Nonnull request) {
        
        [weakself updateTextViewWithLog:[NSString stringWithFormat:@"讀取失敗:\n%@",weakself.infoManger.error]];
        
    }];
}

這是我個人總結(jié)的這幾點,如果有更好的方案也可以跟我一起探討

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末羡滑,一起剝皮案震驚了整個濱河市菇爪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柒昏,老刑警劉巖凳宙,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異职祷,居然都是意外死亡氏涩,警方通過查閱死者的電腦和手機届囚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來是尖,“玉大人意系,你說我怎么就攤上這事〗刃冢” “怎么了蛔添?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長兜辞。 經(jīng)常有香客問我迎瞧,道長,這世上最難降的妖魔是什么逸吵? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任凶硅,我火速辦了婚禮,結(jié)果婚禮上胁塞,老公的妹妹穿的比我還像新娘咏尝。我一直安慰自己,他們只是感情好啸罢,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布编检。 她就那樣靜靜地躺著,像睡著了一般扰才。 火紅的嫁衣襯著肌膚如雪允懂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天衩匣,我揣著相機與錄音蕾总,去河邊找鬼。 笑死琅捏,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的柄延。 我是一名探鬼主播蚀浆,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼市俊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了摆昧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤绅你,失蹤者是張志新(化名)和其女友劉穎伺帘,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勇吊,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡曼追,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了汉规。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片礼殊。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖针史,靈堂內(nèi)的尸體忽然破棺而出晶伦,到底是詐尸還是另有隱情,我是刑警寧澤啄枕,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布婚陪,位于F島的核電站,受9級特大地震影響频祝,放射性物質(zhì)發(fā)生泄漏泌参。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一常空、第九天 我趴在偏房一處隱蔽的房頂上張望沽一。 院中可真熱鬧,春花似錦漓糙、人聲如沸铣缠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蝗蛙。三九已至,卻和暖如春醉鳖,著一層夾襖步出監(jiān)牢的瞬間捡硅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工盗棵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留病曾,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓漾根,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鲫竞。 傳聞我的和親對象是個殘疾皇子辐怕,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)从绘,斷路器寄疏,智...
    卡卡羅2017閱讀 134,704評論 18 139
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫是牢、插件陕截、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,121評論 4 61
  • Swift版本點擊這里歡迎加入QQ群交流: 594119878最新更新日期:18-09-17 About A cu...
    ylgwhyh閱讀 25,418評論 7 249
  • 人都有從眾心理倘感,制片人男兒一跪縱然爭議紛紜侠仇,到底如他所愿犁珠,將《百鳥朝鳳》推倒了輿論浪口。 對這部電影的興趣余素,并不是...
    君鈞閱讀 416評論 0 1
  • 送給我們的畢業(yè)季 熟悉的太慢,分開的太急 前一天晚上都還在寢室佑淀, 第二天回來彰檬, 許多人卻已離開了成都 這就是最近的...
    溯雨閱讀 207評論 0 1