天天看点

IOS:MKNetworkKit的简介与使用

MKNetworkKit  资源地址:https://github.com/MugunthKumar/MKNetworkKit

一、MKNetworkKit的介绍

      MKNetworkKit是一个 O-C 编写的网络框架,支持块,ARC且用法简单。MKNetworkKit集 ASIHTTPRequest 和 AFNetworking 两个框架于一体。ASIHTTPRequest 框架是一个用O-C编写,对 CFNetwork API 进行了封装,并且使用简便的一套API,可以用于各种从简单到复杂的HTTP请求,或者可用于处理Amazon S3、Rackspace等REST服务的强大框架,可以说是网络框架的终结者,但是Ben在2011年9月21日就已经声明停止开发和支持该框架。而AFNetworking相对于只有两个类的MKNetworkKit框架,便显得有些繁琐了,所以这次决定初步研究一下MKNetworkKit的入门。

二、MKNetworkKit 新特性的了解与初步解析

       首先要导入一些依赖框架,CFNetwork.Framework、SystemCofiguration.framework、Security.framework。

      相对于ASIHTTPRequest 和 AFWorking ,MKNetworkKit 增添了一些新的功能,现在我们看看这些新特性和相关的demo。

      ①超轻量级框架。

       MKNetworkKit 整个框架只有两个类(MKNetworkEngine与MKNetworkOperation)和一些类别方法(Categories和Reachability)。

       MKNetworkEngine是整个框架的核心,MKNetworkOperation是对网络的一些具体操作。如果想找相关方法的实现,找的也很方便,但是作者有些放封装的很好,还需要慢慢解析。

      ②完全支持Arc

       MKNetworkKit完全支持Arc,Arc通常比非Arc代码更快,而且目前Arc的使用量越来越广,逐渐成为主流,而且的确十分方便,所以在新的项目中使用新的网络框架时就不用再放弃之前已有的框架了。

      ③在整个程序中只有一个全局队列

关于拥有网络请求的的APP而言,控制网络线程并发数是重中之重,控制不好极其容易出现各种错误。所以MKNetworkKit 使用单例生成一个全局共享单例来保证这个问题。demo如下:

//实例化一个共享队列

static NSOperationQueue *_sharedNetworkQueue;

  ④显示网络状态指示

     MKNetworkKit使用了单例的共享队列,在共享队列中有一个线程通过KVO方式随时观察operationCount属性,通过观察者方法随时改变网络状态。demo如下:

KVO观察:

//单例、KVO观察_sharedNetworkQueue中的operationCount,并将队列的并发连接最大数设置为6

+(void) initialize {

  //单例

  if(!_sharedNetworkQueue) {

      //线程保护

      static dispatch_once_t oncePredicate;

      dispatch_once(&oncePredicate, ^{

        //初始化_sharedNetworkQueue

        _sharedNetworkQueue = [[NSOperationQueuealloc]init];

        [_sharedNetworkQueueaddObserver:[selfself]

                              forKeyPath:@"operationCount"

                                 options:0

                                 context:NULL];

        //将队列的并发连接数设置为6

        [_sharedNetworkQueuesetMaxConcurrentOperationCount:6];

    });

  }

}

KVO观察者方法:

+ (void) observeValueForKeyPath:(NSString *)keyPath

                       ofObject:(id)object

                         change:(NSDictionary *)change

                        context:(void *)context

