构buildiOSnetworking应用程序的最佳架构方法(REST客户端)

我是一个具有一定经验的iOS开发人员,这个问题对我来说真的很有趣。 我在这个主题上看到了很多不同的资源和材料,但是我仍然感到困惑。 什么是iOSnetworking应用程序的最佳体系结构? 我的意思是基本的抽象框架,模式,这将适合每个networking应用程序,无论是只有less数服务器请求的小应用程序,还是一个复杂的REST客户端。 苹果公司build议使用MVC作为所有iOS应用程序的基本架构方法,但MVC和更现代的MVVM模式都不能解释将networking逻辑代码放在哪里以及如何组织它。

我是否需要开发一些类似MVCSS for Service )的东西,并且在这个Service层中放置所有API请求和其他networking逻辑,这些东西可能真的很复杂? 在做了一些研究之后,我发现了两个基本的方法。 在这里build议为每个networking请求创build一个单独的类到Web服务API (如LoginRequest类或PostCommentRequest类等),它们都从基本请求抽象类AbstractBaseRequestinheritance,并且除了创build一些封装的全局networkingpipe理器常见的networking代码和其他偏好(如果我们有复杂的对象映射和持久性,甚至是使用标准API的自己的networking通信实现,则可能是AFNetworking定制或RestKit调优)。 但是这个方法对我来说似乎是一个开销。 另一种方法是像第一种方法一样使用一些单例API调度程序或pipe理器类, 但不是为每个请求创build类,而是将每个请求封装为此pipe理器类的实例公共方法,如: fetchContactsloginUser方法等。那么,最好的和正确的方法是什么? 还有其他有趣的方法我还不知道?

我应该为所有这个networking的东西,如Service ,或NetworkProvider层或任何我的MVC架构上创build另一层,或者这个层应该被集成(注入)到现有的MVC层,例如Model

我知道那里有美丽的方法,或者像Facebook客户端或LinkedIn客户端这样的移动怪物如何处理networking逻辑的指数级增长的复杂性?

我知道这个问题没有确切而正式的答案。 这个问题的目标是收集有经验的iOS开发人员最有趣的方法 。 最好的build议方法将被标记为接受和奖励声誉赏金,其他人将被高举。 这主要是一个理论和研究问题。 我想了解iOS中的networking应用的基本,抽象和正确的架构方法。 我希望有经验的开发人员的详细解释。

I want to understand basic, abstract and correct architectural approach for networking applications in iOS :构build应用架构没有 “最好”或“最正确”的方法。 这是一个非常有创意的工作。 您应该总是select最直接,最具扩展性的架构,对于开始为您的项目或其他团队开发人员开发的开发人员来说,这是非常明确的,但我同意,可能会出现“好”和“坏” “架构。

你说: collect the most interesting approaches from experienced iOS developers ,我不认为我的方法是最有趣或最正确的,但是我已经在几个项目中使用了它,并且满意。 这是上面提到的混合方法,也是我自己研究工作的改进。 我对构build方法的问题感兴趣,这些方法结合了几个众所周知的模式和习语。 我认为很多Fowler的企业模式可以成功应用到移动应用程序中。 下面是一个最有趣的列表,我们可以申请创build一个iOS应用程序体系结构( 在我看来 ): 服务层 , 工作单元 , 远程门面 , 数据传输对象 , 网关 , 层超types , 特例 , 域模型 。 您应该始终正确地devise模型图层,并始终不要忘记持久性(它可以显着提高您的应用程序的性能)。 您可以使用Core Data 。 但是你不应该忘记, Core Data不是一个ORM或数据库,而是一个具有持久性的对象graphicspipe理器作为它的一个很好的select。 因此, Core Data通常可能太重,无法满足您的需求,您可以查看Realm和Couchbase Lite等新解决scheme,或者基于原始SQLite或LevelDB构build自己的轻量级对象映射/持久层。 另外我build议你熟悉域驱动devise和CQRS 。

起初,我认为,我们应该build立另一个networking层面,因为我们不需要胖控制器或沉重的,不堪重负的模型。 我不相信那些fat model, skinny controller东西。 但是我确实相信 skinny everything方法skinny everything ,因为没有class级应该变胖。 所有的networking一般都可以抽象为业务逻辑,因此我们应该有另一个层面,我们可以放在这里。 服务层是我们需要的:

 It encapsulates the application's business logic, controlling transactions and coordinating responses in the implementation of its operations. 

