什么是 MKNetworkKit?
MKNetworkKit 是一个用objective-c写的网络库,具有无缝连接,基于block,ARC支持以及易用等特点。
MKNetworkKit有其他两个库:ASIHTTPRequest和AFNetworking,结合了两个库的共同特点,并且有一些新的特性。除此之外,MKNetworkKit可能会比其他网络库而言为了代码的清晰性。
特性
超轻量级
完整的库只有2个主类和一些category方法。
正确的显示网络连接的标志
MKNetworkKit是通过KVO注册共享队列里正在运行的操作。
if (object == _sharedNetworkQueue && [keyPath isEqualToString:@"operationCount"]) {
[UIApplication sharedApplication].networkActivityIndicatorVisible =
([_sharedNetworkQueue.operations count] = 0);
}
Auto queue sizing 自动队列大小
所以当3G网络时你的队列大小应该设为2,MKNetworkKit自动为你处理这些。当网络进入3G/EDGE/GPRS时,它会更改并发连接的数目为2,并且在连接wifi时自动设置回6。有了这个特性,你会看到当你通过3G网络从服务器加载缩略图(或者多个小的请求)时有巨大的性能提升
Auto caching 自动缓存
MKNetworkKit可以自动缓存你的所有GET请求。当你发起同样一个请求的时候,MKNetworkKit会立即用缓存的响应(如果有)来调用完成方法,也会向远程服务器发出一个请求。当服务器的数据返回以后,会用取到的新的相应再次调用完成方法。这就是说,你不需要手动的处理缓存,你需要做的,就只有调用这个方法:
[[MKNetworkEngine sharedEngine] useCache]; |
当然,你也可以在你的MKNetworkEngine子类中定制缓存的目录和内存缓存的消耗量。
Operation freezing 操作冻结
用了MKNetworkKit,你就拥有了冻结网络操作的能力。当你冻结一个操作的时候,为了防止网络连接丢失,操作会被自动序列化并且在设备联网之后自动执行。
Image Caching 图片缓存
MKNetworkKit可以无缝的缓存缩略图。通过重写几个方法,你可以设置缓存多少张图片在内存缓存以及缓存的目录。重写这些方法完全是可选的。
Performance 性能
性能非常好。
Full support for Objective-C ARC 全面支持Ojbective-C ARC
一般你会为新的项目选择一个新的网络库。MKNetworkKit不是为了取代你现有的(当然你也可以取代,但是可能会很麻烦)。在新的项目中,你会很想要使用ARC功能。MKNetworkKit可能是唯一全面支持ARC的网络库。
How to use 如何使用
Adding the MKNetworkKit 添加MKNetworkKit
- 拖拽MKNetworkKit的目录到你的工程里
- 添加 CFNetwork.Framework, SystemConfiguration.framework and Security.framework依赖库
- 在文件中包含 MKNetworkKit.h
- 删除 NSAlert+MKNetworkKitAdditions.h如果你开发的是iOS程序。
MKNetworkKit 中的类
- MKNetworkOperation
- MKNetworkEngine
- 混杂的帮助类 (苹果的 Reachability)以及一些cateogory方法。
巨大的库的问题就在于很难去理解他的内在的工作机制,而且很难去根据自己的需求定制。使用MKNetworkKit,你只需要知道MKNetworkOperation和 MKNetworkEngine两个类所暴露出来的方法。MKNetworkOperation类似于ASIHttpRequest类,是一个NSOperation的子类并且封装了请求相应类。你需要为你的app的每一个网络请求创建一个MKNetworkOperation。
MKNetworkEngine是一个假单例的类,负责管理你的app的网络队列。因此,简单的请求时,你应该直接使用MKNetworkEngine的方法。在更为复杂的定制中,你应该集成并子类化它。每一个MKNetworkEngine的子类都有他自己的Reachability对象来通知服务器的连通情况。你应该考虑为你的每一个特别的REST服务器请求子类化MKNetworkEngine。因为是假单例模式,每一个单独的子类的请求,都会通过仅有的队列发送。
你可以在应用的delegate里面retain MKNetworkEngine的实例,就像CoreDatademanagedObjectContext类。当你使用MKNetworkKit的时候,你创建一个MKNetworkEngine的子类来从逻辑上分组你的网络请求。就是说,Yahoo相关的请求都在一个类中,Facebook相关的请求都在另一个类中。我们会看到3个不同的使用库的例子。
当你创建一个MKNetworkEngine子类,Reachability的会自动实现。这样当服务器宕机或者其他不可预料的情况下时,请求会被自动的队列或者冻结。想要获取更多关于冻结操作的信息,请阅读后面冻结操作相关的章节。
步骤 2:设计 Engine类 (分离考虑)
Engine里面的方法会在ViewController里面调用。一个好的设计实践就是确保你的Engine类不会暴露URL和http的请求头给调用的类。你的View层不应该知道url路径以及需要的参数,就是对应着YahooEngine里面的货币及以及货币单位。返回的数据可以为double值表示货币交换率或者时间戳。因为请求都是异步的,你应该在block里面返回这些值,比如:
-(MKNetworkOperation*) currencyRateFor:(NSString*) sourceCurrency inCurrency:(NSString*) targetCurrency onCompletion:(CurrencyResponseBlock) completion onError:(ErrorBlock) error; |
MKNetworkEngine父类定义了block的类型如下:
typedef void (^ProgressBlock)(double progress); typedef void (^ResponseBlock)(MKNetworkOperation* operation); typedef void (^ErrorBlock)(NSError* error); |
In our YahooEngine, we are using a new kind of block, CurrencyResponseBlock that returns the exchange rate. The definition looks like this.
在YahooEngine里面,我们使用一种新的block,CurrencyResponseBlock来返回交换率的值,定义如下:
typedef void (^CurrencyResponseBlock)(double rate); |
在任何其他app里面,你都应该丁一你的block的方法类似于CurrencyResponseBlock这样的来给viewController传回值。
步骤 3: 处理返回数据
处理数据,就是转换你从服务器取回的数据,不论时Json还是XML或者二进制plist,都应该在Engine里面完成。再次声明,不要让你的Viewcontroller做这件事,你的Engine应该仅仅发回正确的model对象或者model对象的数组。在Engine中转换json或者xml。再次声明,为了确保分类原则,你的viewController应该不知道从json里面获取单个元素的key。
这就是设计Engine的准则了,大部分的网络库不强迫你遵循一些接口分离的原则,但我do,因为我爱你。
步骤4: 方法实现
现在我们讨论一下计算货币交换率的方法的具体实现细节
从yahoo获取交换率,就是一个简单的GET请求,我写了一个宏来定义获取货币率的url请求的格式
[NSString stringWithFormat:@"d/quotes.csv?e=.csv&f=sl1d1t1&s=%@%@=X", __C1__, __C2__] |
你写的方法应该按照如下顺序执行:
- 准备好url和请求参数
- 创建一个请求的MKNetworkOperation对象.
- 设置方法参数
- 添加完成和错误处理方法,(完成方法就是你处理你响应到相应的model类的地方)
- 可选的,还有请求操作的进度指示。(或者在viewController里面处理)
- 如果你的操作时下载文件,那么设置一个下载的流(通常是一个文件)给他,这也是可选的
- 当请求操作完成时,处理结果并且调用block方法来向调用方法返回数据。
上面的代码构造url然后创建了MKNetworkOperation,设置完完成方法和错误处理方法之后就把他通过调用父类的enqueueOperation方法放入队列中,并且返回一个引用。你的viewController应该持有这个操作并且在viewController弹出视图体系的时候取消网络操作。所以你可以在viewDidAppear里面调用engine的方法,并且在viewWillDisappear里面取消操作。取消操作会释放队列从而使队列继续其他操作(记住,在移动网络中仅仅允许2个并发请求,取消不在需要的操作可以提升性能,加速你的app)
你的ViewController也可以(可选的)添加进度处理,并且更新界面,下面就是如何做
[self.uploadOperation onUploadProgressChanged:^(double progress) {
DLog(@"%.2f", progress*100.0); self.uploadProgessBar.progress = progress; }]; |
MKNetworkEngine也可以很方便的根据url来创建请求,所以下面代码可以写为:
MKNetworkOperation *op = [self operationWithPath:YAHOO_URL(sourceCurrency, targetCurrency)]; |
注意请求的url是自动在初始化engine的代码里面加上提供的域名。
创建一个POST,DELETE或者PUT的方法一样简单,就是更换http方法的参数。MKNetworkEngine也有更多给你多的便利指出比如读取header文件
示例 2:
上传图片到服务器(比如TwitPic)
现在我们看一个如何上传图片到服务器的例子,上传图片明显需要操作把data编码为表格请求。MKNetworkKit遵循一系列的类似ASIHttpRequest的请求。
你可以调用addFile:forKey:在MKNetworkOperation里面来添加文件附件作为请求表格的数据。很简单。
MKNetworkOperation 也有一个简单的方法来从NSData指针中添加图片,就是调用addData:forKey:方法来直接从NSData上传图片。 (比如直接从相机添加图片).
例子 3:
下载文件到本地目录(缓存)
用MKNetworkKit从远程服务器下载文件并且保存到用户的iPhone的某个地方是超级简单的
仅仅需要设置MKNetworkOperation的outputStream即可:
[operation setDownloadStream:[NSOutputStream outputStreamToFileAtPath:@"/Users/mugunth/Desktop/DownloadedFile.pdf" append:YES]]; |
你可以设置多个输出流到单个请求操作中来保存相同的文件到不同的位置。(比如你想既保存到缓存目录又想保存到工作目录)
例子 4:
图片缩略图缓存。
为了下载图片,你可能需要提供绝对的url而不是一个相对目录
MKNetworkEngine有一个便捷的方法。只需要调用operationWithURLString:params:httpMethodMKNetworkEngine来用绝对url创建个一个请求,MKNetworkEngine是智能的,它会合并多个get请求到同一个url并且当操作完成时通知所有的block,这极大的提高了获取缩略图的速度。
子类 MKNetworkEngine 并且重写图片缓存目录和缓存级别,如果你不想自定义这两个参数,你只需要调用MKNetworkEngine的方法来下载图片即可,实际上我也比较推荐这种方法,。
缓存操作
MKNetworkKit默认缓存所有的请求。你需要做的仅仅是在你的Engine上打开缓存。当GET请求执行时,如果响应之前被缓存过,你的完成处理代码几乎是立即会被调用并传递缓存过的相应,要知道请求是否被缓存,调用isCachedResponse方法,比如下面
[op onCompletion:^(MKNetworkOperation *completedOperation) { if([completedOperation isCachedResponse]) { DLog(@"Data from cache"); } else { DLog(@"Data from server"); }
DLog(@"%@", [completedOperation responseString]); }onError:^(NSError* error) {
errorBlock(error); }]; |
冻结操作
可以确定的, MKNetworkKit的最又去的功能就是内置的冻结操作的功能。所有你需要做的就是设置请求操作为freezable,不用费任何力气!
[op setFreezable:YES]; |
冻结的操作会在网络不通时自动的被序列化并且上线后自动执行。想一下在离线时标记一条微博为喜欢然后稍后上线之后操作会自动执行。冻结的操作也会被持久化到磁盘当app进入后台之后。并且会在稍后app恢复之后自动执行。
MKNetworkOperation中的便捷方法
MKNetworkOperation 提供了一些如下便捷方法来方便你格式化你的响应数据
- responseData
- responseString
- responseJSON
- responseImage
- responseXML
- error
</pre><pre name="code" class="objc">MKNetworkEngine *engine = [[MKNetworkEngine alloc] initWithHostName:@"192.168.2.176:3000" customHeaderFields:nil];
MKNetworkOperation *op = [engine operationWithPath:@"/index" params:nil httpMethod:@"GET" ssl:NO];
[op addCompletionHandler:^(MKNetworkOperation *operation) {
NSLog(@"[operation responseData]-->>%@", [operation responseString]);
}errorHandler:^(MKNetworkOperation *errorOp, NSError* err) {
NSLog(@"MKNetwork request error : %@", [err localizedDescription]);
}];
[engine enqueueOperation:op];
POST:
<span style="font-size:14px;">MKNetworkEngine *engine = [[MKNetworkEngine alloc] initWithHostName:@"192.168.2.176:3000" customHeaderFields:nil];
NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
[dic setValue:@"admin" forKey:@"username"];
[dic setValue:@"123" forKey:@"password"];
MKNetworkOperation *op = [engine operationWithPath:@"/login" params:dic httpMethod:@"POST"];
[op addCompletionHandler:^(MKNetworkOperation *operation) {
NSLog(@"[operation responseData]-->>%@", [operation responseString]);
}errorHandler:^(MKNetworkOperation *errorOp, NSError* err) {
NSLog(@"MKNetwork request error : %@", [err localizedDescription]);
}];
[engine enqueueOperation:op];</span><span style="font-size:24px;"> </span>
下载文件:
<span style="font-size:14px;">+(MKNetworkOperation*) downloadFatAssFileFrom:(NSString*) remoteURL toFile:(NSString*) filePath {
MKNetworkEngine *engine = [[MKNetworkEngine alloc] initWithHostName:@"127.0.0.1:5558" customHeaderFields:nil];
MKNetworkOperation *op = [engine operationWithURLString:remoteURL
params:nil
httpMethod:@"GET"];
[op addDownloadStream:[NSOutputStream outputStreamToFileAtPath:filePath
append:YES]];
[engine enqueueOperation:op];
return op;
}
+(void)testDownload{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachesDirectory = [paths objectAtIndex:0];
NSString *downloadPath = [cachesDirectory stringByAppendingPathComponent:@"DownloadedFile.pdf"];
MKNetworkOperation *downloadOperation=[HttpManager downloadFatAssFileFrom:@"http://127.0.0.1:5558/QQ"
toFile:downloadPath];
[downloadOperation onDownloadProgressChanged:^(double progress) {
//下载进度
NSLog(@"download progress: %.2f", progress*100.0);
}];
//事件处理
[downloadOperation addCompletionHandler:^(MKNetworkOperation* completedRequest) {
NSLog(@"download file finished!");
} errorHandler:^(MKNetworkOperation *errorOp, NSError* err) {
NSLog(@"download file error: %@", err);
}];
} </span>
<span style="font-size:14px;">+(MKNetworkOperation*) uploadImageFromFile:(NSString*) filePath mimeType:(NSString *)fileType{
MKNetworkEngine *engine = [[MKNetworkEngine alloc] initWithHostName:@"127.0.0.1:5558" customHeaderFields:nil];
MKNetworkOperation *op = [engine operationWithPath:@"upload"
params:[NSDictionary dictionaryWithObjectsAndKeys:
@"admin", @"username",
@"123", @"password",nil]
httpMethod:@"POST"];
[op addFile:filePath forKey:@"media" mimeType:fileType];
// setFreezable uploads your images after connection is restored!
[op setFreezable:YES];
[op addCompletionHandler:^(MKNetworkOperation* completedOperation) {
NSString *responseString = [completedOperation responseString];
NSLog(@"server response: %@",responseString);
} errorHandler:^(MKNetworkOperation *errorOp, NSError* err){
NSLog(@"Upload file error: %@", err);
}];
[engine enqueueOperation:op];
return op;
}
+(void)testUpload{
NSString *uploadPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"SampleImage.jpg"];
// NSData *myData = [NSData dataWithContentsOfFile:uploadPath];
// NSLog(@">>>>>>>>%@",myData);
MKNetworkOperation *uploadOperation = [HttpManager uploadImageFromFile:uploadPath mimeType:@"jpg"];
[uploadOperation onUploadProgressChanged:^(double progress) {
// 上传进度
DLog(@"Upload file progress: %.2f", progress*100.0);
}];
} </span>
总结 :