{

  //如果被观察对象是全局共享队列_sharedNetworkQueue且观察的属性值是线程并发数 operationCount

  if (object == _sharedNetworkQueue && [keyPath isEqualToString:@"operationCount"]) {

    [[NSNotificationCenterdefaultCenter]postNotificationName:kMKNetworkEngineOperationCountChanged

                                                        object:[NSNumber numberWithInteger:(NSInteger)[_sharedNetworkQueueoperationCount]]];

#if TARGET_OS_IPHONE

    //networkActivityIndicatorVisible网络活动可视指示(请求网络时状态栏上会有小图标转动,这是一个BOOL型,如果全局共享队列_sharedNetworkQueue的连接并发数大于0则为YES)

    [UIApplication sharedApplication].networkActivityIndicatorVisible =

    ([_sharedNetworkQueue.operationscount] >0);

#endif

  }

    //如果观察对象不是我们想要的

  else {

    [super observeValueForKeyPath:keyPath ofObject:object

                           change:changecontext:context];

  }

}

 ⑤自动改变队列大小

      绝大部分的移动网络不允许两个以上的并发连接,因此一般队列在3G网络下应该设置为2。而MKNetworkKit会为自动处理队列大小问题,当网络处于3G情况时,并发数为2,但是当网络处于WiFi环境下时,则自动调整到6。实现demo在MKNetworkEngine.m类中,首先是在初始化方法中注册通知,用来监听网络类型,当网络类型发生变化时,随即队列大小也一同改变。具体实现demo如下:

- (id) initWithHostName:(NSString*) hostName portNumber:(int)portNumber apiPath:(NSString*) apiPath customHeaderFields:(NSDictionary*) headers;

注册通知:

if(hostName) {

        [[NSNotificationCenterdefaultCenter]addObserver:self

                                               selector:@selector(reachabilityChanged:)

                                                   name:kReachabilityChangedNotification

                                                 object:nil];

      //域名

      self.hostName = hostName;

      self.reachability = [ReachabilityreachabilityWithHostname:self.hostName];

      //开启一个异步线程

      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

        //启动通知

        [self.reachabilitystartNotifier];

      });

    }

通知触发的方法:

//通知触发的事件,转变网络状态

-(void) reachabilityChanged:(NSNotification*) notification

{

  //如果当前网络类型为WiFi,并将队列并发连接最大数设置为6

  if([self.reachabilitycurrentReachabilityStatus] ==ReachableViaWiFi)

  {

    DLog(@"Server [%@] is reachable via Wifi",self.hostName);

    //将队列并发连接最大数设置为6

    [_sharedNetworkQueuesetMaxConcurrentOperationCount:6];

    //检查和恢复冷冻操作

    [self checkAndRestoreFrozenOperations];

  }

  //如果当前网络类型为WWAN,将队列并发连接最大数设置为0

  else if([self.reachabilitycurrentReachabilityStatus] ==ReachableViaWWAN)

  {

    if(self.wifiOnlyMode) {

      DLog(@" Disabling engine as server [%@] is reachable only via cellular data.",self.hostName);

      [_sharedNetworkQueuesetMaxConcurrentOperationCount:0];

    }

    //如果为蜂窝数据类型,将队列并发连接最大数设置为2

    else {

      DLog(@"Server [%@] is reachable only via cellular data",self.hostName);

      [_sharedNetworkQueuesetMaxConcurrentOperationCount:2];

      //检查和恢复冷冻操作

      [self checkAndRestoreFrozenOperations];

    }

  }

  //未知网络

  else if([self.reachabilitycurrentReachabilityStatus] ==NotReachable)

  {

    DLog(@"Server [%@] is not reachable",self.hostName);

    //调用冷冻操作

    [self freezeOperations];

  }

  if(self.reachabilityChangedHandler) {

     self.reachabilityChangedHandler([self.reachability    currentReachabilityStatus]);

  }

}

⑥自动缓存

      MKNetworkKit会自动缓存所有的GET请求,当再次发送相同的请求时,MKNetworkKit随即就能调用response缓存(可用条件下)传递给handler进行处理。而且也会同时向服务器发出请求,一旦获取服务器数据,handler会被再次要求处理新的数据。也就是我们只需要使用:

      [[MKNetworkEngineshareEngine]useCache];

      也可以子类化覆写这个方法,自定义缓存路径和缓存占用的内存开销。

⑦冻结网络操作

      MKNetworkKit能够“冻结”网络操作。在一个网络被“冻结”情况下,一旦网络断开,它们将自动序列化并在设备再次连接网络时在被提交一次。