在我们的MVC领域中, Service Layer就像域模型和控制器之间的中介。 这种叫做MVCS的方法有一个相当类似的变化,其中Store实际上是我们的Service层。 Store vends模型实例,并处理networking,caching等。我想提到的是,你不应该写所有的networking和业务逻辑在你的服务层。 这也可以被认为是一个糟糕的devise。 有关更多信息,请参阅贫血和富有领域的模型。 一些服务方法和业务逻辑可以在模型中处理,所以它将是一个“丰富”(有行为)的模型。

我总是广泛使用两个库: AFNetworking 2.0和ReactiveCocoa 。 我认为这是任何与networking和networking服务交互或者包含复杂的UI逻辑的现代应用程序必须具备的

build筑

首先我创build一个通用的APIClient类,它是AFHTTPSessionManager的子类。 这是应用程序中所有networking的主力:所有服务类都将实际的REST请求委托给它。 它包含了我在特定应用程序中需要的所有HTTP客户端定制:SSL固定,error handling和创build简单的NSError对象,具有详细的失败原因以及所有API和连接错误的描述(在这种情况下,控制器将能够显示正确消息为用户),设置请求和响应序列化器,http头和其他networking相关的东西。 然后,我将所有API请求逻辑地划分为子服务,或者更准确地说是微服务 : UserSerivcesUserSerivcesSecurityServicesUserSerivces等等,从而实现它们实现的业务逻辑。 这些微服务中的每一个都是独立的类。 他们一起形成一个Service Layer 。 这些类包含每个API请求的方法,处理域模型,并始终向parsing的响应模型或NSError返回一个RACSignal给调用者。

我想提到的是,如果你有复杂的模型序列化逻辑 – 然后为它创build另一个层:像数据映射器,但更一般的如JSON / XML – >模型映射器。 如果你有caching:然后创build它作为一个单独的层/服务(你不应该混合业务逻辑与caching)。 为什么? 因为正确的caching层可能相当复杂,有自己的陷阱。 人们执行复杂的逻辑来获得有效的,可预测的caching,例如monoidalcaching和基于工作stream程的预测。 您可以阅读关于这个名为卡洛斯美丽的图书馆了解更多。 不要忘记,核心数据真的可以帮助你解决所有的caching问题,并且可以让你编写更less的逻辑。 此外,如果您在NSManagedObjectContext和服务器请求模型之间有一些逻辑, NSManagedObjectContext可以使用Repository模式,它将检索数据的逻辑与作用于该模型的业务逻辑之间的映射关系映射到实体模型。 所以,即使你有一个基于核心数据的架构,我也build议使用Repository模式。 存储库可以抽象的东西,如NSFetchRequestNSEntityDescriptionNSPredicate普通的方法,如getput

在Service层的所有这些操作之后,调用者(视图控制器)可以在ReactiveCocoa原语的帮助下,通过响应:信号操作,链接,映射等来完成一些复杂的asynchronous事件,或者只是订阅它并在视图。 我在所有这些服务类中注入了dependency injection ,我的APIClient会将特定的服务调用转换为相应的GETPOSTPUTDELETE等请求到REST端点。 在这种情况下, APIClient隐式传递给所有的控制器,您可以通过APIClient服务类的参数化来明确这一点。 如果你想对特定的服务类使用APIClient不同定制,这是APIClient的,但是如果你由于某些原因不想额外拷贝,或者你确定总是使用一个特定的实例(没有定制) APIClient – 使它成为一个单身人士,但不要,请不要把服务类作为单身人士。

然后每个视图控制器再次使用DI注入所需的服务类,调用适当的服务方法并使用UI逻辑编写结果。 对于dependency injection,我喜欢使用BloodMagic或更强大的框架Typhoon 。 我从来没有使用单身,上帝APIManagerWhatever类或其他错误的东西。 因为如果你调用你的类WhateverManager ,这表明你不知道它的目的,这是一个糟糕的deviseselect 。 单身也是反模式,在大多数情况下(除了罕见的)是一个错误的解决scheme。 只有满足以下三条标准,才能考虑单身人士:

  1. 单一实例的所有权不能合理分配;
  2. 懒惰初始化是可取的;
  3. 全球访问不另外规定。

在我们的例子中,单实例的所有权不是问题,在我们将神pipe理器分成服务之后,我们也不需要全局访问,因为现在只有一个或几个专用控制器需要特定的服务(例如UserProfile控制器需要UserServices等上)。

我们应该始终尊重SOLID中的 S原则并使用关注点分离 ,所以不要把所有的服务方法和networking调用放在一个类中,因为这太疯狂了,特别是当你开发一个大型企业应用程序的时候。 这就是为什么我们应该考虑dependency injection和服务方法。 我认为这种方法是现代和后OO 。 在这种情况下,我们将应用程序分为两部分:控制逻辑(控制器和事件)和参数。

