如何在iOS中优雅的处理网络请求

在iOS开发过程中,如何为UIViewController瘦身是一个常见并且复杂的问题。为什么常见?如果没有精心的设计,UIViewController很容易变成很厚重、冗余。为什么复杂?在真实的项目中,都有各自的历史问题,要在现有的代码中瘦身是个非常有挑战的工作。

其实所谓的瘦身,核心就是要单一职责,这个跟面向对象的设计一样。像MVVM也是将之前UIViewController中处理的工作转换到其它的对象中。

在移动端开发的过程中,总是避免不了请求各种网络接口,这是移动App的特点。那么与网络接口交互的代码是一个界面的重要组成部分,这些部分完全可以独立成单独的层。而这些网络接口的请求和处理也都有自己的业务逻辑。

那么应该如何把网络接口交互的代码独立出来呢?我的答案是把单个接口封装成单独的类,这个类接收请求的参数,处理接口返回的结果。在PP的时候就已经在实践这种模式了,效果还不错。在最近的项目中,通过整合AFNetworkingMantle这2大常用库,完成了最新的网络框架GQDataController,她可以自动的将接口返回的JSON转成Mantle对象。项目地址见:https://github.com/gonefish/GQDataController

下面我们将通过一个例子来描述如何使用GQDataController将网络请求处理的代码封装成单独的类。

要开始使用GQDataController,必须先创建自己的子类,就像你继承UIViewController一样。

.h文件

@interface BasicDataController : GQDataController

@property (nonatomic, strong) NSString *ip;

@end

你可以添加自定义的属性,用于保存接口返回的结果。

.m文件

@implementation BasicDataController

- (NSArray *)requestURLStrings
{
    return @[@"http://httpbin.org/ip"];
}

- (void)handleWithObject:(id)object
{
    [super handleWithObject:object];
    self.ip = [object objectForKey:@"origin"];
}

@end

requestURLStrings用于返回请求接口的地址,支持多个地址备用。子类重写handleWithObject:方法,并将返回的JSON对象赋值给实例属性。

在ViewController中创建实例,然后调用request开始请求(默认是GET请求,子类可以重写requestMethod来进行修改)最后通过Delegate方法处理结果,非常简单清晰的流程。


- (void)viewDidLoad {
    [super viewDidLoad];

    self.basicDataController = [[BasicDataController alloc] initWithDelegate:self];

    [self.basicDataController request];
}

- (void)dataControllerDidFinishLoading:(GQDataController *)controller
{
    self.label.text = [NSString stringWithFormat:@"IP: %@", self.basicDataController.ip];   
}

对UIViewController来说,它并不需要知道请求的是什么接口和如何处理结果。这种模式与MVVM类似,但服务的对象稍微有点区别。所以,你可以在老代码中使用这种模式,也可以与ViewModel一起使用。

上面的例子只是GQDataController的基本使用,在对接口的结果处理上是采用手动保存的。GQDataController另外一个非常重要的功能是提供了对Mantle的支持,你可以自动的将返回的JSON结果转换成指定的Mantle对象。

你只需要在.m文件中添加如何代码

- (Class)mantleModelClass
{
    return [IP class];
}

mantleModelClass返回用于转换成Mantle的类型,IP是一个Mantle子类。另外,子类也不需要重写handleWithObject了。

在ViewController中修改Delegate方法的内容

- (void)dataControllerDidFinishLoading:(GQDataController *)controller
{
    self.label.text = [NSString stringWithFormat:@"IP: %@", self.basicDataController.mantleObject.origin];   
}

GQDataController会将转换的结果保存在mantleObject和mantleObjectList中。如果转换的JSON是字典对象会保存在mantleObject中,如果是数组对象则会保存在mantleObjectList中。

@property (nonatomic, strong, nullable) __kindof MTLModel<MTLJSONSerializing> *mantleObject;

@property (nonatomic, strong, nullable) NSMutableArray<__kindof MTLModel *> *mantleObjectList;

GQDataController还提供了分页、DataSource、接口重试、接口Stub的支持,更多使用可以参考项目的Demo

通过使用GQDataController,强迫你把所有的接口都从UIViewController中分离出来,一方面减少了UIViewController的负担,另一方面也提高了接口对象的复用程度。而且,集成GQDataController非常简单,对现有代码模式影响非常小,你可以轻松的转换过来。

最近的这篇文章《 猿题库 iOS 客户端架构设计》也提到了类似的解决方案。

使用Launch Screen的坑

