构建iOS网络应用程序(REST客户端)的最佳架构方法

我是一名有一定经验的iOS开发人员,这个问题对我来说真的很有趣。关于这个话题,我看过很多不同的资源和材料,但我还是很困惑。iOS网络应用的最佳架构是什么?我指的是基本的抽象框架和模式,它们适用于每个网络应用程序,无论是只有少量服务器请求的小应用程序还是复杂的REST客户端。Apple建议使用MVC作为所有iOS应用程序的基本架构方法,但无论是MVC还是更现代的MVVM模式都没有解释在哪里放置网络逻辑代码以及如何组织它。 我是否需要开发类似MVCS(S for Service)的东西,并在这个Service层中放置所有API请求和其他网络逻辑,从角度来看可能真的很复杂?在做了一些研究之后,我发现了两种基本的方法。S4建议为每个对web服务API的网络请求创建一个单独的类(如LoginRequest类或PostCommentRequest类等),这些类都继承自基本请求抽象类AbstractBaseRequest,此外还可以创建一些全局网络管理器,封装公共网络代码和其他首选项(可能是AFNetworking自定义或S0调优,如果我们有复杂的对象映射和持久化,甚至可以使用标准API实现自己的网络通信)。但这种方法对我来说似乎是一种开销。另一种方法是使用一些单独的API分派器或管理器类,如第一种方法中的S5,为每个请求创建类,并将每个请求封装为该管理器类的实例公共方法,如:S2, S3方法等。那么,什么是最好和正确的方法呢?还有其他我不知道的有趣的方法吗? 我是否应该为所有这些网络内容创建另一层,如Service,或NetworkProvider层或任何在我的MVC架构之上的东西,或者该层应该集成(注入)到现有的MVC层,如Model? 我知道存在很好的方法,但是像Facebook客户端或LinkedIn客户端这样的移动怪物是如何处理呈指数级增长的网络逻辑的?

我知道这个问题没有确切而正式的答案。这个问题的目的是从有经验的iOS开发人员那里收集最有趣的方法。建议的最佳方法将被标记为已接受,并获得声誉奖励,其他方法将得到好评。这主要是一个理论和研究问题。我想了解iOS中网络应用的基本、抽象和正确的架构方法。我希望有经验的开发人员能给出详细的解释。

66378 次浏览
因为所有iOS应用程序都是不同的,我认为这里有不同的方法可以考虑,但我通常会这样做:
创建一个中央管理器(单例)类来处理所有API请求(通常命名为apiccommunicator),每个实例方法都是一个API调用。有一个中心(非公共)方法:

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

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

我在不同的应用程序中尝试了许多方法,但这个方法是最好的,所以我最近在一些应用程序中使用它,它适用于小型和大型项目,如果需要修改,它很容易扩展和维护。
希望这能有所帮助,我想听听其他人对我的方法的意见,也许其他人认为这可能会得到改进

我想了解iOS中网络应用的基本、抽象和正确的架构方法

没有 &;the best&;或&;the most correct&;构建应用程序体系结构的方法。这是一个非常创造性的工作。您应该始终选择最直接和可扩展的架构,这对于任何开始开发您的项目的开发人员或您团队中的其他开发人员来说都是清楚的,但我同意,可以有一个“好”的架构。而“bad”;体系结构。

你说:

从经验丰富的iOS开发者那里收集最有趣的方法

我不认为我的方法是最有趣或最正确的,但我已经在几个项目中使用了它,并对它感到满意。这是你上面提到的方法的混合,也有我自己的研究成果的改进。我对构建方法的问题很感兴趣,它结合了几个众所周知的模式和习语。我认为很多福勒的企业模式可以成功地应用到移动应用程序。下面是一个最有趣的列表,我们可以应用它来创建一个iOS应用程序架构(在我看来): 服务层工作单元远程门面数据传输对象网关Core Data0, Core Data1, Core Data2。你应该始终正确地设计模型层,并且始终不要忘记持久性(持久性可以显著提高应用程序的性能)。你可以使用Core Data。但是你Core Data3忘记了,Core Data不是一个ORM或数据库,而是一个对象图管理器,持久化是一个很好的选择。因此,对于你的需求来说,Core Data通常过于沉重,你可以寻找新的解决方案,如Core Data4和Core Data5,或者基于原始SQLite或Core Data6构建自己的轻量级对象映射/持久层。我还建议你熟悉Core Data7和Core Data8。