一种参数是普通的“数据”参数。 这就是我们通过函数传递,操纵,修改,坚持等等。这些是实体,集合,集合,案例类。 另一种是“服务”参数。 这些是封装业务逻辑的类,允许与外部系统通信,提供数据访问。

以下是我的架构的一个通用工作stream程。 假设我们有一个FriendsViewController ,它显示用户的朋友列表,我们可以select从朋友中删除。 我在我的FriendsServices类中创build了一个名为:

 - (RACSignal *)removeFriend:(Friend * const)friend 

Friend是一个模型/域对象(或者如果它们具有相似的属性,则它可以只是一个User对象)。 Undercover这个方法将Friendparsing为JSON参数friend_idnamesurnamefriend_request_id等的NSDictionary 。 我总是对这种样板文件和我的模型层使用Mantle库(前后parsing,pipe理JSON中的嵌套对象层次结构等)。 parsing之后,它调用APIClient DELETE方法来创build一个实际的REST请求,并将RACSignal中的Response返回给调用者(在我们的例子中为FriendsViewController ),以便为用户显示适当的消息。

如果我们的应用程序是一个非常大的应用程序,我们必须更清楚地分离我们的逻辑。 例如,将Repository或模型逻辑与Service一起混合并不总是好事。 当我描述我的方法时,我曾经说过removeFriend方法应该在Service层,但是如果我们会更迂腐,我们可以注意到它更好地属于Repository 。 让我们记住仓库是什么。 埃里克·埃文斯(Eric Evans)在他的书[DDD]中给了它一个精确的描述:

一个Repository将一个特定types的所有对象表示为一个概念集合。 它的行为就像一个集合,除了更精细的查询能力。

所以, Repository本质上是一个使用Collection风格语义(Add,Update,Remove)来提供对数据/对象访问的外观。 这就是为什么当你有这样的东西: getFriendsListgetUserGroupsremoveFriend你可以把它放在Repository ,因为类似集合的语义在这里很清楚。 和代码一样:

 - (RACSignal *)approveFriendRequest:(FriendRequest * const)request; 

绝对是一个商业逻辑,因为它超越了基本的CRUD操作,并且连接了两个域对象( FriendRequest ),所以它应该被放置在Service层。 另外我想注意: 不要造成不必要的抽象 。 明智地使用所有这些方法。 因为如果你用抽象来压倒你的应用程序,这会增加它的意外复杂性,而且复杂性在软件系统上引起更多的问题

我给你描述一个“旧”的Objective-C例子,但是这种方法可以很容易地适应Swift语言,并且有很多改进,因为它有更多有用的function和function糖。 我强烈推荐使用这个库: Moya 。 它允许你创build一个更优雅的APIClient层(我们的主力,你记得)。 现在,我们的APIClient提供程序将是一个值types(枚举),其扩展符合协议并利用解构模式匹配。 Swift枚举+模式匹配允许我们像传统的函数式编程那样创build代数数据types 。 我们的微服务将像通常的Objective-C方法一样使用改进的APIClient提供程序。 对于模型层而不是Mantle您可以使用ObjectMapper库,或者我喜欢使用更优雅和function更强大的Argo库。

所以,我描述了我的通用架构方法,我认为可以适用于任何应用程序。 当然可以有更多的改进。 我build议你学习函数式编程,因为你可以从中受益很多,但是也不要太过分。 消除过度的,共享的,全局的可变状态,创build一个不变的域模型或者创build没有外部副作用的纯函数通常是一个很好的实践,新的Swift语言鼓励了这一点。 但是请记住,重载纯代码的function模式,类别理论的方法是一个主意,因为其他开发人员将阅读和支持您的代码,他们可能会感到沮丧或可怕的prismatic profunctors和类似的东西你不可改变的模型。 ReactiveCocoa :不要太多地 RACify你的代码进行RACify ,因为它可能非常快,尤其对于新手而言,会变得不可读。 当它可以真正简化您的目标和逻辑时使用它。

所以, read a lot, mix, experiment, and try to pick up the best from different architectural approaches 。 这是我可以给你的最好的build议。

根据这个问题的目标,我想描述我们的架构方法。

架构方法

我们一般的iOS应用程序的架构是以下模式: 服务层 , MVVM , UI数据绑定 , dependency injection ; 和function反应编程范例。

