封装AFN3.0网络请求框架,使用FMDB缓存并可带有时效性缓存

现在项目处于维护阶段,以前的一部分代码随着用户量的增加,不断暴露出问题,特别是网络请求--回调处理方面的修改比较多。每次修改对应的接口都要跳到对应控制器啊,或者是搜一发提示语,其他网络请求操作也是分散在各个控制器内,乱七八糟。好烦的。趁着有点空闲,研究了下别人的封装,改进了项目中使用的封装,并做成了SDK发布在cocoaPods上,欢迎使用.
pod 'TYNetworkTool'即可使用。

项目原先的AFN封装

也就是很简单的按照AFN封装了一下,.m
//
//  PPRHttpManager.m
//  PaopaoRunning
//
//  Created by 王天永 on 16/5/22.

//

#import "PPRHttpManager.h"
#import <AFNetworking.h>
#import "NSString+Paths.h"
#import "NSString+UUID.h"

@implementation PPRHttpManager
+ (NSURLSessionDataTask *)get:(NSString *)url params:(NSDictionary *)params success:(void (^)(id responseObject))success failure:(void (^)(NSError *error))failure{
    
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    manager.requestSerializer = [AFHTTPRequestSerializer serializer];
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    if (IOS11) {    //适配IOS11增加的这句
        manager.securityPolicy.allowInvalidCertificates = YES;
    }
    manager.requestSerializer.timeoutInterval = 15;
//    [manager.requestSerializer setValue:@"application/x-www-form-urlencoded; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
    NSURLSessionDataTask *task = [manager GET:url parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        if (success){
            NSString *responseString = [[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding];
            PRLog(@"responseString---%@",responseString);
            NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingAllowFragments error:nil];
            success(dict);
        }
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        if (failure){
            failure(error);
        }
    }];
    return task;
}

+ (NSURLSessionDataTask *)post:(NSString *)url params:(NSDictionary *)params success:(void (^)(id responseObject))success failure:(void (^)(NSError *error))failure{
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    manager.requestSerializer = [AFHTTPRequestSerializer serializer];
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
//    manager.securityPolicy.allowInvalidCertificates = YES;
    manager.requestSerializer.timeoutInterval = 15;
    NSURLSessionDataTask *task = [manager POST:url parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        if (success){
            NSString *responseString = [[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding];
            PRLog(@"responseString---%@",responseString);
            
            NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingAllowFragments error:nil];

            success(dict);
        }
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        if (failure){

            failure(error);
        }
    }];
    return task;

}

+ (NSURLSessionDownloadTask *)downloadFile:(NSString *)url progress:(void (^)(CGFloat progress))progress success:(void (^)(id responseObject))success failure:(void (^)(NSError *error))failure{

    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
    
    NSURL *URL = [NSURL URLWithString:url];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress *downloadProgress){
//    NSLog(@"progress is %f", downloadProgress.fractionCompleted);
        progress(downloadProgress.fractionCompleted);
    } destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
        NSString *caches = [NSString cachesPath];
        NSString *videovideoCaches = [caches stringByAppendingPathComponent:@"videoCaches"];
        
        NSURL *videoFileUrl = [NSURL fileURLWithPath:videovideoCaches];
        return [videoFileUrl URLByAppendingPathComponent:[response suggestedFilename] ];
    } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
        if (!error){
            PRLog(@"%@",F(@"File downloaded to: %@", filePath));
            success(filePath);
            
            
        }else{
            PRLog(@"File download fail!");
            failure(error);
        }
    }];
    [downloadTask resume];
    return downloadTask;
    
}

@end

着手封装新的低耦合的AFN网络封装

封装的主要方向

  1. 本地缓存优化,请求可根据需求(缓存的时效性等)决定是否直接使用本地缓存,或者请求到新数据后刷新本地。(当前已完成)
  2. 增加短时缓存,避免相同的请求重复调用,浪费用户流量。(当前已完成)
  3. 封装请求失败的重试机制。
  4. 将成功失败提示的逻辑封装。
  5. 其他比如网络状况监控,请求一键撤销等。(当前已完成)
    感谢巨巨文章提供思路和参考
    猿题库YTKNetwork的GitHub
    iOS 工作中封装通用性网络请求框架——鸿雁长飞光不度
    AFNetworking3.x与YYCache的二次封装,和FMDB说拜拜——jkpang

