我是一名有经验的iOS开发者,这个问题对我来说非常有趣.我在这个话题上看到了很多不同的资源和material ,但我仍然感到困惑.iOS网络应用程序的最佳体系 struct 是什么?我指的是基本的抽象框架和模式,它将适合每一个网络应用程序,无论是一个只有少量服务器请求的小应用程序,还是一个复杂的REST客户端.苹果建议将MVC作为所有iOS应用程序的基本架构方法,但无论是MVC还是更现代的MVVM模式,都无法解释网络逻辑代码的放置位置以及总体上的组织方式.

我是否需要开发MVCS这样的东西(Service需要S),并在这Service层中放置所有API个请求和其他网络逻辑,从长远来看,这可能真的很复杂?在做了一些研究之后,我发现了两种基本的方法.Here建议为对web服务API的每个网络请求创建单独的类(如LoginRequest类或PostCommentRequest类等),其全部继承自基本请求抽象类AbstractBaseRequest,并且除了创建封装公共联网代码和其他偏好的某个全局网络管理器之外(如果我们具有复杂的对象映射和持久性,则其可以是AFNetworking定制或RestKit调优,或者甚至可以是具有标准API的自己的网络通信实现).但这种方法对我来说似乎是一种开销.另一种方法是像第一种方法那样让一些单例API分派器或管理器类而不是 for each 请求创建类,并将每个请求封装为该管理器类的实例公共方法,如fetchContactsloginUser方法等.那么,最好且正确的方法是什么呢?还有其他有趣的方法我还不知道吗?

我是应该在我的MVC架构之上为所有这些网络内容创建另一个层(如Service层、NetworkProvider层或其他层),还是应该将该层集成(注入)到现有的MVC层(例如Model层)中?

我知道有很多很好的方法,或者像Facebook客户端或LinkedIn客户端这样的移动怪物是如何应对日益复杂的网络逻辑的?

我知道这个问题没有确切正式的答案 这个问题的目的是从经验丰富的iOS开发者那里收集最有趣的方法.建议的最佳方法将被标记为已接受,并授予声誉奖励,其他方法将被提升.这主要是一个理论和研究问题.我想了解iOS中网络应用程序的基本、抽象和正确的体系 struct 方法.我希望有经验的开发人员能提供详细的解释.

推荐答案

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

构建应用程序体系 struct 有no种"最佳"或"最正确"的方法.这是一项有创意的工作.您应该始终 Select 最直接、最可扩展的体系 struct ,这对任何开始从事您的项目的开发人员或团队中的其他开发人员来说都是明确的,但我同意,体系 struct 可以是"好的"和"坏的".

你说:

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

我认为我的方法不是最有趣的,也不是最正确的,但我已经在几个项目中使用了它,并对它感到满意.这是你上面提到的一种混合方法,也是我自己研究工作的改进.我对构建方法的问题很感兴趣,它结合了几个众所周知的模式和习惯用法.我认为有很多Fowler's enterprise patterns可以成功地应用到移动应用中.下面是最有趣的列表,我们可以应用它们来创建IOS应用程序体系 struct (in my opinion):Service LayerUnit Of WorkRemote FacadeData Transfer ObjectGatewayLayer SupertypeSpecial CaseDomain Model.您应该始终正确地设计模型层,并且始终不要忘记持久性(它可以显著提高应用程序的性能).你可以用Core Data来做这件事.但是您should not忘了,Core Data不是ORM或数据库,而是一个对象图管理器,持久化是它的一个很好的 Select .因此,通常Core Data对于您的需求来说太重了,您可以考虑新的解决方案,如RealmCouchbase Lite,或者基于原始SQLite或LevelDB构建您自己的轻量级对象映射/持久层.另外,我建议您熟悉一下Domain Driven DesignCQRS.

首先,我认为,我们should为网络创造了另一个层面,因为我们不想要肥胖的控制器或沉重的、不堪重负的模型.我不相信这fat model, skinny controller件事.但是我do believeskinny everything接近,因为没有课应该是胖的,永远.所有的网络通常都可以抽象为业务逻辑,因此我们应该有另一个层,我们可以把它放在那里.Service Layer是我们需要的:

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

在我们的MVC领域中,Service Layer有点像域模型和控制器之间的中介.这种方法有一个类似的变体,叫做MVCS,其中Store实际上是我们的Service层.Store个供应商为实例建模并处理网络、缓存等.我想提到的是,您需要在服务层编写所有的网络和业务逻辑.这也可以被认为是一个糟糕的设计.有关更多信息,请查看AnemicRich域模型.一些服务方法和业务逻辑可以在模型中处理,因此它将是一个"丰富"(带有行为)的模型.

我总是广泛使用两个库:AFNetworking 2.0ReactiveCocoa.我认为它是与网络和web服务交互或包含复杂UI逻辑的任何现代应用程序的首选.

ARCHITECTURE

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

我想提一下,如果你有复杂的模型序列化逻辑,那么为它创建另一个层:大约Data Mapper层,但是更通用,比如JSON/XML->;模型映射器.如果您有缓存:那么也将其创建为单独的层/服务(您不应该将业务逻辑与缓存混为一谈).为什么?因为正确的缓存层可能相当复杂,有它自己的trap .人们实现复杂的逻辑来获得有效的、可预测的高速缓存,例如具有基于函数的投影的一元式高速缓存.你可以阅读这个叫做Carlos的美丽的图书馆来了解更多.别忘了,核心数据确实可以帮助您解决所有缓存问题,并且允许您编写更少的逻辑.此外,如果您在NSManagedObjectContext和服务器请求模型之间有一些逻辑,您可以使用Repository模式,它将检索数据并将其映射到实体模型的逻辑与作用于模型的业务逻辑分开.因此,我建议即使您拥有基于核心数据的体系 struct ,也应该使用存储库模式.存储库可以将对象(如NSFetchRequestNSEntityDescriptionNSPredicate等)抽象为普通方法(如getput).