我们可以将典型的面向消费者的应用程序划分为以下逻辑层

  • 部件
  • 模型
  • 服务
  • 存储
  • 经理
  • 协调员
  • UI
  • 基础设施

程序集层是我们应用程序的引导点。 它包含一个dependency injection容器和应用程序对象及其依赖关系的声明。 这层也可能包含应用程序的configuration(url,第三方服务密钥等)。 为此我们使用台风图书馆。

模型层包含领域模型类,validation,映射。 我们使用Mantle库来映射我们的模型:它支持将序列化/反序列化成JSON格式和NSManagedObject模型。 对于我们模型的validation和表示forms,我们使用FXForms和FXModelValidation库。

服务层声明我们用于与外部系统进行交互的服务,以发送或接收在我们的域模型中表示的数据。 所以通常我们有与服务器API(每个实体),消息服务(比如PubNub ),存储服务(比如Amazon S3)等进行通信的服务。基本上,服务包装由SDK提供的对象(例如PubNub SDK)或者实现它们自己的通信逻辑。 对于一般networking我们使用AFNetworking库。

存储层的目的是在设备上组织本地数据存储。 我们使用核心数据或领域 (这两者都有利弊,决定使用什么是基于具体的规格)。 对于核心数据设置,我们使用MDMCoreData库和一堆类 – 存储 – (类似于服务),为每个实体提供对本地存储的访问。 对于Realm,我们只是使用类似的存储来访问本地存储。

经理层是我们的抽象/包装生活的地方。

在一个经理angular色可能是:

  • 凭证pipe理器及其不同的实现(钥匙串,NSDefaults …)
  • 当前会话pipe理器知道如何保持和提供当前用户会话
  • 捕获pipe道,提供访问媒体设备(video录制,audio,拍照)
  • BLEpipe理器,提供对蓝牙服务和外围设备的访问
  • 地理位置经理

所以,pipe理者angular色可以是实现应用程序工作所需的特定方面或关注逻辑的任何对象。

我们尽量避免单身人士,但是如果需要的话,这一层是他们居住的地方。

协调器层提供依赖于来自其他层(服务,存储,模型)的对象的对象,以便将它们的逻辑组合到某个模块(特征,屏幕,用户故事或用户体验)所需的一系列工作中。 它通常链接asynchronous操作,并知道如何应对成功和失败的情况。 作为一个例子,您可以想象一个消息传递function和相应的MessagingCoordinator对象。 处理发送消息操作可能如下所示:

  1. validation消息(模型图层)
  2. 在本地保存消息(消息存储)
  3. 上传消息附件(亚马逊S3服务)
  4. 更新消息状态和附件在本地保存消息(消息存储)
  5. 将消息序列化为JSON格式(模型层)
  6. 将发布消息发布到PubNub(PubNub服务)
  7. 更新消息状态和属性并将其保存在本地(消息存储)

在上述每个步骤中,相应地处理错误。

UI层由以下子组成:

  1. 的ViewModels
  2. ViewControllers
  3. 查看

为了避免大量视图控制器,我们使用MVVM模式,并在ViewModels中实现UI呈现所需的逻辑。 ViewModel通常将协调员和pipe理者视为依赖关系。 ViewControllers使用ViewModel和一些视图(例如表格视图单元格)。 ViewControllers和ViewModels之间的粘合是数据绑定和命令模式。 为了使这个胶水成为可能,我们使用ReactiveCocoa库。

我们还使用ReactiveCocoa及其RACSignal概念作为接口,并返回所有协调器,服务和存储方法的值types。 这可以让我们链接操作,并行或串行运行,以及ReactiveCocoa提供的许多其他有用的东西。

我们尝试以声明的方式实现我们的UI行为。 数据绑定和自动布局有助于实现这一目标。

基础设施层包含应用程序工作所需的所有助手,扩展程序和实用程序。


这种方法适用于我们和我们通常构build的那些types的应用程序。 但是你应该明白,这只是一个主观的方法, 应该为了具体的团队的目的而改变/改变。

希望对你有帮助!

你也可以在这个博客文章的iOS开发即服务中find更多关于iOS开发过程的信息

因为所有的iOS应用程序都是不同的,所以我认为在这里有不同的方法可以考虑,但我通常这样做:
创build一个中央pipe理器(单例)类来处理所有API请求(通常名为APICommunicator),每个实例方法都是一个API调用。 有一个中央(非公开)的方法:

(RACSignal *)sendGetToServerToSubPath:(NSString *)path withParameters:(NSDictionary *)params;