-(void) freezeOperations {

  //如果不是isCacheEnabled启动缓存,直接返回结束

  if(![selfisCacheEnabled])return;

  //遍历全局共享线程中的操作,并由MKNetworkOperation实例化接收

  for(MKNetworkOperation *operationin_sharedNetworkQueue.operations) {

    //如果不是可冻结操作,跳过此次循环

    // freeze only freeable operations.只能冻结操作

    if(![operation freezable]) continue;

    //如果没有域名,直接结束遍历

    if(!self.hostName)return;

    // freeze only operations that belong to this server冻结操作只属于此服务器

    if([[operation url] rangeOfString:self.hostName].location ==NSNotFound)continue;

    NSString *archivePath = [[[selfcacheDirectoryName]stringByAppendingPathComponent:[operationuniqueIdentifier]]

                             stringByAppendingPathExtension:kFreezableOperationExtension];

    [NSKeyedArchiver archiveRootObject:operation toFile:archivePath];

    //操作取消

    [operation cancel];

  }

}

//检查和恢复冷冻操作,用来再次执行那些网络连接失败的操作

-(void) checkAndRestoreFrozenOperations {

  //如果没有启动缓存,直接返回

  if(![selfisCacheEnabled])return;

  //构建错误内容

  NSError *error = nil;

  NSArray *files = [[NSFileManagerdefaultManager]contentsOfDirectoryAtPath:[selfcacheDirectoryName]error:&error];

  //如果出错

  if(error)

    DLog(@"%@", error);

  NSArray *pendingOperations = [filesfilteredArrayUsingPredicate:[NSPredicatepredicateWithBlock:^BOOL(id evaluatedObject,NSDictionary *bindings) {

    //将求值对象转换为字符串

    NSString *thisFile = (NSString*) evaluatedObject;

      //rangeOfString:获取指定短字符串在长字符串中的开始,结尾索引值

      //判断thisFile里面是否是否拥有kFreezableOperationExtension,有的话返回

    return ([thisFile rangeOfString:kFreezableOperationExtension].location !=NSNotFound);

  }]];

  //遍历待定操作的数组

  for(NSString *pendingOperationFilein pendingOperations) {

      //获取这些待定操作的路径

    NSString *archivePath = [[selfcacheDirectoryName]stringByAppendingPathComponent:pendingOperationFile];

      //将这些路径字符串赋值为MKNetworkOperation对象

    MKNetworkOperation *pendingOperation = [NSKeyedUnarchiverunarchiveObjectWithFile:archivePath];

      //入队操作

    [self enqueueOperation:pendingOperation];

    NSError *error2 = nil;

      //入队操作后移除archivePath

    [[NSFileManager defaultManager] removeItemAtPath:archivePatherror:&error2];

    if(error2)

      DLog(@"%@", error2);

  }

}

⑧类似的请求只执行一个操作

      当我们加载缩略图(针对 twitter stream)时,最终要为每个实际的图片创建一个新的请求。而事实上我们请求的都是同一个URL。MKNetworkKit对于队列中的每个GET请求都只会执行一次。在MKNetworkEngine.m中存在这样的方法:

//取消包含相同URL字符串的操作

+(void) cancelOperationsContainingURLString:(NSString*) string {

  //取消相同操作

  [self cancelOperationsMatchingBlock:^BOOL (MKNetworkOperation* op) {

    return [[op.readonlyRequest.URLabsoluteString]rangeOfString:string].location !=NSNotFound;

  }];

}

//取消相同的操作

+(void) cancelOperationsMatchingBlock:(BOOL (^)(MKNetworkOperation* op))block {

    //获取全局共享队列_sharedNetworkQueue的正在进行的所有操作

    NSArray *runningOperations =_sharedNetworkQueue.operations;

    //enumerateObjectsUsingBlock枚举对象使用块

    [runningOperations enumerateObjectsUsingBlock:^(id obj,NSUInteger idx,BOOL *stop) {

        MKNetworkOperation *thisOperation = obj;

        //如果这个操作存在就取消这个操作

        if (block(thisOperation))

            [thisOperation cancel];

    }];

}

//取消所有操作