从iOS 8开始,可以使用XIB来制作启动页,这给了我们另外一种选择。

如果你的App已经开始使用XIB来做启动页,那么最好不要回退到设置启动图片的模式。因为这时系统很可能会缓存你原来的XIB界面:(,这样会看到2个启动界面闪现的问题。只有在用户删除App,重新安装才能解决。

iOS技术文章

为了收集和分享iOS的技术文章,我在Github上创建了iOSArticle项目,按时间收集优秀的技术文章。

https://github.com/gonefish/iOSDevArticle

2014年

2月

2013年

12月

11月

10月

9月

8月

7月

6月

5月

4月

2012

11月

5月

2011

5月

iOS App版本迭代问题思考

MIUI每周更新一个版本,按雷军的说法:“用互联网模式开发手机操作系统”。传统软件的开发周期非常长,而操作系统就更长了,而互联网模式的特点就是快速迭代,及时响应用户需求及修复问题,这很符合敏捷开发的原则。对于新生、竞争激烈的移动互联网行业来说,快速进行版本迭代非常有必要。

在这2年的iOS开发工作中,迭代了许多版本,迭代流程也发生过变化,在这些迭代版本之间碰到了许多问题,这些问题可能是特有的,但也有些团队开发通常碰到的问题。而这些问题会影响版本周期的稳定性,常常导致无法按时发布,扰乱开发节奏,如:出现同时开发3个版本的情况。以下列出我碰到过影响迭代周期的因素:

App Store审核

iOS的App Store渠道是需要审核的,而这个时间基本有一定的不可预测性,Apple会以某些理由拒绝你。一般等待审核至少要1周,这个时间MIUI都更新一个版本了。这个时间是不可能停止开发,而等审核通过的。这就造成了一个版本的周期还没有结束,已经开始了下一个迭代版本的开发周期。

版本的开发周期

这个版本的定义还是类似于传统软件的定义,只不过周期更短,功能更少。我觉得这跟敏捷开发中快速迭代版本相差甚远。由于还是预先定义好版本的功能,因此给临时插入需求提供了机会。

需求

在稍微大一点的公司,需求可能分为2类:功能需求和非功能需求。功能需求我认为是App本身应该具备的功能,能够解决用户的实际问题。非功能需求基本大多来自于运营或合作。在版本的开发周期中,总会碰到紧急的需求,要求必需在当前迭代的版本完成。

质量

代码质量也是影响发布的重要因素,《人件》有说,“质量是免费的”,只需要花费点时间。如果团队够小够精干这个问题不大。但如果团队不小,并非所有成员都是精干,而需求也各种各样,这就可能造成许多代码质量不高。这就对团队中的软件工程化提出了更高的要求。如何管理迭代分枝的代码?是否采用特性分枝的开发模式?QA的工作如何快速的展开?持续集成如何展开?是否有单元测试?如何更好的进行跨部门合作?

针对这些问题,如何制定适合公司环境的流程,来保证迭代周期的稳定性变成了一个有意思挑战。

iOS开发小记之Appearance方法

从iOS 5开始,UIViewController会自动调用subviews中,单独UIViewController的Appearance相关方法(即使没有调用addChildViewController也会),如:

viewWillAppear:

viewDidAppear:

viewWillDisappear:

viewDidDisappear:

在一些复杂的界面中通常这个特性非常有用,因为可能会需要对某部分界面创建自己的UIViewController。但有时还是希望自己维护这类视图状态方法,在iOS 5中需要重写这个方法:

– (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers

iOS 6中这个方法被分成了2个

– (BOOL)shouldAutomaticallyForwardRotationMethods

– (BOOL)shouldAutomaticallyForwardAppearanceMethods

这通常在自定义容器控制器中非常有用,如果想了解如何实现Custom Container View Controller可参考下面的实现:

https://github.com/gonefish/GQFlowController

新的开源项目GQFlowController

GQFlowController实现了一个多层的容器视图控制器,其目标是实现类似于网易新闻客户端iPhone的UI结构(主要是3.0之后的版本)。

特性:

  • 支持从4个不同方向滑入或滑出视图控制器界面
  • 多层视图控制器结构
  • 滑动手势
  • ARC支持
  • 支持iPad(暂不支持)

 

通过这个项目可以让你方便的创建多层的UI结构:类网易新闻客户端或类Path应用。

iOS 7 好戏即将开始

iOS 7绝对是个非常好的改进,而且意义深远。

在Jobs走后,Apple还能否继续引领潮流成为大家关心的话题,iOS 7则是绝好的回应。

去年的iOS 6所带来的改变非常有限,而且还造成了许多问题,这可能不是偶然发生,或许是当前的方向碰到了瓶颈。而这种瓶颈不解决,可能会造成极大的影响。所以iOS 7抛弃了原有拟物化设计,而这个行为非常的大胆,而这次的UI风格改版可能极有战略意义,因为Apple的目标不仅仅在手持设备上,很显然对于不是采用手来交互的设备,拟物化是不能满足需求。

iOS 7的使用体验非常的好,虽然在图标上有一些争议,但看习惯后,会觉得非常好,因为跟系统非常的的融合。因为拿之前的图标和新系统一起比,当然会非常奇怪。iOS 7也借鉴了一些其它OS的设计,这是非常明显的。但这也证明某些东西确实是好的,不过Apple应该是不会原封不动搬过来的。可惜的是WebOS没有被Apple收购…在这个Beta版本中还存在一些问题,如:待办事项不好用;指南针也奇怪。不过,还是蛮期待正式版的。

从iOS 7公开的API接口来看,1年内绝对会推荐新的产品,估计会是电视。这样,明年应该可以与Xbox One、PS 4开始客厅争夺战。

通过iOS与父母沟通

去年给家里买了台iPad,教我爸用着还可行,不过App都是我安装的,但文字输入确实是个问题。随着用的越来越熟悉,我爸开始提出些其它需求:如何跟我聊天?怎么把照片(这次旅行拍的一些照片)放到iPad去?虽然方法有很多种,但有些都不简单,于是准备测试一下iOS原生的功能:

照片流分享:一直没怎么用这个功能,也许会非常简单,在家里的iPad上设置好另外一个Apple ID,然后我可以把我拍的照片通过“照片流分享”功能,发给我爸看。

FaceTime:直接按联系人进行视频通话。

iMessage:通过文字交流,这个估计有点难,2代iPad不支持Siri输入…

这样真的So easy啊!就像Apple广告中的那样。

Mac上的OneNote解决方案

不得不说OneNote是一款非常好的软件,可惜Office for Mac没有这款软件。虽然EverNote有Mac版,但界面确实让我感觉很难用。

MS确实是云服务上下了点功夫,在SkyDrive服务中提供了在线版的OneNote,而且还有Word、Excel、PowerPoint。Metro风格的界面确实比较简洁,这让整个界面看起来比较干净。整个服务都是免费的,没有广告,功能虽然没有原生的多,但已经够用了。SkyDrive有Mac的客户端,也能支持同步,就跟DropBox一样,OneNote文档在Mac同步目录中是一个链接,双击后就在浏览器中打开了该文档,还是挺方便的。另外OneNote有iOS版本,虽然功能比较弱,但还是可以用,SkyDrive for iOS就有点挫了,没看到有什么功能。

从最近的使用来看,2个层级已经可以很好的帮助你管理内容了。太灵活的东西往往不容易形成模式,没有模式的话易用性会很差。

Objective-C运行时的类操作例子

Objective-C是一种动态特性非常强大的语言,但在日常使用过程中可能不会太接触到一些底层的使用方式,最近发现了一个项目很好的展现了这类特性。

怎样扩展UITabBarController大概是每个iOS开发者都会碰到的问题,我在实践中尝试用一些方法来解决这类问题,在没有完美解决这个问题后,我开始寻找一些其它的实现方案。最近在github上找到了名为JBTabBarController项目,在看过源码之后,我觉得这个项目的实现非常巧妙,很好的利用了Objective-C的动态特性,而这类动态特性封装由另外一个项目JRSwizzle来提供。这个项目非常有意思,其实现原理可先看看这篇文章深入浅出Cocoa之Method Swizzling

重点在https://github.com/jinthagerman/JBTabBarController/blob/master/JBTabBarController/UINavigationController%2BJBAdditions.m中

+ (void)initialize {
    if (self == [UINavigationController class]) {
        [UINavigationController jr_swizzleMethod:@selector(pushViewController:animated:)
                                      withMethod:@selector(jb_pushViewController:animated:)
                                           error:nil];
    }
}
– (void)jb_pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
    viewController.JBTabBarController = self.JBTabBarController;
    [self jb_pushViewController:viewController animated:animated];
}

在运行时非常优雅的修改了pushViewController:animated:消息的实现,这种思路让我为之一亮。

另外,这位高人写的 深入浅出Cocoa系列 文章非常之不错,强烈建议学习。