为了logging,我使用了两个主要的库/框架,ReactiveCocoa和AFNetworking。 ReactiveCocoa完美地处理asynchronousnetworking响应,你可以做(​​sendNext:,sendError:等)。
此方法调用API,获取结果并通过RAC以“原始”格式发送(如NSArray AFNetworking返回的那样)。
然后像getStuffList:这样调用上述方法的方法订阅它的信号,将原始数据parsing成对象(类似于Motis),并将对象一个接一个地发送给调用者( getStuffList:类似的方法也返回一个信号控制器可以订阅)。
订阅的控制器通过subscribeNext:的块接收对象并处理它们。

我在不同的应用程序中尝试了很多方法,但是这个方法是最好的,所以我最近在一些应用程序中使用了它,它既适用于大型项目,也适合于需要修改的情况。
希望这有帮助,我想听到别人对我的方法的意见,也许别人认为这可能会改善。

在我的情况下,我通常使用ResKit库来设置networking层。 它提供了易于使用的parsing。 这减less了我设置不同反应和东西的映射的努力。

我只添加一些代码来自动设置映射。 我为我的模型定义了基类(不是协议,因为有很多代码来检查是否实现了某种方法,在模型本身中代码less):

MappableEntry.h

 @interface MappableEntity : NSObject + (NSArray*)pathPatterns; + (NSArray*)keyPathes; + (NSArray*)fieldsArrayForMapping; + (NSDictionary*)fieldsDictionaryForMapping; + (NSArray*)relationships; @end 

MappableEntry.m

 @implementation MappableEntity +(NSArray*)pathPatterns { return @[]; } +(NSArray*)keyPathes { return nil; } +(NSArray*)fieldsArrayForMapping { return @[]; } +(NSDictionary*)fieldsDictionaryForMapping { return @{}; } +(NSArray*)relationships { return @[]; } @end 

关系是表示响应中的嵌套对象的对象:

RelationshipObject.h

 @interface RelationshipObject : NSObject @property (nonatomic,copy) NSString* source; @property (nonatomic,copy) NSString* destination; @property (nonatomic) Class mappingClass; +(RelationshipObject*)relationshipWithKey:(NSString*)key andMappingClass:(Class)mappingClass; +(RelationshipObject*)relationshipWithSource:(NSString*)source destination:(NSString*)destination andMappingClass:(Class)mappingClass; @end 

RelationshipObject.m

 @implementation RelationshipObject +(RelationshipObject*)relationshipWithKey:(NSString*)key andMappingClass:(Class)mappingClass { RelationshipObject* object = [[RelationshipObject alloc] init]; object.source = key; object.destination = key; object.mappingClass = mappingClass; return object; } +(RelationshipObject*)relationshipWithSource:(NSString*)source destination:(NSString*)destination andMappingClass:(Class)mappingClass { RelationshipObject* object = [[RelationshipObject alloc] init]; object.source = source; object.destination = destination; object.mappingClass = mappingClass; return object; } @end 

然后,我正在像这样设置RestKit的映射:

ObjectMappingInitializer.h

 @interface ObjectMappingInitializer : NSObject +(void)initializeRKObjectManagerMapping:(RKObjectManager*)objectManager; @end 