首先,我认为,我们为网络创建了另一层,因为我们不想要肥胖的控制器或沉重的,不堪重负的模型。我不相信那些fat model, skinny controller的东西。但我在skinny everything方法中相信,因为任何类都不应该是胖的。所有的网络通常都可以抽象为业务逻辑,因此我们应该有另一个层,我们可以把它放在那里。服务层是我们需要的:

它封装应用程序的业务逻辑,控制事务并协调其操作实现中的响应。

在我们的MVC领域中,Service Layer类似于域模型和控制器之间的中介。这种方法有一个相当类似的变体,称为mvc,其中Store实际上是我们的Service层。Store提供模型实例并处理网络,缓存等。我想提到的是,你不应该在你的服务层中编写了所有的网络和业务逻辑。这也可以被认为是一个糟糕的设计。有关更多信息,请参阅贫血丰富的域模型。一些服务方法和业务逻辑可以在模型中处理,因此它将是一个“丰富”的模型。(与行为)模型。

我经常广泛使用两个库:AFNetworking 2.0ReactiveCocoa。我认为对于任何与网络和web服务交互或包含复杂UI逻辑的现代应用程序,它都是必须有

体系结构

首先,我创建了一个通用的APIClient类,它是NSError0的子类。这是应用程序中所有网络的主力:所有服务类都将实际REST请求委托给它。它包含了HTTP客户端的所有自定义,这是我在特定的应用程序中需要的:SSL固定,错误处理和创建直接的NSError对象,详细的失败原因和所有API和连接错误的描述(在这种情况下,控制器将能够为用户显示正确的消息),设置请求和响应序列化器,HTTP报头和其他与网络相关的东西。然后我逻辑地将所有API请求划分为子服务,或者更准确地说,NSError1: UserSerivcesCommonServicesSecurityServicesFriendsServices等等,根据它们实现的业务逻辑。每个微服务都是一个单独的类。它们一起构成了Service Layer。这些类包含每个API请求的方法,处理域模型,并且总是返回一个带有解析响应模型的RACSignalNSError给调用者。

我想提到的是,如果你有复杂的模型序列化逻辑,那么为它创建另一层:类似数据映射器的东西,但更通用,例如JSON/XML ->模型映射器。如果你有缓存:那么也把它创建为一个单独的层/服务(你不应该把业务逻辑和缓存混合在一起)。为什么?因为正确的缓存层可能相当复杂,有它自己的陷阱。人们实现复杂的逻辑来获得有效的、可预测的缓存,例如基于profunctor的投影的单级缓存。你可以阅读这个名为卡洛斯的漂亮库来了解更多。不要忘记,Core Data可以真正帮助您解决所有缓存问题,并允许您编写更少的逻辑。此外,如果你在NSManagedObjectContext和服务器请求模型之间有一些逻辑,你可以使用存储库模式,它将检索数据并将其映射到实体模型的逻辑与作用于模型的业务逻辑分离开来。所以,我建议使用存储库模式,即使你有一个基于核心数据的架构。存储库可以将NSFetchRequestNSEntityDescriptionNSPredicate等抽象为getput等普通方法。

在服务层的所有这些操作之后,调用者(视图控制器)可以用响应做一些复杂的异步操作:在ReactiveCocoa原语的帮助下,信号操作、链接、映射等,或者只是订阅它并在视图中显示结果。我在所有这些服务类中注入了APIClient0,我的APIClient,它将把特定的服务调用转换为对应的GETPOSTPUTDELETE等请求到REST端点。在这种情况下,APIClient隐式传递给所有控制器,你可以通过APIClient服务类的参数化使其显式。如果你想为特定的服务类使用不同的APIClient自定义,这是有意义的,但如果你出于某些原因,不想要额外的副本,或者你确定你总是会使用APIClient的一个特定实例(没有自定义)——让它成为单例,但是不要,请不要让服务类成为单例。

然后每个视图控制器再次使用DI注入它需要的服务类,调用适当的服务方法,并将其结果与UI逻辑组合。对于依赖注入,我喜欢使用BloodMagic或更强大的框架台风。我从不使用单例对象,God APIManagerWhatever类或其他错误的东西。因为如果你调用你的类WhateverManager,这表明你不知道它的目的,它是糟糕的设计选择。单例也是一种反模式,在大多数情况下(少数例外)是一个错误的解决方案。只有满足以下三个条件时,才应该考虑单例:

  1. 单个实例的所有权不能合理分配的;
  2. 延迟初始化是可取的;
  3. 另外没有提供全局访问。

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