在服务层完成所有这些操作之后,调用者(视图控制器)可以在ReactiveCocoa原语的帮助下对响应执行一些复杂的异步操作:信号操作、链接、映射等,或者直接订阅它并在视图中显示结果.我在所有这些服务类别MYSAPIClient中注入Dependency Injection,这将把特定的服务调用转换成对睡觉端点的相应的GETPOSTPUTDELETE等请求.在这种情况下,APIClient被隐式地传递给所有控制器,您可以通过在APIClient个服务类上参数化来明确这一点.如果您想对特定的服务类别使用APIClient的不同定制,这可能是有意义的,但是如果您出于某些原因不想要额外的副本,或者您确定您将始终使用APIClient的一个特定实例(没有自定义)-将其设置为单例,但不要这样做,请不要将服务类设置为单例.

然后,每个带有DI的视图控制器再次注入它需要的服务类,调用适当的服务方法,并用UI逻辑合成它们的结果.对于依赖项注入,我喜欢使用BloodMagic或更强大的框架Typhoon.我从不使用单人,god APIManagerWhatever类或其他错误的东西.因为如果你给WhateverManager班打电话,这表明你不知道它的用途,它是bad design choice.Singletons也是一种反模式,在most种情况下(罕见的情况除外)是wrong种解决方案.仅当满足以下所有三个条件时,才应考虑单例:

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

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

我们应该始终尊重SOLID中的S原则,使用separation of concerns,所以不要将所有的服务方法和网络调用放在一个类中,因为这太疯狂了,尤其是在开发大型企业应用程序时.这就是为什么我们应该考虑依赖注入和服务的方法.我认为这种方法是现代的和post-OO的.在本例中,我们将应用程序分为两部分:控制逻辑(控制器和事件)和参数.

一种参数是普通的"数据"参数.这就是我们传递函数、操作、修改、持久化等等的内容.这些是实体、聚合、集合、 case 类.另一种是"服务"参数.这些类封装业务逻辑,允许与外部系统通信,提供数据访问.

以下是我的体系 struct 的一般工作流程示例.假设我们有一个FriendsViewController,它显示用户的朋友列表,并且我们有一个从朋友中删除的选项.我在我的FriendsServices类中创建了一个名为:

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

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

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

存储库将特定类型的所有对象表示为概念集.它的行为类似于一个集合,只是具有更精细的查询功能.

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

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

这绝对是一个业务逻辑,因为它超越了基本的CRUD操作,连接了两个域对象(FriendRequest),这就是为什么它应该放在Service层.我还要注意:don't create unnecessary abstractions.明智地使用所有这些方法.因为如果你用抽象来压倒你的应用程序,这将导致它意外的复杂性,而在软件系统中,复杂性将达到causes more problems

我向您描述一个"旧的"Objective-C示例,但是这种方法可以非常容易地适应SWIFT语言,并有更多改进,因为它有更多有用的特性和功能.我强烈推荐使用这个库:Moya.它允许您创建一个更优雅的APIClient层(您还记得我们的主力).现在,我们的APIClient提供程序将是一个值类型(枚举),其扩展符合协议并利用解构模式匹配.SWIFT枚举+模式匹配使我们可以像在classic 函数式编程中一样创建algebraic data types.我们的微服务将像通常的Objective-C方法一样使用这个改进的APIClient提供程序.对于模型层,你可以用ObjectMapper library代替Mantle,或者我喜欢用更优雅、功能更强大的Argo库.

所以,我描述了我的通用架构方法,我认为它可以适用于任何应用程序.当然,还可以有更多的改进.我建议您学习函数式编程,因为您可以从中获益良多,但不要做得太过火.通常,消除过多的共享全局可变状态、创建immutable domain model或创建没有外部副作用的纯函数是一个很好的实践,新的Swift语言鼓励这样做.但请始终记住,使用繁重的纯函数模式、类别理论方法重载您的代码是一个bad的 idea ,因为other个开发人员将阅读并支持您的代码,他们可能会对您的不可变模型中的prismatic profunctors之类的东西感到沮丧或害怕.ReactiveCocoa也是如此:不要使用RACify代码too much,因为它很快就会变得不可读,特别是对于新手来说.当它真的可以简化你的目标和逻辑时就使用它.

所以,多阅读、混合、试验,试着从不同的架构方法中挑选出最好的.这是我能给你的最好的建议.

Ios相关问答推荐

带外部笔划的圆形进度视图

带有动画层的UIView表示不会在工作表中设置动画

如果没有等待,SWIFT Async让任务抛出取消异常

如何在SwiftUI中基于嵌套视图中的状态动态显示外部视图的内容?

当目标位于菜单标签内时,matchedGeometryEffect 的行为很奇怪

视图之间的 SwiftUI 过渡,没有过渡

以正确的方式在 Text() 中使用 colored颜色

SwiftUI 中的偏移量和按钮有问题吗?

如何将标准化坐标系中的点的位置转换为具有相对位置的常规坐标系?

如何快速设置条形按钮的图像?

如何 comments .plist 文件中的行?

光标未显示在 UITextView 中

iOS 中是否有用于自定义振动的 API?

通过touch 传递到下面的 UIViews

如何覆盖@synthesized getter?

如何像在 Facebook 应用程序中一样以编程方式打开设置?

使用 Xcode 4 的 iPhone 临时构建

iOS:以编程方式制作屏幕截图的最快、最高效的方法是什么?

iOS在应用程序中下载并保存图像

未找到符号:kUTTypeImage