ObjectMappingInitializer.m

 @interface ObjectMappingInitializer (Private) + (NSArray*)mappableClasses; @end @implementation ObjectMappingInitializer +(void)initializeRKObjectManagerMapping:(RKObjectManager*)objectManager { NSMutableDictionary *mappingObjects = [NSMutableDictionary dictionary]; // Creating mappings for classes for (Class mappableClass in [self mappableClasses]) { RKObjectMapping *newMapping = [RKObjectMapping mappingForClass:mappableClass]; [newMapping addAttributeMappingsFromArray:[mappableClass fieldsArrayForMapping]]; [newMapping addAttributeMappingsFromDictionary:[mappableClass fieldsDictionaryForMapping]]; [mappingObjects setObject:newMapping forKey:[mappableClass description]]; } // Creating relations for mappings for (Class mappableClass in [self mappableClasses]) { RKObjectMapping *mapping = [mappingObjects objectForKey:[mappableClass description]]; for (RelationshipObject *relation in [mappableClass relationships]) { [mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:relation.source toKeyPath:relation.destination withMapping:[mappingObjects objectForKey:[relation.mappingClass description]]]]; } } // Creating response descriptors with mappings for (Class mappableClass in [self mappableClasses]) { for (NSString* pathPattern in [mappableClass pathPatterns]) { if ([mappableClass keyPathes]) { for (NSString* keyPath in [mappableClass keyPathes]) { [objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:[mappingObjects objectForKey:[mappableClass description]] method:RKRequestMethodAny pathPattern:pathPattern keyPath:keyPath statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]]; } } else { [objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:[mappingObjects objectForKey:[mappableClass description]] method:RKRequestMethodAny pathPattern:pathPattern keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]]; } } } // Error Mapping RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[Error class]]; [errorMapping addAttributeMappingsFromArray:[Error fieldsArrayForMapping]]; for (NSString *pathPattern in Error.pathPatterns) { [[RKObjectManager sharedManager] addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:errorMapping method:RKRequestMethodAny pathPattern:pathPattern keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)]]; } } @end @implementation ObjectMappingInitializer (Private) + (NSArray*)mappableClasses { return @[ [FruiosPaginationResults class], [FruioItem class], [Pagination class], [ContactInfo class], [Credentials class], [User class] ]; } @end 

MappableEntry实现的一些例子:

User.h

 @interface User : MappableEntity @property (nonatomic) long userId; @property (nonatomic, copy) NSString *username; @property (nonatomic, copy) NSString *email; @property (nonatomic, copy) NSString *password; @property (nonatomic, copy) NSString *token; - (instancetype)initWithUsername:(NSString*)username email:(NSString*)email password:(NSString*)password; - (NSDictionary*)registrationData; @end 

User.m

 @implementation User - (instancetype)initWithUsername:(NSString*)username email:(NSString*)email password:(NSString*)password { if (self = [super init]) { self.username = username; self.email = email; self.password = password; } return self; } - (NSDictionary*)registrationData { return @{ @"username": self.username, @"email": self.email, @"password": self.password }; } + (NSArray*)pathPatterns { return @[ [NSString stringWithFormat:@"/api/%@/users/register", APIVersionString], [NSString stringWithFormat:@"/api/%@/users/login", APIVersionString] ]; } + (NSArray*)fieldsArrayForMapping { return @[ @"username", @"email", @"password", @"token" ]; } + (NSDictionary*)fieldsDictionaryForMapping { return @{ @"id": @"userId" }; } @end 

Now about the Requests wrapping:

I have header file with blocks definition, to reduce line length in all APIRequest classes:

APICallbacks.h

 typedef void(^SuccessCallback)(); typedef void(^SuccessCallbackWithObjects)(NSArray *objects); typedef void(^ErrorCallback)(NSError *error); typedef void(^ProgressBlock)(float progress); 

And Example of my APIRequest class that I'm using:

LoginAPI.h

 @interface LoginAPI : NSObject - (void)loginWithCredentials:(Credentials*)credentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError; @end 

LoginAPI.m

 @implementation LoginAPI - (void)loginWithCredentials:(Credentials*)credentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError { [[RKObjectManager sharedManager] postObject:nil path:[NSString stringWithFormat:@"/api/%@/users/login", APIVersionString] parameters:[credentials credentialsData] success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) { onSuccess(mappingResult.array); } failure:^(RKObjectRequestOperation *operation, NSError *error) { onError(error); }]; } @end 

And all you need to do in code, simply initialize API object and call it whenever you need it:

SomeViewController.m

 @implementation SomeViewController { LoginAPI *_loginAPI; // ... } - (void)viewDidLoad { [super viewDidLoad]; _loginAPI = [[LoginAPI alloc] init]; // ... } // ... - (IBAction)signIn:(id)sender { [_loginAPI loginWithCredentials:_credentials onSuccess:^(NSArray *objects) { // Success Block } onError:^(NSError *error) { // Error Block }]; } // ... @end 

My code isn't perfect, but it's easy to set once and use for different projects. If it's interesting to anyone, mb I could spend some time and make a universal solution for it somewhere on GitHub and CocoaPods.

To my mind all software architecture is driven by need. If this is for learning or personal purposes, then decide the primary goal and have that drive the architecture. If this is a work for hire, then the business need is paramount. The trick is to not let shiny things distract you from the real needs. I find this hard to do. There are always new shiny things appearing in this business and lots of them are not useful, but you can't always tell that up front. Focus on the need and be willing to abandon bad choices if you can.

For example, I recently did a quick prototype of a photo sharing app for a local business. Since the business need was to do something quick and dirty, the architecture ended up being some iOS code to pop up a camera and some network code attached to a Send Button that uploaded the image to a S3 store and wrote to a SimpleDB domain. The code was trivial and the cost minimal and the client has an scalable photo collection accessible over the web with REST calls. Cheap and dumb, the app had lots of flaws and would lock the UI on occasion, but it would be a waste to do more for a prototype and it allows them to deploy to their staff and generate thousands of test images easily without performance or scalability concerns. Crappy architecture, but it fit the need and cost perfectly.

Another project involved implementing a local secure database which synchronizes with the company system in the background when the network is available. I created a background synchronizer that used RestKit as it seemed to have everything I needed. But I had to write so much custom code for RestKit to deal with idiosyncratic JSON that I could have done it all quicker by writing my own JSON to CoreData transformations. However, the customer wanted to bring this app in house and I felt that RestKit would be similar to the frameworks that they used on other platforms. I waiting to see if that was a good decision.

Again, the issue to me is to focus on the need and let that determine the architecture. I try like hell to avoid using third party packages as they bring costs that only appears after the app has been in the field for a while. I try to avoid making class hierarchies as they rarely pay off. If I can write something in a reasonable period of time instead of adopting a package that doesn't fit perfectly, then I do it. My code is well structured for debugging and appropriately commented, but third party packages rarely are. With that said, I find AF Networking too useful to ignore and well structured, well commented, and maintained and I use it a lot! RestKit covers a lot of common cases, but I feel like I've been in a fight when I use it, and most of the data sources I encounter are full of quirks and issues that are best handled with custom code. In my last few apps I just use the built in JSON converters and write a few utility methods.

One pattern I always use is to get the network calls off the main thread. The last 4-5 apps I've done set up a background timer task using dispatch_source_create that wakes up every so often and does network tasks as needed. You need to do some thread safety work and make sure that UI modifying code gets sent to the main thread. It also helps to do your onboarding/initialization in such a way that the user doesn't feel burdened or delayed. So far this has been working rather well. I suggest looking into these things.

Finally, I think that as we work more and as the OS evolves, we tend to develop better solutions. It has taken me years to get over my belief that I have to follow patterns and designs that other people claim are mandatory. If I am working in a context where that is part of the local religion, ahem, I mean the departmental best engineering practices, then I follow the customs to the letter, that's what they are paying me for. But I rarely find that following older designs and patterns is the optimal solution. I always try to look at the solution through the prism of the business needs and build the architecture to match it and keep things as simple as they can be. When I feel like there isn't enough there, but everything works correctly, then I'm on the right track.

I use the approach that I've gotten from here: https://github.com/Constantine-Fry/Foursquare-API-v2 . I've rewritten that library in Swift and you can see the architectural approach from these parts of the code:

 typealias OpertaionCallback = (success: Bool, result: AnyObject?) -> () class Foursquare{ var authorizationCallback: OperationCallback? var operationQueue: NSOperationQueue var callbackQueue: dispatch_queue_t? init(){ operationQueue = NSOperationQueue() operationQueue.maxConcurrentOperationCount = 7; callbackQueue = dispatch_get_main_queue(); } func checkIn(venueID: String, shout: String, callback: OperationCallback) -> NSOperation { let parameters: Dictionary <String, String> = [ "venueId":venueID, "shout":shout, "broadcast":"public"] return self.sendRequest("checkins/add", parameters: parameters, httpMethod: "POST", callback: callback) } func sendRequest(path: String, parameters: Dictionary <String, String>, httpMethod: String, callback:OperationCallback) -> NSOperation{ let url = self.constructURL(path, parameters: parameters) var request = NSMutableURLRequest(URL: url) request.HTTPMethod = httpMethod let operation = Operation(request: request, callbackBlock: callback, callbackQueue: self.callbackQueue!) self.operationQueue.addOperation(operation) return operation } func constructURL(path: String, parameters: Dictionary <String, String>) -> NSURL { var parametersString = kFSBaseURL+path var firstItem = true for key in parameters.keys { let string = parameters[key] let mark = (firstItem ? "?" : "&") parametersString += "\(mark)\(key)=\(string)" firstItem = false } return NSURL(string: parametersString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)) } } class Operation: NSOperation { var callbackBlock: OpertaionCallback var request: NSURLRequest var callbackQueue: dispatch_queue_t init(request: NSURLRequest, callbackBlock: OpertaionCallback, callbackQueue: dispatch_queue_t) { self.request = request self.callbackBlock = callbackBlock self.callbackQueue = callbackQueue } override func main() { var error: NSError? var result: AnyObject? var response: NSURLResponse? var recievedData: NSData? = NSURLConnection.sendSynchronousRequest(self.request, returningResponse: &response, error: &error) if self.cancelled {return} if recievedData{ result = NSJSONSerialization.JSONObjectWithData(recievedData, options: nil, error: &error) if result != nil { if result!.isKindOfClass(NSClassFromString("NSError")){ error = result as? NSError } } if self.cancelled {return} dispatch_async(self.callbackQueue, { if (error) { self.callbackBlock(success: false, result: error!); } else { self.callbackBlock(success: true, result: result!); } }) } override var concurrent:Bool {get {return true}} } 

Basically, there is NSOperation subclass that makes the NSURLRequest, parses JSON response and adds the callback block with the result to the queue. The main API class constructs NSURLRequest, initialises that NSOperation subclass and adds it to the queue.

We use a few approaches depending on the situation. For most things AFNetworking is the simplest and most robust approach in that you can set headers, upload multipart data, use GET, POST, PUT & DELETE and there are a bunch of additional categories for UIKit which allow you to for example set an image from a url. In a complex app with a lot of calls we sometimes abstract this down to a convenience method of our own which would be something like:

 -(void)makeRequestToUrl:(NSURL *)url withParameters:(NSDictionary *)parameters success:(void (^)(id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; 

There are a few situations where AFNetworking isn't appropriate however such as where you are creating a framework or other library component as AFNetworking may already be in another code base. In this situation you would use an NSMutableURLRequest either inline if you are making a single call or abstracted into a request / response class.

I avoid singletons when designing my applications. They are a typical go to for a lot of people but I think you can find more elegant solutions elsewhere. Typically what I do is a build out my entities in CoreData and then put my REST code in an NSManagedObject category. If for instance I wanted to create and POST a new User, I'd do this:

 User* newUser = [User createInManagedObjectContext:managedObjectContext]; [newUser postOnSuccess:^(...) { ... } onFailure:^(...) { ... }]; 

I use RESTKit for the object mapping and initialize it at start up. I find routing all of your calls through a singleton to be a waste of time and adds a lot of boilerplate that isn't needed.

In NSManagedObject+Extensions.m:

 + (instancetype)createInContext:(NSManagedObjectContext*)context { NSAssert(context.persistentStoreCoordinator.managedObjectModel.entitiesByName[[self entityName]] != nil, @"Entity with name %@ not found in model. Is your class name the same as your entity name?", [self entityName]); return [NSEntityDescription insertNewObjectForEntityForName:[self entityName] inManagedObjectContext:context]; } 

In NSManagedObject+Networking.m:

 - (void)getOnSuccess:(RESTSuccess)onSuccess onFailure:(RESTFailure)onFailure blockInput:(BOOL)blockInput { [[RKObjectManager sharedManager] getObject:self path:nil parameters:nil success:onSuccess failure:onFailure]; [self handleInputBlocking:blockInput]; } 

Why add extra helper classes when you can extend the functionality of a common base class through categories?

If you're interested in more detailed info on my solution let me know. I'm happy to share.

Try https://github.com/kevin0571/STNetTaskQueue

Create API requests in separated classes.

STNetTaskQueue will deal with threading and delegate/callback.

Extendable for different protocols.

From a purely class design perspective, you will usually have something like this:

  • Your view controllers controlling one or more views
  • Data model class – It really depends upon how many real distinct entities you are dealing with, and how they are related.

    For example, if you have an array of items to be displayed in four different representations (list, chart, graph etc), you will have one data model class for list of items, one more for an item. The list of item class will be shared by four view controllers – all children of a tab bar controller or a nav controller.

    Data model classes will come handy in not only displaying data, but also serializing them wherein each of them can expose their own serialization format through JSON / XML / CSV (or anything else) export methods.

  • It is important to understand that you also need API request builder classes that map directly with your REST API endpoints. Let's say you have an API that logs the user in – so your Login API builder class will create POST JSON payload for login api. In another example, an API request builder class for list of catalog items API will create GET query string for corresponding api and fire the REST GET query.

    These API request builder classes will usually receive data from view controllers and also pass the same data back to view controllers for UI update / other operations. View controllers will then decide how to update Data Model objects with that data.

  • Finally, the heart of the REST client – API data fetcher class which is oblivious to all sorts of API requests your app makes. This class will more likely be a singleton, but as others pointed out, it doesn't have to be a singleton.

    Note that the link is just a typical implementation and does not take into consideration scenarios like session, cookies etc, but it is enough to get you going without using any 3rd party frameworks.

This question has a lot of excellent and extensive answers already, but I feel I have to mention it since no one else has.

Alamofire for Swift. https://github.com/Alamofire/Alamofire

It's created by the same people as AFNetworking, but is more directly designed with Swift in mind.