今天我想说的是如何封装iOS原生的网络请求 , 本文是基于 NSURLConnection 网络请求的封装 , 实际的网络封装代码要比示例代码复杂 , 考虑的东西也更多 , 本文仅带大家了解一下网络封装的流程与原理,其中渗透了一些面试会问到的知识点等!
NSURLConnection 虽然已经被废弃,但是也是面试中会提及的东西 , 面了不少人 , 也被面了不少次 , 算是这两年的经验之谈吧...
前言:
本文中提到了一个单例类的概念(在程序运行过程全局只通过类方法创建的对象都是同一个对象,访问的都是同一片内存)
但是如果是小白看你的代码,很容易看到它就alloc init方法创建对象,虽然你用了default前缀,尽管你用了share开头,然而这并没有提醒到这个小白,那怎么办呢!?
代码文件2.2MD_RequestManager.m文件:
中有提到这一点,这也是一道很有技术含量的面试题
主bundle栏:
Snip20170202_1.png1.ViewController.m文件
#import "ViewController.h"
#import "MD_RequestManager.h"
#import "MD_DataModel.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self HTTPRequestConnection] ;
}
- (void)HTTPRequestConnection {
MD_RequestManager *requestManager = [[MD_RequestManager alloc] init] ;
// requestManager.HTTPHeadersDic = @{@"在这里填入你的 api 接口的 key 值" : @"在这里填入你的 api 接口的 value 值"} ;
requestManager.HTTPHeadersDic = @{@"在这里填入 apiKey" : @"在这里填入 apiValue"} ;
[requestManager addRequestConnectionToManager:@"在这里填入url地址字段" finished:^(BOOL isSuccess, NSData *data) {
if (isSuccess) {
NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ;
NSLog(@"dataStr = %@" , dataStr) ;
/*
//进行JSON解析:
NSError *error ;
//JSON数据转OC(NSDictionary)对象:
//JSON数据的最外层是 字典 就用字典接收JSON数据:
NSDictionary *dataDict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error] ;
NSLog(@"%@" , dataDict) ;
//JSON数据的最外层是 数组 就用字典接收JSON数据:
NSArray *dataArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error] ;
NSLog(@"%@" , dataArray) ;
*/
NSError *error ;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error] ;
NSDictionary *modelDict = dict[@"JSON转字典后字典中的请求字段"] ;
MD_DataModel *dataModel = [[MD_DataModel alloc] init] ;
dataModel.allNum = [modelDict[@"allNum"] intValue];
dataModel.allPages = [modelDict[@"allPages"] intValue] ;
dataModel.currentPage = [modelDict[@"currentPage"] intValue] ;
dataModel.maxResult = [modelDict[@"maxResult"] intValue] ;
dataModel.contentlist = modelDict[@"contentlist"] ;
//键值编码:替换上边的一一赋值操作:
//要注意 要在模型里调用: setValue: forUndefinedKey: 这个方法保证程序即便没有字段一一对应也不至于崩溃!!!
[dataModel setValuesForKeysWithDictionary:modelDict] ;
NSLog(@"%@" , dataModel) ;
}
}] ;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end
2.网络请求管理类:
2.1MD_RequestManager.h文件:
#import <Foundation/Foundation.h>
@interface MD_RequestManager : NSObject
//创建网络请求管理中心(管理用单例,便于统一管理):
+ (MD_RequestManager *)defaultRequestManagerCenter ;
//将某个网络请求方法添加到管理类里:
- (void)addRequestConnectionToManager:(NSString *)urlStr finished:(void (^) (BOOL isSuccess , NSData *data))finish ;
//请求头文件参数:
@property (nonatomic, strong) NSDictionary *HTTPHeadersDic ;
@end
2.2MD_RequestManager.m文件:
#import "MD_RequestManager.h"
#import "MD_RequestConnection.h"
//创建全局静态变量 requestManager对象 :
static MD_RequestManager *_requestManager ;
@interface MD_RequestManager () <NSCopying>
//存放网络请求连接对象用的数组:
@property (nonatomic, strong) NSMutableArray *requestConnectionArray ;
@end
@implementation MD_RequestManager
#pragma mark -
- (void)addRequestConnectionToManager:(NSString *)urlStr finished:(void (^)(BOOL, NSData *))finish {
// 1.防止重复请求浪费用户流量:
//遍历数组,如果已有,则return返回:
for (MD_RequestConnection *requestConnection in _requestConnectionArray) {
//如果请求文件urlStr地址相同:
if ([requestConnection.urlStr isEqualToString:urlStr]) {
return ;
}
}
//创建网络连接对象:
MD_RequestConnection *requestConnection = [[MD_RequestConnection alloc] init] ;
if (self.HTTPHeadersDic) {
//把网络管理类的请求头文件参数赋值传递给网络请求的请求头文件参数:
requestConnection.HTTPHeadersDic = self.HTTPHeadersDic ;
}
/*可变数组里装满了网络请求的连接对象,这些个网络请求的连接对象的请求头文件参数与网络请求管理类的请求头文件参数是一致的,这样就把两者联系起来了!
*/
//这样你这的网络请求类就归我网络请求管理类管理了!
[_requestConnectionArray addObject:requestConnection] ;
//发送网络请求:
[requestConnection beginRequestWithurlString:urlStr finished:^(BOOL isSuccess, NSData *data) {
finish(isSuccess , data) ;
NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ;
NSLog(@"dataStr = %@" , dataStr) ;
//网络请求完成后移除连接 , 本次连接就断开了,就不归我这个网络管理类管理了:
[_requestConnectionArray removeObject:requestConnection] ;
}] ;
}
#pragma mark - 单例方法
+ (MD_RequestManager *)defaultRequestManagerCenter {
//创建静态变量(通过static创建的静态变量可以保证它全局不被释放):
//dispatch_once方法保证代码全局范围内只执行一次:
//不用考虑线程安全问题,因为 dispatch_once 已经替我们考虑到了同步锁互斥锁的问题了!
//这么敲线程就是安全的!
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (_requestManager == nil) {
_requestManager = [[MD_RequestManager alloc] init] ;
}
});
return _requestManager ;
}
/*
* 这很重要的一点,我在之前去 百度 和 360 面试OC内容的时候都遇到了这个问题,而且而且!!!面试官明确说道这个题问了快一个月了没有一个人能说得出来~
+ (instancetype)allocWithZone:(struct _NSZone *)zone 这个方法!!!他才是真正去内存上开辟空间的方法,他才是alloc init方法的底层实现!!!
*/
#warning - 完整单例还需要的方法 - 1 - alloc init方法创建的对象也能访问到我的这个网络请求控制中心!
//重写allocWithZone方法:
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_requestManager = [super allocWithZone:zone] ;
});
return _requestManager ;
}
#warning 完整单例还需要的方法 - 2 - 防止别人通过 [对象 copy]方法复制对象,这样相当于又创建了新的对象了!
/*
* 如果这个方法你敲不出来,别担心~因为你没遵守 <NSCopying> 协议 ;
*/
//重写copyWithZone 方法:
- (id)copyWithZone:(NSZone *)zone {
return _requestManager ;
}
#pragma mark - 连接对象数组初始化
- (instancetype)init {
self = [super init] ;
if (self) {
_requestConnectionArray = [NSMutableArray array] ;
}
return self ;
}
@end
3网络请求连接类:
3.1MD_RequestConnection.h文件:
#import <Foundation/Foundation.h>
@interface MD_RequestConnection : NSObject
//请求头文件参数:
@property (nonatomic, strong) NSDictionary *HTTPHeadersDic ;
//请求文件地址:
@property (nonatomic, copy) NSString *urlStr ;
//封装网络请求的方法:
- (void)beginRequestWithurlString:(NSString *)urlStr finished:(void (^) (BOOL isSuccess , NSData *data))finish ;
@end
3.2MD_RequestConnection.m文件:
#import "MD_RequestConnection.h"
@implementation MD_RequestConnection
- (void)beginRequestWithurlString:(NSString *)urlStr finished:(void (^)(BOOL, NSData *))finish {
self.urlStr = urlStr ;
NSURL *url = [NSURL URLWithString:urlStr] ;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url] ;
//遍历请求头键值字段:
if (self.HTTPHeadersDic) {
for (NSString *key in self.HTTPHeadersDic.allKeys) {
[request setValue:[self.HTTPHeadersDic objectForKey:key] forHTTPHeaderField:key] ;
}
}
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
if (connectionError) {
NSLog(@"网络请求错误 --> connectionError = %@" , connectionError) ;
finish(NO , data) ;
} else {
// NSLog(@"网络请求成功 --> data = %@" , data) ;
NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ;
NSLog(@"网络请求成功 --> dataStr = %@" , dataStr) ;
finish(YES , data) ;
}
}] ;
}
@end
4 模型文件类
4.1MD_DataModel.h文件:
#import <Foundation/Foundation.h>
@interface MD_DataModel : NSObject
//allNum:
@property (nonatomic, assign) int allNum ;
//allPages:
@property (nonatomic, assign) int allPages ;
//currentPage:
@property (nonatomic, assign) int currentPage ;
//maxResult:
@property (nonatomic, assign) int maxResult ;
//contentlist:
@property (nonatomic, strong) NSArray *contentlist ;
//字段必须与JSON数据返回来的数据格式一模一样!!!
@end
@interface MD_DataContentlistModel : NSObject
//ct:
@property (nonatomic, copy) NSString *ct ;
//text:
@property (nonatomic, copy) NSString *text ;
//title:
@property (nonatomic, copy) NSString *title ;
//type:
@property (nonatomic, assign) int type ;
@end
4.2MD_DataModel.m文件:
#import "MD_DataModel.h"
@implementation MD_DataModel
- (void)setContentlist:(NSArray *)contentlist {
NSMutableArray *mArray = [NSMutableArray array] ;
// 1.for循环遍历:
for (int i = 0; i < contentlist.count; i ++) {
//最外层为字典接收:
NSDictionary *dict = contentlist[i] ;
MD_DataContentlistModel *contentlistModel = [[MD_DataContentlistModel alloc] init] ;
//KVC:键值编码:
//[contentlistModel setValuesForKeysWithDictionary:dict] ;
contentlistModel.ct = dict[@"ct"] ;
contentlistModel.text = dict[@"text"] ;
contentlistModel.title = dict[@"title"] ;
contentlistModel.type = [dict[@"type"] intValue] ;
[mArray addObject:contentlistModel] ;
}
//2.forin遍历:
for (NSDictionary *dict in contentlist) {
MD_DataContentlistModel *contentlistModel = [[MD_DataContentlistModel alloc] init] ;
//KVC:键值编码:
//[contentlistModel setValuesForKeysWithDictionary:dict] ;
contentlistModel.ct = dict[@"ct"] ;
contentlistModel.text = dict[@"text"] ;
contentlistModel.title = dict[@"title"] ;
contentlistModel.type = [dict[@"type"] intValue] ;
[mArray addObject:contentlistModel] ;
}
_contentlist = contentlist ;
}
#pragma mark - 键值编码异常保证程序不崩溃
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
}
@end
//MD_DataContentlistModel 的实现不能忘记写!!!
@implementation MD_DataContentlistModel
#pragma mark - 键值编码异常保证程序不崩溃
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
}
@end
网友评论
另外这个类可以提供一个用NSURLRequest作为参数的初始化方法,这样会更灵活一点。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_requestManager = [super allocWithZone:zone] ;
});
return _requestManager ;
}
这里根本不用搞得这么复杂。
直接 return [self defaultRequestManagerCenter]就可以了。将所有的初始化行为直接指向defaultRequestManagerCenter的实现就好了。
另外copying的实现也是没有任何必要的。人家看你的头文件发现没有实现copying就不会考虑copy了。如果说为了防止别人真的写了copy。。。基于这样的逻辑所有自定义的类型我们都得考虑copy了。。。。这个得多累。
还有就是requestConnectionArray这个其实用字典更方便,直接用url来索引比你历遍来过滤要好