我们应该始终尊重S原则在固体和使用分离关注点,所以不要把你所有的服务方法和网络调用放在一个类,因为这是疯狂的,特别是如果你开发一个大型企业应用程序。这就是为什么我们应该考虑依赖注入和服务方法。我认为这种方法是现代的和post-OO。在本例中,我们将应用程序分为两部分:控制逻辑(控制器和事件)和参数。

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

下面是我的体系结构的一般工作流示例。让我们假设我们有一个FriendsViewController,它显示用户的朋友列表,并且我们有一个从朋友中删除的选项。我在FriendsServices类中创建了一个方法,称为:

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

其中Friend是一个模型/域对象(或者如果它们具有相似的属性,它可以只是一个User对象)。下面这个方法解析JSON参数friend_idnamesurnamefriend_request_id等的FriendNSDictionary。我总是使用User3库用于这种样板和我的模型层(前后解析,在JSON中管理嵌套对象层次结构等)。解析后,它调用APIClient DELETE方法来发出一个实际的REST请求,并将User1中的User0返回给调用者(在我们的例子中是User2),以为用户或其他对象显示适当的消息。

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

存储库将某种类型的所有对象表示为一个概念集。它的作用类似于集合,只是具有更详细的查询功能。

因此,Repository本质上是一个facade,它使用Collection样式语义(添加、更新、删除)来提供对数据/对象的访问。这就是为什么当你有像getFriendsListgetUserGroupsremoveFriend这样的东西时,你可以把它放在Repository中,因为类似集合的语义在这里非常清楚。代码如下:

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

绝对是一个业务逻辑,因为它超出了基本的CRUD操作,并且连接了两个域对象(FriendRequest),这就是为什么它应该被放在Service层。我还想注意:不要创造不必要的抽象。明智地使用所有这些方法。因为如果你用抽象来压倒你的应用程序,这将会导致它的意外复杂性,而在软件系统中的复杂性将会超过其他任何东西

我形容你是一个“老”;Objective-C的例子,但是这种方法可以很容易地适应Swift语言,有更多的改进,因为它有更多有用的特性和功能糖。我强烈推荐使用这个库:火山泥。它允许你创建一个更优雅的APIClient层(你记得我们的主力)。现在我们的APIClient提供者将是一个值类型(enum),其扩展符合协议并利用解构模式匹配。Swift枚举+模式匹配允许我们像在经典函数式编程中一样创建代数数据类型。我们的微服务将像通常的Objective-C方法一样使用这个改进的APIClient提供者。对于模型层,你可以使用objectmap图书馆代替Mantle,或者我喜欢使用更优雅和功能更丰富的阿尔戈库。

因此,我描述了我的一般架构方法,我认为它可以适用于任何应用程序。当然,还可以有更多的改进。我建议你学习函数式编程,因为你可以从中受益匪浅,但也不要学得太过了。消除过度的、共享的、全局的可变状态、创建不可变域模型或创建没有外部副作用的纯函数通常是一个很好的实践,新的Swift语言鼓励这样做。但是永远记住,用沉重的纯函数模式和类别理论方法重载代码是的想法,因为其他开发人员将阅读并支持你的代码,他们可能会对你的不可变模型中的prismatic profunctors和此类东西感到沮丧或恐惧。与ReactiveCocoa同样的事情:不要RACify你的代码太多,因为它会很快变得不可读,尤其是对新手来说。当它真的可以简化你的目标和逻辑时,就使用它。

因此,要大量阅读、混合、试验,并尝试从不同的架构方法中获得最好的。这是我能给你的最好的建议。

我们根据具体情况使用几种方法。对于大多数事情来说,AFNetworking是最简单和最健壮的方法,因为你可以设置报头,上传多部分数据,使用GET, POST, PUT &DELETE还有很多附加的UIKit类别它们允许你从url中设置图像。在一个有很多调用的复杂应用程序中,我们有时会将其抽象为我们自己的方便方法,就像这样:

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

