我想了解iOS中网络应用程序的基本、抽象和正确的架构方法
构建应用程序体系 struct 有no种"最佳"或"最正确"的方法.这是一项有创意的工作.您应该始终 Select 最直接、最可扩展的体系 struct ,这对任何开始从事您的项目的开发人员或团队中的其他开发人员来说都是明确的,但我同意,体系 struct 可以是"好的"和"坏的".
你说:
从经验丰富的iOS开发者那里收集最有趣的方法
我认为我的方法不是最有趣的,也不是最正确的,但我已经在几个项目中使用了它,并对它感到满意.这是你上面提到的一种混合方法,也是我自己研究工作的改进.我对构建方法的问题很感兴趣,它结合了几个众所周知的模式和习惯用法.我认为有很多Fowler's enterprise patterns可以成功地应用到移动应用中.下面是最有趣的列表,我们可以应用它们来创建IOS应用程序体系 struct (in my opinion):Service Layer、Unit Of Work、Remote Facade、Data Transfer Object、Gateway、Layer Supertype、Special Case、Domain Model.您应该始终正确地设计模型层,并且始终不要忘记持久性(它可以显著提高应用程序的性能).你可以用Core Data
来做这件事.但是您should not忘了,Core Data
不是ORM或数据库,而是一个对象图管理器,持久化是它的一个很好的 Select .因此,通常Core Data
对于您的需求来说太重了,您可以考虑新的解决方案,如Realm和Couchbase Lite,或者基于原始SQLite或LevelDB构建您自己的轻量级对象映射/持久层.另外,我建议您熟悉一下Domain Driven Design和CQRS.
首先,我认为,我们should为网络创造了另一个层面,因为我们不想要肥胖的控制器或沉重的、不堪重负的模型.我不相信这fat model, skinny controller
件事.但是我do believe在skinny everything
接近,因为没有课应该是胖的,永远.所有的网络通常都可以抽象为业务逻辑,因此我们应该有另一个层,我们可以把它放在那里.Service Layer是我们需要的:
它封装应用程序的业务逻辑,在实现其操作时控制事务并协调响应.
在我们的MVC
领域中,Service Layer
有点像域模型和控制器之间的中介.这种方法有一个类似的变体,叫做MVCS,其中Store
实际上是我们的Service
层.Store
个供应商为实例建模并处理网络、缓存等.我想提到的是,您需要在服务层编写所有的网络和业务逻辑.这也可以被认为是一个糟糕的设计.有关更多信息,请查看Anemic和Rich域模型.一些服务方法和业务逻辑可以在模型中处理,因此它将是一个"丰富"(带有行为)的模型.
我总是广泛使用两个库:AFNetworking 2.0和ReactiveCocoa.我认为它是与网络和web服务交互或包含复杂UI逻辑的任何现代应用程序的首选.
ARCHITECTURE
首先,我创建了一个通用的APIClient
类,它是AFHTTPSessionManager的子类.这是应用程序中所有网络的主力:所有服务类别都将实际的睡觉请求委托给它.它包含HTTP客户端的所有自定义,这是我在特定应用程序中需要的:SSL固定、错误处理和创建简单的NSError
对象,这些对象具有详细的失败原因和所有API
和连接错误的描述(在这种情况下,控制器将能够为用户显示正确的消息),设置请求和响应序列化程序、http标头和其他与网络相关的内容.然后,我根据它们实现的业务逻辑将所有API请求逻辑地划分为子服务,或者更准确地说,是microservices:UserSerivces
、CommonServices
、SecurityServices
、FriendsServices
等等.这些微服务中的每一个都是单独的类.它们加在一起,形成了Service Layer
.这些类包含每个API请求的方法,处理域模型,并始终向调用者返回RACSignal
和已解析的响应模型或NSError
.
我想提一下,如果你有复杂的模型序列化逻辑,那么为它创建另一个层:大约Data Mapper层,但是更通用,比如JSON/XML->;模型映射器.如果您有缓存:那么也将其创建为单独的层/服务(您不应该将业务逻辑与缓存混为一谈).为什么?因为正确的缓存层可能相当复杂,有它自己的trap .人们实现复杂的逻辑来获得有效的、可预测的高速缓存,例如具有基于函数的投影的一元式高速缓存.你可以阅读这个叫做Carlos的美丽的图书馆来了解更多.别忘了,核心数据确实可以帮助您解决所有缓存问题,并且允许您编写更少的逻辑.此外,如果您在NSManagedObjectContext
和服务器请求模型之间有一些逻辑,您可以使用Repository模式,它将检索数据并将其映射到实体模型的逻辑与作用于模型的业务逻辑分开.因此,我建议即使您拥有基于核心数据的体系 struct ,也应该使用存储库模式.存储库可以将对象(如NSFetchRequest
、NSEntityDescription
、NSPredicate
等)抽象为普通方法(如get
或put
).
在服务层完成所有这些操作之后,调用者(视图控制器)可以在ReactiveCocoa
原语的帮助下对响应执行一些复杂的异步操作:信号操作、链接、映射等,或者直接订阅它并在视图中显示结果.我在所有这些服务类别MYSAPIClient
中注入Dependency Injection,这将把特定的服务调用转换成对睡觉端点的相应的GET
POST
PUT
DELETE
等请求.在这种情况下,APIClient
被隐式地传递给所有控制器,您可以通过在APIClient
个服务类上参数化来明确这一点.如果您想对特定的服务类别使用APIClient
的不同定制,这可能是有意义的,但是如果您出于某些原因不想要额外的副本,或者您确定您将始终使用APIClient
的一个特定实例(没有自定义)-将其设置为单例,但不要这样做,请不要将服务类设置为单例.
然后,每个带有DI的视图控制器再次注入它需要的服务类,调用适当的服务方法,并用UI逻辑合成它们的结果.对于依赖项注入,我喜欢使用BloodMagic或更强大的框架Typhoon.我从不使用单人,god APIManagerWhatever
类或其他错误的东西.因为如果你给WhateverManager
班打电话,这表明你不知道它的用途,它是bad design choice.Singletons也是一种反模式,在most种情况下(罕见的情况除外)是wrong种解决方案.仅当满足以下所有三个条件时,才应考虑单例:
- 单实例所有权不能合理分配的;
- 延迟初始化是可取的;
- 不提供全球访问.
在我们的例子中,单个实例的所有权不是问题,而且在我们将神管理器划分为服务之后,我们也不需要全局访问,因为现在只有一个或几个专用控制器需要特定的服务(例如,UserProfile
个控制器需要UserServices
等等).
我们应该始终尊重SOLID中的S
原则,使用separation of concerns,所以不要将所有的服务方法和网络调用放在一个类中,因为这太疯狂了,尤其是在开发大型企业应用程序时.这就是为什么我们应该考虑依赖注入和服务的方法.我认为这种方法是现代的和post-OO的.在本例中,我们将应用程序分为两部分:控制逻辑(控制器和事件)和参数.
一种参数是普通的"数据"参数.这就是我们传递函数、操作、修改、持久化等等的内容.这些是实体、聚合、集合、 case 类.另一种是"服务"参数.这些类封装业务逻辑,允许与外部系统通信,提供数据访问.
以下是我的体系 struct 的一般工作流程示例.假设我们有一个FriendsViewController
,它显示用户的朋友列表,并且我们有一个从朋友中删除的选项.我在我的FriendsServices
类中创建了一个名为:
- (RACSignal *)removeFriend:(Friend * const)friend
其中Friend
是一个模型/域对象(或者,如果它们具有相似的属性,那么它可以只是一个User
对象).这个方法解析Friend
到NSDictionary
个JSON参数friend_id
、name
、surname
、friend_request_id
等等.对于这种样板文件和模型层,我总是使用Mantle库(前后解析、管理JSON中的嵌套对象层次 struct 等等).解析后,它调用APIClient
DELETE
方法来发出实际的REST请求,并将RACSignal
中的Response
返回给调用者(在我们的例子中是FriendsViewController
),以便为用户显示适当的消息或其他信息.
如果我们的应用程序非常大,我们必须更清晰地分离我们的逻辑.例如,将`Repository`或模型逻辑与`Service`混合在一起并不"总是"好的.当我描述我的方法时,我说过`removeFriend`方法应该在`Service`层中,但是如果我们更加迂腐,我们可以注意到它更好地属于`Repository`.让我们记住什么是存储库.埃里克·埃文斯(Eric Evans)在他的书[DDD]中对此进行了精确的描述:
存储库将特定类型的所有对象表示为概念集.它的行为类似于一个集合,只是具有更精细的查询功能.
因此,Repository
本质上是一个门面,它使用集合样式的语义(添加、更新、删除)来提供对数据/对象的访问.这就是为什么当你有getFriendsList
、getUserGroups
、removeFriend
这样的东西时,你可以把它放在Repository
中,因为类似集合的语义在这里非常清楚.代码如下:
- (RACSignal *)approveFriendRequest:(FriendRequest * const)request;
这绝对是一个业务逻辑,因为它超越了基本的CRUD
操作,连接了两个域对象(Friend
和Request
),这就是为什么它应该放在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,因为它很快就会变得不可读,特别是对于新手来说.当它真的可以简化你的目标和逻辑时就使用它.
所以,多阅读、混合、试验,试着从不同的架构方法中挑选出最好的.这是我能给你的最好的建议.