这片文章也有塞尔维亚-克罗地亚语(由Jovana Milutinovich翻译)和日语(由@noradaiko翻译)
如果有个一个网络库能够自动的为你处理cache该有多好啊。
如果有一个网络库能够在设备离线的时候自动的记住用户的操作该有多酷啊。
当你离线的时候,你喜欢了一条微博或者把一条新闻标记为已读,然后网络库会在设备连网后自动执行这些操作,并且还不用写一行多余的代码。下面我们就介绍MKNetworkKit可以做到这些。
什么是 MKNetworkKit?
MKNetworkKit 是一个用objective-c写的网络库,具有无缝连接,基于block,ARC支持以及易用等特点。
MKNetworkKit的灵感来自于其他两个流行的网络库:ASIHTTPRequest和AFNetworking,结合了两个库的共同特点,并且有一些新的特性。除此之外,MKNetworkKit可能会比其他网络库而言为了代码的清晰性,要求你写一丁点多的代码。用了MKNetworkKit,你很难写出丑陋的网络代码。
特性
超轻量级
The complete kit is just 2 major classes and some category methods. This means, adopting MKNetworkKit should be super easy.
完整的库只有2个主类和一些category方法。也就是说,采用MKNetworkKit是非常容易的。
整个应用共享单一队列
严重依赖网络连接的应用应该优化他们的网络并发连接数。十分不幸的是,现在还没有网络库可以正确的完成这些功能。让我来举个例子说明如果你不去优化或者控制网络的并发连接数会发生什么。
Let’s assume that you are uploading a bunch of photos (think Color or Batch) to your server. Most
假如你正在上传一系列的图片(比如Color和 Batch)到服务器。大多数的移动网络(3G)不允许一个给定的IP地址超过两个的并发的http请求。这就是说,在你的设备上,3G网络下,你不能同时打开超过两个的并发HTTP请求。EDGE网络就更差了,大多数情况下你甚至不能打开超过一个的连接。这个限制在传统的wifi的情况下是相当高的(6个)。但是,你的设备并不总是连接到Wifi下,你应该为受限制的网络环境考虑。在最普通的情况下你的设备都是连接到3G网络,就是说你被限制同时只能上传2张图片。现在问题的关键不是上传两张图片时很慢,而是当你上传图片时再打开一个新的View,这个view在加载图片的缩略图的时候。当你不去通过app控制正确的队列大小时,你的缩略图加载操作就会超时,这种现象可不是正确的。正确的做法是把缩略图的加载排好优先级,或者等待上传完成后再加载缩略图。这就要求你的app有一个全局的队列。MKNetworkKit自动的保证你的app的每一个队列的实例使用单一的共享队列。虽然MKNetworkKit自己不是单例的,但是他的共享队列是。
正确的显示网络连接的标志
现在有许多第三方的类使用记录网络调用的次数的方式来控制网络链接标志的显示。但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,你就拥有了冻结网络操作的能力。当你冻结一个操作的时候,为了防止网络连接丢失,操作会被自动序列化并且在设备联网之后自动执行。想想微博客户端的草稿箱功能。
当你发送一条微博时,把这个网络操作标记为冻结,MKNetworkKit会自动的处理冻结和解冻工作!然后这条微博你不用写一行代码就会自动稍后发送。你可以使用在比如标记一条微博为喜欢或者从google reader客户端分享一篇文章或者添加一个连接到instpaper等类似场景中。
Image Caching 图片缓存
MKNetworkKit可以无缝的缓存缩略图。通过重写几个方法,你可以设置缓存多少张图片在内存缓存以及缓存的目录。重写这些方法完全是可选的。
Performance 性能
一个词:速度。MKNetworkKit的缓存是无缝的,就像NSCache一样工作,除此之外,当内存警告的时候,内存缓存会写入到缓存目录中。
Full support for Objective-C ARC 全面支持Ojbective-C ARC
一般你会为新的项目选择一个新的网络库。MKNetworkKit不是为了取代你现有的(当然你也可以取代,但是可能会很麻烦)。在新的项目中,你会很想要使用ARC功能。MKNetworkKit可能是唯一全面支持ARC的网络库。基于ARC管理的代码速度可能比非ARC的代码是数量级的差距。
How to use 如何使用
OK,不自吹了,让我们来看看如何使用这个framework。
Adding the MKNetworkKit 添加MKNetworkKit
- 拖拽MKNetworkKit的目录到你的工程里
- 添加 CFNetwork.Framework, SystemConfiguration.framework and Security.framework 依赖库
- 在PCH文件中包含 MKNetworkKit.h
- 删除 NSAlert+MKNetworkKitAdditions.h如果你开发的是iOS程序。
- 删除 UIAlertView+MKNetworkKitAdditions.h如果你开发的是Mac程序。
就是这样子,仅仅5个核心文件,妈妈再也不用担心网络请求了。
MKNetworkKit 中的类
- MKNetworkOperation
- MKNetworkEngine
- 混杂的帮助类 (苹果的 Reachability) 以及一些cateogory方法。
我相信简洁就是美。苹果已经把实际网络操作的繁重工作都做了,所以第三方的网络库应该提供的就是一个优雅的队列和可选的缓存。我坚信,任何第三方的库都应该少于10个类(不管是网络库还是UIkit的替代库)。多过10个,就是过度。Three 20库就是一个臃肿的例子,ShareKit也是,也许这两个库不错,但是仍然是庞大而且臃肿的。ASIHttpRequest或者AFNetworking比RESTKit而言都是轻量级的,JSONKit比起TouchJSON(或者其他任何TouchCode库)就是轻量级的。也许就我一个人喜欢这样子,但我就是不能忍受我的app里有第三方的库比我的代码还要多。
巨大的库的问题就在于很难去理解他的内在的工作机制,而且很难去根据自己的需求定制。我的添加IAP的MKStoreKit库就是超级易用的而且我相信MKNetWorkKit也是如此。使用MKNetworkKit,你只需要知道MKNetworkOperation 和 MKNetworkEngine两个类所暴露出来的方法。MKNetworkOperation类似于ASIHttpRequest类,是一个NSOperation的子类并且封装了请求相应类。你需要为你的app的每一个网络请求创建一个MKNetworkOperation。
MKNetworkEngine是一个假单例的类,负责管理你的app的网络队列。因此,简单的请求时,你应该直接使用MKNetworkEngine的方法。在更为复杂的定制中,你应该集成并子类化它。每一个MKNetworkEngine的子类都有他自己的Reachability对象来通知服务器的连通情况。你应该考虑为你的每一个特别的REST服务器请求子类化MKNetworkEngine。因为是假单例模式,每一个单独的子类的请求,都会通过仅有的队列发送。
你可以在应用的delegate里面retain MKNetworkEngine的实例,就像CoreDatademanagedObjectContext类。当你使用MKNetworkKit的时候,你创建一个MKNetworkEngine的子类来从逻辑上分组你的网络请求。就是说,Yahoo相关的请求都在一个类中,Facebook相关的请求都在另一个类中。我们会看到3个不同的使用库的例子。
例1:
让我们创建一个YahooEngine来获取雅虎财经的货币交换率。
第一步: 创建一个YahooEngine 继承自 MKNetworkEngine. MKNetworkEngine的初始化方法需要主机名和自定义的header(如果有)。自定义的头是可选的而且可以为nil,如果你正在编写自己的REST服务器(不是现在的情况)你可能需要考虑添加客户端版本以及其他类似客户端标识的元数据。
NSMutableDictionary *headerFields = [NSMutableDictionary dictionary]; [headerFields setValue:@"iOS" forKey:@"x-client-identifier"]; self.engine = [[YahooEngine alloc] initWithHostName:@"download.finance.yahoo.com" customHeaderFields:headerFields]; |
注意,雅虎不需要你发送x-client-identifider在header里面,所以上面的例子仅仅是为了演示这个功能特性。
既然代码都是ARC的,那么是否对Engine的实例进行strong引用取决于开发者。
当你创建一个MKNetworkEngine子类,Reachability的会自动实现。这样当服务器宕机或者其他不可预料的情况下时,请求会被自动的队列或者冻结。想要获取更多关于冻结操作的信息,请阅读后面冻结操作相关的章节。
步骤 2:设计 Engine 类 (分离考虑)
让我们开始在YahooEngine里面编写获取货币交换率的代码。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请求的格式
#define YAHOO_URL(__C1__, __C2__) [NSString stringWithFormat:@"d/quotes.csv?e=.csv&f=sl1d1t1&s=%@%@=X", __C1__, __C2__] |
你写的方法应该按照如下顺序执行:
- 准备好url和请求参数
- 创建一个请求的MKNetworkOperation对象.
- 设置方法参数
- 添加完成和错误处理方法,(完成方法就是你处理你响应到相应的model类的地方)
- 可选的,还有请求操作的进度指示。(或者在viewController里面处理)
- 如果你的操作时下载文件,那么设置一个下载的流(通常是一个文件)给他,这也是可选的
- 当请求操作完成时,处理结果并且调用block方法来向调用方法返回数据。
下面是实例代码:
MKNetworkOperation *op = [self operationWithPath:YAHOO_URL(sourceCurrency, targetCurrency) params:nil httpMethod:@"GET"]; [op onCompletion:^(MKNetworkOperation *completedOperation) { DLog(@"%@", [completedOperation responseString]); // do your processing here completionBlock(5.0f); }onError:^(NSError* error) { errorBlock(error); }]; [self enqueueOperation:op]; return op; |
上面的代码构造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 (Only on iOS 5)
- responseImage
- responseXML
- error
从网络请求获取响应很方便,当返回格式错误时,这些方法返回nil,比如试图从响应为html中获取image会返回nil,唯一可以确保返回正确结果的方法时responseData,如果你确定返回类型,可以用其他方法。
方便宏定义
宏定义Dlog和ALog是我不知羞耻的从stackoverflow上偷的,而且找不到源文件了,如果时你写的,请让我知道
关于GCD
我故意没用gcd,因为网络请求需要随时停止和安排优先级,gcd打给你然比NSOperationQueue更高效,但是不能做到上面两点。我不推荐在网络请求的队列中使用gcd。
关于文档
头文件都加了注释,而且我正在尝试整理,于此同事,你可以随时玩弄代码。
源代码
源代码和Demo工程都在Github,连接:MKNetworkKit on Github
关于新增特性
请不要email添加新特性,最好的方式是在github上添加issue
许可证
MKNetworkKit is licensed under MIT License
All of my source code can be used free of charge in your app, provided you add the copyright notices to your app. A little mention on one of your most obscure “about” page will do.
Attribution free licensing available upon request. Contact me at mknetworkkit@mk.sg