有一些情况下,AFNetworking是不合适的,但是,如你正在创建一个框架或其他库组件,因为AFNetworking可能已经在另一个代码库。在这种情况下,你可以使用NSMutableURLRequest内联,如果你正在做一个单独的调用或抽象到一个请求/响应类。

在设计应用程序时,我避免使用单例。他们是很多人的典型选择,但我认为你可以在其他地方找到更优雅的解决方案。通常我做的是在CoreData中构建我的实体,然后把我的REST代码放在NSManagedObject类别中。例如,如果我想创建并POST一个新用户,我会这样做:

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

我使用RESTKit进行对象映射,并在启动时初始化它。我发现通过单例路由您的所有调用是浪费时间,并添加了许多不需要的样板文件。

在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];
}

在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];
}

当可以通过类别扩展公共基类的功能时,为什么还要添加额外的helper类呢?

如果你对我的解决方案更详细的信息感兴趣,请告诉我。我很乐意分享。

我使用从这里得到的方法:https://github.com/Constantine-Fry/Foursquare-API-v2。我已经在Swift中重写了这个库,你可以从这部分代码中看到架构方法:

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}}
}

基本上,有一个NSOperation子类,它生成NSURLRequest,解析JSON响应,并将回调块和结果添加到队列中。主API类构造NSURLRequest,初始化NSOperation子类并将其添加到队列中。

在我看来,所有的软件架构都是由需求驱动的。如果这是为了学习或个人目的,那么确定主要目标并让它驱动架构。如果这是一项雇佣工作,那么业务需求是最重要的。诀窍就是不要让光鲜的东西分散你对真正需求的注意力。我发现这很难做到。在这个行业里总是有新的闪亮的东西出现,其中很多是没有用的,但你不能一开始就知道这一点。专注于需求,如果可以的话,愿意放弃糟糕的选择。

例如,我最近为一家本地企业做了一个照片共享应用程序的快速原型。由于业务需要做一些快速而复杂的事情,因此架构最终是一些弹出摄像头的iOS代码和附加到Send Button的网络代码,Send Button将图像上传到S3存储并写入SimpleDB域。代码很简单,成本最低,客户端有一个可扩展的照片集,可以通过REST调用在web上访问。这款应用既便宜又愚蠢,它有很多缺陷,有时还会锁定UI,但为原型做更多工作是一种浪费,它允许他们部署给员工,轻松生成数千个测试图像,而无需考虑性能或可伸缩性。蹩脚的架构,但它符合需求和成本完美。

另一个项目涉及实现一个本地安全数据库,当网络可用时,该数据库可以在后台与公司系统同步。我创建了一个使用RestKit的后台同步器,因为它似乎拥有我需要的一切。但是为了处理特殊的JSON,我不得不为RestKit编写大量自定义代码,如果我自己编写JSON到CoreData转换,就可以更快地完成这些工作。然而,客户想要把这个应用带入内部,我觉得RestKit应该和他们在其他平台上使用的框架类似。我在等着看这个决定是否正确。

同样,对我来说,问题在于关注需求,并让需求决定架构。我尽量避免使用第三方软件包,因为它们带来的成本只会在应用程序运行一段时间后才显现出来。我尽量避免建立阶级等级,因为他们很少有回报。如果我能在合理的时间内写出一些东西,而不是采用一个不完全合适的软件包,那么我就会这样做。我的代码结构良好,便于调试和适当注释,但第三方包很少这样。话虽如此,我发现AF Networking太有用了,不容忽视,结构良好,评论良好,维护良好,我经常使用它!RestKit涵盖了很多常见的情况,但我觉得在使用它时就像在战斗一样,我遇到的大多数数据源都充满了怪僻和问题,最好用自定义代码来处理。在我的最后几个应用程序中,我只是使用了内置的JSON转换器,并编写了一些实用程序方法。

我经常使用的一种模式是让网络调用脱离主线程。在过去的4-5个应用程序中,我使用dispatch_source_create设置了一个后台定时器任务,它会经常唤醒,并根据需要执行网络任务。您需要做一些线程安全工作,并确保UI修改代码被发送到主线程。它还有助于以用户不会感到负担或延迟的方式进行入职/初始化。到目前为止,这种方法运行得相当不错。我建议调查一下这些事情。