封装的结构考虑

  1. 考虑以后换网络框架的可能。(网络单独封装
  2. 考虑以后换回调处理及提示的可能。网络请求和回调处理要低耦合,能够单独抽离。(回调和显示的单独封装
  3. 考虑以后换缓存框架的可能。(缓存单独封装

封装的结构

考虑到网络请求的通用性,而回调处理和提示等并不通用,所以回调的封装暂时不纳入SDK,但是仍然建议将网络请求的回调封装起来存于一处,相同回调调用相应的统一方法,以后需要改动改这一处就够了,如本人GitHub上的TYNetworkManage文件夹内所封装的方式。
所以最终的SDK内的文件结构是这样的(暂时

TYNetworkTool封装结构

· 第一层 网络请求入口 TYNetworkTool,.h文件对外暴露,用户之间调用即可完成网络请求。.m调用TYCacheTool.h的方法来完成缓存的存储和读取;
· 第二层 缓存的存取入口TYCacheTool,主要来完成缓存的业务逻辑
· 第三层 FMDB的实际缓存操作,实际操控SQLite文件来进行存取操作。

文件内容
//
//  TYNetworkTool.h
//  TYNetworkHelper
//
//  Created by 王天永 on 2017/7/14.
//  Copyright © 2017年 王天永. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "TYCacheTool.h"

typedef NS_ENUM(NSUInteger, TYNetworkStatusType) {
    /// 未知网络
    TYNetworkStatusUnknown,
    /// 无网络
    TYNetworkStatusNotReachable,
    /// 手机网络
    TYNetworkStatusReachableViaWWAN,
    /// WIFI网络
    TYNetworkStatusReachableViaWiFi
};

typedef NS_ENUM(NSUInteger, TYRequestSerializer) {
    /// 设置请求数据为JSON格式
    TYRequestSerializerJSON,
    /// 设置请求数据为二进制格式
    TYRequestSerializerHTTP,
};

typedef NS_ENUM(NSUInteger, TYResponseSerializer) {
    /// 设置响应数据为JSON格式
    TYResponseSerializerJSON,
    /// 设置响应数据为二进制格式
    TYResponseSerializerHTTP,
};

/// 请求成功的Block
typedef void(^TYHttpRequestSuccess)(id responseObject);

/// 请求失败的Block
typedef void(^TYHttpRequestFailed)(NSError *error);

/// 缓存的Block
typedef void(^TYHttpRequestCache)(id responseCache);

/// 上传或者下载的进度, Progress.completedUnitCount:当前大小 - Progress.totalUnitCount:总大小
typedef void (^TYHttTYrogress)(NSProgress *progress);

/// 网络状态的Block
typedef void(^TYNetworkStatus)(TYNetworkStatusType status);

@class AFHTTPSessionManager;
@interface TYNetworkTool : NSObject

/// 有网YES, 无网:NO
+ (BOOL)isNetwork;

/// 手机网络:YES, 反之:NO
+ (BOOL)isWWANNetwork;

/// WiFi网络:YES, 反之:NO
+ (BOOL)isWiFiNetwork;

/// 取消所有HTTP请求
+ (void)cancelAllRequest;

/// 实时获取网络状态,通过Block回调实时获取(此方法可多次调用)
+ (void)networkStatusWithBlock:(TYNetworkStatus)networkStatus;

/// 取消指定URL的HTTP请求
+ (void)cancelRequestWithURL:(NSString *)URL;

/// 开启日志打印 (Debug级别)
+ (void)openLog;

/// 关闭日志打印,默认关闭
+ (void)closeLog;


/**
 *  GET请求,无缓存
 *
 *  @param URL        请求地址
 *  @param parameters 请求参数
 *  @param success    请求成功的回调
 *  @param failure    请求失败的回调
 *
 *  @return 返回的对象可取消请求,调用cancel方法
 */
+ (__kindof NSURLSessionTask *)GET:(NSString *)URL
                        parameters:(id)parameters
                           success:(TYHttpRequestSuccess)success
                           failure:(TYHttpRequestFailed)failure;

/**
 *  GET请求,自动缓存
 *
 *  @param URL           请求地址
 *  @param parameters    请求参数
 *  @param responseCache 缓存数据的回调
 *  @param success       请求成功的回调
 *  @param failure       请求失败的回调
 *
 *  @return 返回的对象可取消请求,调用cancel方法
 */
+ (__kindof NSURLSessionTask *)GET:(NSString *)URL
                        parameters:(id)parameters
                     responseCache:(TYHttpRequestCache)responseCache
                           success:(TYHttpRequestSuccess)success
                           failure:(TYHttpRequestFailed)failure;

/**
 GET请求,带时效自动缓存

 @param URL 请求地址
 @param parameters 请求参数
 @param life 缓存时效
 @param responseCache 缓存数据的回调
 @param success 请求成功的回调
 @param failure 请求失败的回调
 @return 返回的对象可取消请求,调用cancel方法
 */
+ (__kindof NSURLSessionTask *)GET:(NSString *)URL
                        parameters:(id)parameters
                   userfulLifeUnit:(TYTimeUnit)timeUnit
                      userfullLife:(double)life
                     responseCache:(TYHttpRequestCache)responseCache
                           success:(TYHttpRequestSuccess)success
                           failure:(TYHttpRequestFailed)failure;
/**
 *  POST请求,无缓存
 *
 *  @param URL        请求地址``
 *  @param parameters 请求参数
 *  @param success    请求成功的回调
 *  @param failure    请求失败的回调
 *
 *  @return 返回的对象可取消请求,调用cancel方法
 */
+ (__kindof NSURLSessionTask *)POST:(NSString *)URL
                         parameters:(id)parameters
                            success:(TYHttpRequestSuccess)success
                            failure:(TYHttpRequestFailed)failure;

/**
 *  POST请求,自动缓存
 *
 *  @param URL           请求地址
 *  @param parameters    请求参数
 *  @param responseCache 缓存数据的回调
 *  @param success       请求成功的回调
 *  @param failure       请求失败的回调
 *
 *  @return 返回的对象可取消请求,调用cancel方法
 */
+ (__kindof NSURLSessionTask *)POST:(NSString *)URL
                         parameters:(id)parameters
                      responseCache:(TYHttpRequestCache)responseCache
                            success:(TYHttpRequestSuccess)success
                            failure:(TYHttpRequestFailed)failure;

/**
 GET请求,带时效自动缓存
 
 @param URL 请求地址
 @param parameters 请求参数
 @param life 缓存时效
 @param responseCache 缓存数据的回调
 @param success 请求成功的回调
 @param failure 请求失败的回调
 @return 返回的对象可取消请求,调用cancel方法
 */
+ (__kindof NSURLSessionTask *)POST:(NSString *)URL
                        parameters:(id)parameters
                   userfulLifeUnit:(TYTimeUnit)timeUnit
                      userfullLife:(double)life
                     responseCache:(TYHttpRequestCache)responseCache
                           success:(TYHttpRequestSuccess)success
                           failure:(TYHttpRequestFailed)failure;

/**
 *  上传文件
 *
 *  @param URL        请求地址
 *  @param parameters 请求参数
 *  @param name       文件对应服务器上的字段
 *  @param filePath   文件本地的沙盒路径
 *  @param progress   上传进度信息
 *  @param success    请求成功的回调
 *  @param failure    请求失败的回调
 *
 *  @return 返回的对象可取消请求,调用cancel方法
 */
+ (__kindof NSURLSessionTask *)uploadFileWithURL:(NSString *)URL
                                      parameters:(id)parameters
                                            name:(NSString *)name
                                        filePath:(NSString *)filePath
                                        progress:(TYHttTYrogress)progress
                                         success:(TYHttpRequestSuccess)success
                                         failure:(TYHttpRequestFailed)failure;

/**
 *  上传单/多张图片
 *
 *  @param URL        请求地址
 *  @param parameters 请求参数
 *  @param name       图片对应服务器上的字段
 *  @param images     图片数组
 *  @param fileNames  图片文件名数组, 可以为nil, 数组内的文件名默认为当前日期时间"yyyyMMddHHmmss"
 *  @param imageScale 图片文件压缩比 范围 (0.f ~ 1.f)
 *  @param imageType  图片文件的类型,例:png、jpg(默认类型)....
 *  @param progress   上传进度信息
 *  @param success    请求成功的回调
 *  @param failure    请求失败的回调
 *
 *  @return 返回的对象可取消请求,调用cancel方法
 */
+ (__kindof NSURLSessionTask *)uploadImagesWithURL:(NSString *)URL
                                        parameters:(id)parameters
                                              name:(NSString *)name
                                            images:(NSArray<UIImage *> *)images
                                         fileNames:(NSArray<NSString *> *)fileNames
                                        imageScale:(CGFloat)imageScale
                                         imageType:(NSString *)imageType
                                          progress:(TYHttTYrogress)progress
                                           success:(TYHttpRequestSuccess)success
                                           failure:(TYHttpRequestFailed)failure;

/**
 *  下载文件
 *
 *  @param URL      请求地址
 *  @param fileDir  文件存储目录(默认存储目录为Download)
 *  @param progress 文件下载的进度信息
 *  @param success  下载成功的回调(回调参数filePath:文件的路径)
 *  @param failure  下载失败的回调
 *
 *  @return 返回NSURLSessionDownloadTask实例,可用于暂停继续,暂停调用suspend方法,开始下载调用resume方法
 */
+ (__kindof NSURLSessionTask *)downloadWithURL:(NSString *)URL
                                       fileDir:(NSString *)fileDir
                                      progress:(TYHttTYrogress)progress
                                       success:(void(^)(NSString *filePath))success
                                       failure:(TYHttpRequestFailed)failure;


#pragma mark - 设置AFHTTPSessionManager相关属性
#pragma mark 注意: 因为全局只有一个AFHTTPSessionManager实例,所以以下设置方式全局生效
/**
 在开发中,如果以下的设置方式不满足项目的需求,就调用此方法获取AFHTTPSessionManager实例进行自定义设置
 (注意: 调用此方法时在要导入AFNetworking.h头文件,否则可能会报找不到AFHTTPSessionManager的❌)
 @param sessionManager AFHTTPSessionManager的实例
 */
+ (void)setAFHTTPSessionManagerProperty:(void(^)(AFHTTPSessionManager *sessionManager))sessionManager;

/**
 *  设置网络请求参数的格式:默认为二进制格式
 *
 *  @param requestSerializer TYRequestSerializerJSON(JSON格式),TYRequestSerializerHTTP(二进制格式),
 */
+ (void)setRequestSerializer:(TYRequestSerializer)requestSerializer;

/**
 *  设置服务器响应数据格式:默认为JSON格式
 *
 *  @param responseSerializer TYResponseSerializerJSON(JSON格式),TYResponseSerializerHTTP(二进制格式)
 */
+ (void)setResponseSerializer:(TYResponseSerializer)responseSerializer;

/**
 *  设置请求超时时间:默认为30S
 *
 *  @param time 时长
 */
+ (void)setRequestTimeoutInterval:(NSTimeInterval)time;

/// 设置请求头
+ (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;

/**
 *  是否打开网络状态转圈菊花:默认打开
 *
 *  @param open YES(打开), NO(关闭)
 */
+ (void)openNetworkActivityIndicator:(BOOL)open;

/**
 配置自建证书的Https请求, 参考链接: http://blog.csdn.net/syg90178aw/article/details/52839103
 
 @param cerPath 自建Https证书的路径
 @param validatesDomainName 是否需要验证域名,默认为YES. 如果证书的域名与请求的域名不一致,需设置为NO; 即服务器使用其他可信任机构颁发
 的证书,也可以建立连接,这个非常危险, 建议打开.validatesDomainName=NO, 主要用于这种情况:客户端请求的是子域名, 而证书上的是另外
 一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com, 那么mail.google.com是无法验证通过的.
 */
+ (void)setSecurityPolicyWithCerPath:(NSString *)cerPath validatesDomainName:(BOOL)validatesDomainName;

+ (NSString *)jsonToString:(id)data;
@end

//
//  TYCacheTool.h
//  TYNetworkTool
//
//  Created by 王天永 on 2017/7/19.
//  Copyright © 2017年 王天永. All rights reserved.
//

#import <Foundation/Foundation.h>


typedef NS_ENUM (NSUInteger,TYTimeUnit) {
    TYNone,
    TYSecond,
    TYMinute,
    TYHour,
    TYDay,
};

@interface TYCacheTool : NSObject

/**
 无失效日期缓存

 @param httpData 缓存数据
 @param url 请求url
 @param parameters 请求参数
 */
+ (void)setHttpCache:(id)httpData URL:(NSString *)url parameters:(NSDictionary *)parameters;
/**
 带时效的缓存

 @param httpData 缓存数据
 @param url 请求url
 @param parameters 请求参数
 @param timeUnit 时间单位,TYNone则无时效
 @param life 时效数值,具体单位与timeUnit有关,最小单位 秒
 */
+ (void)setHttpCache:(id)httpData URL:(NSString *)url parameters:(NSDictionary *)parameters userfulLifeUnit:(TYTimeUnit)timeUnit life:(double)life;
/**
 获取缓存数据
 
 @param url 请求url
 @param parameter 请求参数
 @return 缓存数据
 */
+ (id)httpCacheWithURL:(NSString *)url parameters:(NSDictionary *)parameter;

@end

//
//  TYFMDBTool.h
//  TYNetworkTool
//
//  Created by 王天永 on 2017/7/20.
//  Copyright © 2017年 王天永. All rights reserved.
//

#import <Foundation/Foundation.h>
@class TYCacheTool;


@interface TYFMDBTool : NSObject


/**
 创建tool实例,包含一个dmdb实例对象

 @param name 对应账号的唯一标识符
 @return 返回fmdbtool实例
 */
+ (instancetype)fmdbWithName:(NSString *)name;

/**
 创建表单

 @param tableName 表单名
 @return FMDB实例
 */
- (instancetype)createTableWithName:(NSString *)tableName;


/**
 储存缓存请求来的回调数据(无时效)
 
 @param httpData 回调数据
 @param cacheKey 存储的关键字
 */
- (void)setHttpCache:(id)httpData forCacheKey:(NSString *)cacheKey;

/**
 带时效的缓存
 
 @param httpData 缓存数据
 @param cacheKey 存储的关键字
 @param life 时效数值,具体单位与timeUnit有关
 */
- (void)setHttpCache:(id)httpData userfulLife:(double)life forCacheKey:(NSString *)cacheKey;


/**
 获取本地缓存

 @param cacheKey 存储的关键字
 @return 缓存本地的网络请求
 */
- (id)httpCacheForCacheKey:(NSString *)cacheKey;
@end

感觉带时效的缓存还有改进的余地,后续再继续改进吧,以新增方法的方式。(当前的方式适合这样的需求:如果是有效的缓存,先读取了显示在界面上,等数据获取到了再显示获取到的数据。但是这样就没有达到节省流量的目的了,需要再SDK内增加先取缓存,无有效缓存再发送网络请求的方法
本人的这个SDK已经发布在cocoapods上,pod 'TYNetworkTool'即可使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,056评论 25 707
  • 点击查看原文 Web SDK 开发手册 SDK 概述 网易云信 SDK 为 Web 应用提供一个完善的 IM 系统...
    layjoy阅读 13,590评论 0 15
  • iOS网络架构讨论梳理整理中。。。 其实如果没有APIManager这一层是没法使用delegate的,毕竟多个单...
    yhtang阅读 5,144评论 1 23
  • AFHTTPRequestOperationManager 网络传输协议UDP、TCP、Http、Socket、X...
    Carden阅读 4,306评论 0 12
  • 作者/晏承林 有时如药罐 苦口难开 有时似蜜糖 甜在心海 其实等待是爱最想念的姿态 其实等待是情最无力的徘徊
    晏承林阅读 377评论 0 3