-(void) cancelAllOperations {

  //是否存在存在域名

  if(self.hostName) {

    //取消所有包含当前域名字符串的操作

    [MKNetworkEngine cancelOperationsContainingURLString:self.hostName];

  } else {

    //没有构建域名,不能取消操作

    DLog(@"Host name is not set. Cannot cancel operations.");

  }

}

⑨图片缓存

      MKNetworkKit内置了缩略图缓存。只需要子类化覆盖几个方法便可以自定义设置内存中最大能缓存的图片的数量,以及缓存目录的路径。

     如下方法,直接将内存缓存存入硬盘,随即销毁内存缓存。

//保存缓存,将内存缓存转移到硬盘中

-(void) saveCache {

  //遍历内存缓存的所有key

  for(NSString *cacheKey in [self.memoryCache allKeys])

  {

      //拼接路径

    NSString *filePath = [[self cacheDirectoryName] stringByAppendingPathComponent:cacheKey];

      //如果所在路径存在文件

    if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {

      NSError *error = nil;

        //移除项目路径

      [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];

      ELog(error);

    }

    [(self.memoryCache)[cacheKey] writeToFile:filePath atomically:YES];

  }

  //移除所有对象

  [self.memoryCache removeAllObjects];

  [self.memoryCacheKeys removeAllObjects];

  //拼接字符串

  NSString *cacheInvalidationPlistFilePath = [[self cacheDirectoryName] stringByAppendingPathExtension:@"plist"];

    //将可变字典cacheInvalidationParams写入到cacheInvalidationPlistFilePath中

  [self.cacheInvalidationParams writeToFile:cacheInvalidationPlistFilePath atomically:YES];

}

⑩性能

      MKNetworkKit缓存是内置的,就如NSCache,当发现内存警告时,缓存到内存的数据将被写入缓存目录。实现方法是注册通知,监听内存警告,当发生内存警告时,便调用saveCache方法,将内存缓存写入缓存目录,并移除内存缓存。

//注册通知,收到内存警告时调用saveCache方法

  [[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(saveCache)

                                               name:UIApplicationDidReceiveMemoryWarningNotification

                                             object:nil];

三、MKNetworkKit的使用

MKNetworkKit 的用法也很简单,这里就仅仅实现一个get方法,加载一个网络图片。在此方法中,发送request请求的操作加入请求队列时就执行了,很明显的说明了MKNetworkKit框架很好的封装性。

       首先,我们新建一个工程,然后将MKNetworkKit文件夹添加进来,记得copy。      

       因为MKNetworkKit完全支持Arc机制,所以此时我们只用导入SystemConfiguration.framework,CFNetwork.framework,Security.framework和 ImageIO.framework。

       然后,在工程中Supporting Files 文件夹中的以 .pch 的文件中加入“MKNetworkKit.h”类,这时,你就可以随时随地调用MKNetworkKit中的方法了。

 #ifdef __OBJC__

    #import <UIKit/UIKit.h>

    #import <Foundation/Foundation.h>

    #import "MKNetworkKit.h"

或者,你只需要在你需要使用MKNetworkKit的类中导入”MKNetworkKit.h”就行了。

      加载网络图片demo示例:

//加载图片的URLhttp://pic1.win4000.com/pic/4/3f/4124407336.jpg

        MKNetworkEngine *engine = [[MKNetworkEnginealloc]initWithHostName:@"pic1.win4000.com"apiPath:@"pic/4/3f/4124407336.jpg"customHeaderFields:Nil];

        MKNetworkOperation *operation = [engineoperationWithPath:Nilparams:NilhttpMethod:@"GET"];

        //添加完成处理程序

        [operation addCompletionHandler:^(MKNetworkOperation *completedOperation) {

            //请求成功,为_imgView添加图片

            _imgView.image = [UIImage imageWithData:[completedOperation responseData]];

        } errorHandler:^(MKNetworkOperation *completedOperation, NSError *error) {

            //请求出错

            NSLog(@"%@",completedOperation.error);

        }];

        //发起网络请求

        [engine enqueueOperation:operation];

继续阅读