最后,我认为随着我们工作的增加和操作系统的发展,我们倾向于开发更好的解决方案。我花了好几年时间才克服了我必须遵循别人认为是强制性的模式和设计的想法。如果我工作的环境是当地宗教的一部分,嗯哼,我的意思是部门的最佳工程实践,那么我要严格遵守习俗,这就是他们付钱给我的原因。但我很少发现遵循旧的设计和模式是最佳解决方案。我总是试图通过业务需求的棱镜来看待解决方案,并构建与之匹配的体系结构,并使事情尽可能简单。当我觉得不够的时候,但一切都很正常,那么我就在正确的轨道上。

尝试https://github.com/kevin0571/STNetTaskQueue

在分开的类中创建API请求。

STNetTaskQueue将处理线程和委托/回调。

可针对不同协议进行扩展。

在我的情况下,我通常使用ResKit库来设置网络层。它提供了易于使用的解析。它减少了我为不同的响应设置映射的工作量。

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

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

现在关于请求包装:

我有头文件与块定义,以减少行长度在所有APIRequest类:

APICallbacks.h

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

我的APIRequest类的例子,我正在使用:

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

在代码中,你只需要初始化API对象,并在需要时调用它:

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

我的代码并不完美,但很容易设置一次,并用于不同的项目。如果任何人都感兴趣,我可以花一些时间,在GitHub和CocoaPods上为它做一个通用的解决方案。

从纯类设计的角度来看,你通常会有这样的东西:

  • 你的视图控制器控制一个或多个视图
  • 数据模型类 -这实际上取决于你要处理多少个真正不同的实体,以及它们是如何相互关联的。

    例如,如果您有一个项目数组,要以四种不同的表示形式(列表、图表、图形等)显示,您将为项目列表提供一个数据模型类,为项目提供一个数据模型类。项目类别列表将由四个视图控制器共享——它们都是标签栏控制器或导航控制器的子控制器。

    数据模型类不仅可以显示数据,还可以序列化数据,其中每个类都可以通过JSON / XML / CSV(或任何其他)导出方法公开自己的序列化格式

  • 重要的是要理解,你还需要API请求构建器类直接映射到你的REST API端点。假设你有一个用户登录的API——那么你的登录API构建器类将为登录API创建POST JSON有效负载。在另一个示例中,用于目录项API列表的API请求构建器类将为相应的API创建GET查询字符串并触发REST GET查询。

    这些API请求构建器类通常会从视图控制器接收数据,并将相同的数据传递回视图控制器用于UI更新/其他操作。然后视图控制器将决定如何使用该数据更新数据模型对象

  • 最后,REST客户端的核心 - API数据获取类,它对应用程序发出的各种API请求无关。这个类更有可能是单例的,但正如其他人指出的,它不一定是单例的。

    注意,这个链接只是一个典型的实现,并没有考虑到会话、cookie等场景,但它足以让你在不使用任何第三方框架的情况下继续运行

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

体系结构方法

我们一般的iOS应用程序的架构基于以下模式:服务层MVVMUI数据绑定依赖注入;和函数式响应式编程范例。

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

  • 组装
  • 模型
  • 服务
  • 存储
  • 经理
  • 协调员
  • 用户界面
  • 基础设施

组装层是应用程序的引导点。它包含一个依赖注入容器和应用程序对象及其依赖项的声明。这一层还可能包含应用程序的配置(url,第三方服务密钥等)。为此,我们使用台风库。

模型层包含域模型类、验证和映射。我们使用地幔库来映射我们的模型:它支持序列化/反序列化为JSON格式和NSManagedObject模型。对于模型的验证和表单表示,我们使用FXFormsFXModelValidation库。

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

存储层的目的是组织设备上的本地数据存储。为此,我们使用Core Data或领域(两者都有优缺点,使用哪个的决定基于具体的规范)。对于Core Data设置,我们使用MDMCoreData库和一堆类-存储-(类似于服务),它们为每个实体提供对本地存储的访问。对于Realm,我们只是使用类似的存储来访问本地存储。

经理层是我们的抽象/包装器所在的地方。

经理角色可以是:

  • 凭据管理器及其不同的实现(keychain, NSDefaults,…)
  • 当前会话管理器,知道如何保持和提供当前用户会话
  • 捕获管道,提供对媒体设备的访问(视频录制,音频,拍照)
  • BLE管理器,提供对蓝牙服务和外围设备的访问
  • 地理位置管理器
  • ...

因此,管理器的角色可以是任何实现应用程序工作所需的特定方面或关注点的逻辑的对象。

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

协调员层提供的对象依赖于来自其他层(服务、存储、模型)的对象,以便将它们的逻辑组合成特定模块(功能、屏幕、用户故事或用户体验)所需的一个工作序列。它通常链接异步操作,并知道如何对它们的成功和失败情况作出反应。作为一个例子,你可以想象一个消息传递特性和对应的MessagingCoordinator对象。处理发送消息操作可能是这样的:

  1. 验证消息(模型层)
  2. 本地保存消息(消息存储)
  3. 上传消息附件(amazon s3服务)
  4. 更新消息状态和附件url并在本地保存消息(消息存储)
  5. 将消息序列化为JSON格式(模型层)
  6. 向PubNub发布消息(PubNub服务)
  7. 更新消息状态和属性并将其保存在本地(消息存储)

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

用户界面层由以下子层组成:

  1. 视图模型
  2. viewcontroller
  3. 的观点

为了避免大量的视图控制器,我们使用MVVM模式,并在视图模型中实现UI表示所需的逻辑。ViewModel通常有协调器和管理器作为依赖项。viewcontroller和某些类型的视图(例如表视图单元格)使用的viewmodel。视图控制器和视图模型之间的粘合剂是数据绑定和命令模式。为了使它能够有这个胶水,我们使用ReactiveCocoa库。

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

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

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


这种方法对我们和我们通常构建的那些类型的应用都很有效。但你应该明白,这只是一个主观的方法,应该可以适应/改变具体的团队的目的。

希望这对你有所帮助!

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

这个问题已经有很多优秀而广泛的答案,但我觉得我必须提一下,因为没有人提过。

斯威夫特的Alamofire。https://github.com/Alamofire/Alamofire

它是由与AFNetworking相同的人创建的,但在设计时更直接地考虑了Swift。

我认为目前中型项目使用MVVM架构,大项目使用VIPER架构 并尝试实现

  • 面向协议编程
  • 软件设计模式
  • S.O.L.D原则
  • 泛型编程
  • 不要重复自己(DRY)

和构建iOS网络应用程序(REST客户端)的架构方法 .

对于代码干净易读的分离问题,避免重复:

import Foundation
enum DataResponseError: Error {
case network
case decoding


var reason: String {
switch self {
case .network:
return "An error occurred while fetching data"
case .decoding:
return "An error occurred while decoding data"
}
}
}


extension HTTPURLResponse {
var hasSuccessStatusCode: Bool {
return 200...299 ~= statusCode
}
}


enum Result<T, U: Error> {
case success(T)
case failure(U)
}

依存关系反演

 protocol NHDataProvider {
func fetchRemote<Model: Codable>(_ val: Model.Type, url: URL, completion: @escaping (Result<Codable, DataResponseError>) -> Void)
}

主要负责:

  final class NHClientHTTPNetworking : NHDataProvider {


let session: URLSession


init(session: URLSession = URLSession.shared) {
self.session = session
}


func fetchRemote<Model: Codable>(_ val: Model.Type, url: URL,
completion: @escaping (Result<Codable, DataResponseError>) -> Void) {
let urlRequest = URLRequest(url: url)
session.dataTask(with: urlRequest, completionHandler: { data, response, error in
guard
let httpResponse = response as? HTTPURLResponse,
httpResponse.hasSuccessStatusCode,
let data = data
else {
completion(Result.failure(DataResponseError.network))
return
}
guard let decodedResponse = try? JSONDecoder().decode(Model.self, from: data) else {
completion(Result.failure(DataResponseError.decoding))
return
}
completion(Result.success(decodedResponse))
}).resume()
}
}

你会发现这里是GitHub MVVM架构与rest API Swift项目

在移动软件工程中,应用最广泛的是Clean Architecture + MVVM和Redux模式。

Clean Architecture + MVVM由3层组成: 域、表示、数据层。 表示层和数据存储层依赖于域层:

Presentation Layer -> Domain Layer <- Data Repositories Layer

表示层由视图模型和视图(MVVM)组成:

Presentation Layer (MVVM) = ViewModels + Views
Domain Layer = Entities + Use Cases + Repositories Interfaces
Data Repositories Layer = Repositories Implementations + API (Network) + Persistence DB

在这篇文章中,有一个清洁架构+ MVVM更详细的描述 https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3 < / p >