构建 iOS 网络应用程序(REST 客户端)的最佳架构方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/24162051/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Best architectural approaches for building iOS networking applications (REST clients)
提问by MainstreamDeveloper00
I'm an iOS developer with some experience and this question is really interesting to me. I saw a lot of different resources and materials on this topic, but nevertheless I'm still confused. What is the best architecture for an iOS networked application? I mean basic abstract framework, patterns, which will fit every networking application whether it is a small app which only have a few server requests or a complex REST client. Apple recommends to use MVC
as a basic architectural approach for all iOS applications, but neither MVC
nor the more modern MVVM
patterns explain where to put network logic code and how to organize it in general.
我是一名有一定经验的 iOS 开发人员,这个问题对我来说真的很有趣。我在这个主题上看到了很多不同的资源和材料,但我仍然感到困惑。iOS 联网应用程序的最佳架构是什么?我的意思是基本的抽象框架、模式,它适用于每个网络应用程序,无论是只有几个服务器请求的小型应用程序还是复杂的 REST 客户端。Apple 建议将其MVC
用作所有 iOS 应用程序的基本架构方法,但MVC
更现代的MVVM
模式都没有解释将网络逻辑代码放在哪里以及如何组织它。
Do I need to develop something like MVCS
(S
for Service
) and in this Service
layer put all API
requests and other networking logic, which in perspective may be really complex? After doing some research I found two basic approaches for this. Hereit was recommended to create a separate class for every network request to web-service API
(like LoginRequest
class or PostCommentRequest
class and so on) which all inherits from the base request abstract class AbstractBaseRequest
and in addition to create some global network manager which encapsulates common networking code and other preferences (it may be AFNetworking
customisation or RestKit
tuning, if the we have complex object mappings and persistence, or even an own network communication implementation with standard API). But this approach seems an overhead for me. Another approach is to have some singleton API
dispatcher or manager class as in the first approach, but notto create classes for every request and instead to encapsulate every request as an instance public method of this manager class like: fetchContacts
, loginUser
methods, etc. So, what is the best and correct way? Are there other interesting approaches I don't know yet?
我是否需要开发类似MVCS
( S
for Service
) 并在这Service
一层中放置所有API
请求和其他网络逻辑的东西,从角度来看这可能真的很复杂?在做了一些研究之后,我找到了两种基本的方法。这里建议为每个网络请求创建一个单独的类API
(如LoginRequest
类或PostCommentRequest
类等),这些类都继承自基本请求抽象类AbstractBaseRequest
,此外还创建了一些封装公共网络代码和其他偏好(可能是AFNetworking
定制或RestKit
调优,如果我们有复杂的对象映射和持久性,或者甚至是自己的带有标准 API 的网络通信实现)。但这种方法对我来说似乎是一种开销。另一种方法是API
像第一种方法一样使用一些单例调度程序或管理器类,但不是为每个请求创建类,而是将每个请求封装为该管理器类的实例公共方法,例如:fetchContacts
,loginUser
方法等。 那么,什么是最好和正确的方法吗?还有其他我不知道的有趣方法吗?
And should I create another layer for all this networking stuff like Service
, or NetworkProvider
layer or whatever on top of my MVC
architecture, or this layer should be integrated (injected) into existing MVC
layers e.g. Model
?
我是否应该为所有这些网络内容创建另一个层,例如Service
,NetworkProvider
层或我的MVC
架构之上的任何层,或者该层应该集成(注入)到现有MVC
层中,例如Model
?
I know there exists beautiful approaches, or how then such mobile monsters like Facebook client or LinkedIn client deal with exponentially growing complexity of networking logic?
我知道存在漂亮的方法,或者像 Facebook 客户端或 LinkedIn 客户端这样的移动怪物如何处理成倍增长的网络逻辑复杂性?
I know there are no exact and formal answer to the problem. The goal of this question is to collect the most interesting approaches from experienced iOS developers. The best suggested approach will be marked as accepted and awarded with a reputation bounty, others will be upvoted. It is mostly a theoretical and research question. I want to understand basic, abstract and correct architectural approach for networking applications in iOS. I hope for detailed explanation from experienced developers.
我知道这个问题没有准确和正式的答案。这个问题的目标是从有经验的 iOS 开发人员那里收集最有趣的方法。最佳建议方法将被标记为已接受并获得声誉奖励,其他方法将被投票。这主要是一个理论和研究问题。我想了解 iOS 中网络应用程序的基本、抽象和正确的架构方法。我希望有经验的开发人员详细解释。
回答by Oleksandr Karaberov
I want to understand basic, abstract and correct architectural approach for networking applications in iOS
: there is no"the best", or "the most correct" approach for building an application architecture. It is a verycreative job. You should always choose the most straightforward and extensible architecture, which will be clear for any developer, who begin to work on your project or for other developers in your team, but I agree, that there can be a "good" and a "bad" architecture.
I want to understand basic, abstract and correct architectural approach for networking applications in iOS
:构建应用程序架构没有“最好的”或“最正确的”方法。这是一项非常有创意的工作。您应该始终选择最直接和可扩展的架构,这对于任何开始为您的项目或团队中的其他开发人员工作的开发人员来说都是清楚的,但我同意,可以有“好”和“坏”之分“ 建筑学。
You said: collect the most interesting approaches from experienced iOS developers
, I don't think that my approach is the most interesting or correct, but I've used it in several projects and satisfied with it. It is a hybrid approach of the ones you have mentioned above, and also with improvements from my own research efforts. I'm interesting in the problems of building approaches, which combine several well-known patterns and idioms. I think a lot of Fowler's enterprise patternscan be successfully applied to the mobile applications. Here is a list of the most interesting ones, which we can apply for creating an iOS application architecture (in my opinion): Service Layer, Unit Of Work, Remote Facade, Data Transfer Object, Gateway, Layer Supertype, Special Case, Domain Model. You should always correctly design a model layer and always don't forget about the persistence (it can significantly increase your app's performance). You can use Core Data
for this. But you should notforget, that Core Data
is not an ORM or a database, but an object graph manager with persistence as a good option of it. So, very often Core Data
can be too heavy for your needs and you can look at new solutions such as Realmand Couchbase Lite, or build your own lightweight object mapping/persistence layer, based on raw SQLite or LevelDB. Also I advice you to familiarize yourself with the Domain Driven Designand CQRS.
你说:collect the most interesting approaches from experienced iOS developers
,我不认为我的方法是最有趣或最正确的,但我已经在几个项目中使用了它并且对它感到满意。这是你上面提到的方法的混合方法,也是我自己研究工作的改进。我对构建方法的问题很感兴趣,这些方法结合了几种众所周知的模式和习语。我认为很多Fowler 的企业模式都可以成功地应用到移动应用程序中。以下是最有趣的列表,我们可以申请创建 iOS 应用程序架构(在我看来):服务层、工作单元、远程外观、数据传输对象、网关、层超类型、特例、领域模型。您应该始终正确设计模型层,并且始终不要忘记持久性(它可以显着提高您的应用程序的性能)。您可以Core Data
为此使用。但是您不要忘记,这Core Data
不是 ORM 或数据库,而是具有持久性的对象图管理器作为它的不错选择。因此,Core Data
对于您的需求而言,很多时候可能过于繁重,您可以查看新的解决方案,例如Realm和Couchbase Lite,或者基于原始 SQLite 或LevelDB构建您自己的轻量级对象映射/持久层. 我还建议您熟悉领域驱动设计和CQRS。
At first, I think, we shouldcreate another layer for networking, because we don't want fat controllers or heavy, overwhelmed models. I don't believe in those fat model, skinny controller
things. But I do believein skinny everything
approach, because no class should be fat, ever. All networking can be generally abstracted as business logic, consequently we should have another layer, where we can put it. Service Layeris what we need:
起初,我认为,我们应该为网络创建另一个层,因为我们不想要胖控制器或沉重、不堪重负的模型。我不相信那些fat model, skinny controller
东西。但我相信在skinny everything
方法,因为不用上课应该是脂肪,永远。所有的网络一般都可以抽象为业务逻辑,因此我们应该有另一层,我们可以把它放在这里。服务层是我们需要的:
It encapsulates the application's business logic, controlling transactions
and coordinating responses in the implementation of its operations.
In our MVC
realm Service Layer
is something like a mediator between domain model and controllers. There is a rather similar variation of this approach called MVCSwhere a Store
is actually our Service
layer. Store
vends model instances and handles the networking, caching etc. I want to mention that you should notwrite all your networking and business logic in your service layer. This also can be considered as a bad design. For more info look at the Anemicand Richdomain models. Some service methods and business logic can be handled in the model, so it will be a "rich" (with behaviour) model.
在我们的MVC
领域Service Layer
中,就像是域模型和控制器之间的中介。这种方法有一个相当相似的变体,称为MVCS,其中 aStore
实际上是我们的Service
层。Store
出售模型实例并处理网络、缓存等。我想提一下,您不应该在服务层中编写所有网络和业务逻辑。这也可以被认为是一个糟糕的设计。有关更多信息,请查看贫血和富域模型。模型中可以处理一些服务方法和业务逻辑,因此它将是一个“丰富”(具有行为)的模型。
I always extensively use two libraries: AFNetworking 2.0and ReactiveCocoa. I think it is a must havefor any modern application that interacts with the network and web-services or contains complex UI logic.
我总是广泛使用两个库:AFNetworking 2.0和ReactiveCocoa。我认为对于与网络和 Web 服务交互或包含复杂 UI 逻辑的任何现代应用程序来说,它都是必备的。
ARCHITECTURE
建筑学
At first I create a general APIClient
class, which is a subclass of AFHTTPSessionManager. This is a workhorse of all networking in the application: all service classes delegate actual REST requests to it. It contains all the customizations of HTTP client, which I need in the particular application: SSL pinning, error processing and creating straightforward NSError
objects with detailed failure reasons and descriptions of all API
and connection errors (in such case controller will be able to show correct messages for the user), setting request and response serializers, http headers and other network-related stuff. Then I logically divide all the API requests into subservices or, more correctly, microservices: UserSerivces
, CommonServices
, SecurityServices
, FriendsServices
and so on, accordingly to business logic they implement. Each of these microservices is a separate class. They, together, form a Service Layer
. These classes contain methods for each API request, process domain models and always returns a RACSignal
with the parsed response model or NSError
to the caller.
首先我创建了一个通用APIClient
类,它是AFHTTPSessionManager的子类。这是应用程序中所有网络的主力:所有服务类都将实际的 REST 请求委托给它。它包含我在特定应用程序中需要的 HTTP 客户端的所有自定义:SSL 固定、错误处理和创建NSError
带有详细失败原因和所有API
连接错误描述的简单对象(在这种情况下,控制器将能够显示正确的消息用户)、设置请求和响应序列化程序、http 标头和其他与网络相关的内容。然后我在逻辑上将所有 API 请求划分为子服务,或者更准确地说,微服务:UserSerivces
, CommonServices
, SecurityServices
,FriendsServices
依此类推,根据他们实现的业务逻辑。这些微服务中的每一个都是一个单独的类。它们一起形成一个Service Layer
. 这些类包含每个 API 请求的方法,处理域模型,并始终返回RACSignal
带有解析响应模型或NSError
调用者的方法。
I want to mention that if you have complex model serialisation logic - then create another layer for it: something like Data Mapperbut more general e.g. JSON/XML -> Model mapper. If you have cache: then create it as a separate layer/service too (you shouldn't mix business logic with caching). Why? Because correct caching layer can be quite complex with its own gotchas. People implement complex logic to get valid, predictable caching like e.g. monoidal caching with projections based on profunctors. You can read about this beautiful library called Carlosto understand more. And don't forget that Core Data can really help you with all caching issues and will allow you to write less logic. Also, if you have some logic between NSManagedObjectContext
and server requests models, you can use Repositorypattern, which separates the logic that retrieves the data and maps it to the entity model from the business logic that acts on the model. So, I advice to use Repository pattern even when you have a Core Data based architecture. Repository can abstract things, like NSFetchRequest
,NSEntityDescription
, NSPredicate
and so on to plain methods like get
or put
.
我想提一下,如果您有复杂的模型序列化逻辑 - 然后为其创建另一个层:类似于Data Mapper但更通用的东西,例如 JSON/XML -> Model mapper。如果您有缓存:那么也将其创建为单独的层/服务(您不应将业务逻辑与缓存混合使用)。为什么?因为正确的缓存层可能会非常复杂,有自己的陷阱。人们实现复杂的逻辑来获得有效的、可预测的缓存,例如基于 profunctor 的投影的幺半群缓存。您可以阅读这个名为Carlos 的美丽库以了解更多信息。并且不要忘记 Core Data 可以真正帮助您解决所有缓存问题,并且可以让您编写更少的逻辑。此外,如果您在NSManagedObjectContext
和服务器请求模型之间有一些逻辑,您可以使用存储库模式,它将检索数据并将其映射到实体模型的逻辑与作用于模型的业务逻辑分开。因此,即使您拥有基于 Core Data 的架构,我也建议使用 Repository 模式。仓库可以抽象的东西,比如NSFetchRequest
,NSEntityDescription
,NSPredicate
等为普通方法,如get
或put
。
After all these actions in the Service layer, caller (view controller) can do some complex asynchronous stuff with the response: signal manipulations, chaining, mapping, etc. with the help of ReactiveCocoa
primitives , or just subscribe to it and show results in the view. I inject with the Dependency Injectionin all these service classes my APIClient
, which will translate a particular service call into corresponding GET
, POST
, PUT
, DELETE
, etc. request to the REST endpoint. In this case APIClient
is passed implicitly to all controllers, you can make this explicit with a parametrised over APIClient
service classes. This can make sense if you want to use different customisations of the APIClient
for particular service classes, but if you ,for some reasons, don't want extra copies or you are sure that you always will use one particular instance (without customisations) of the APIClient
- make it a singleton, but DON'T, please DON'T make service classes as singletons.
在 Service 层完成所有这些操作之后,调用者(视图控制器)可以对响应执行一些复杂的异步操作:借助ReactiveCocoa
原语进行信号操作、链接、映射等,或者只是订阅它并在视图中显示结果. 我注入与依赖注入在所有这些服务类我APIClient
,这将转化特定服务调用成相应的GET
,POST
,PUT
,DELETE
,等请求REST端点。在这种情况下APIClient
,隐式传递给所有控制器,您可以使用参数化APIClient
服务类使其显式。如果您想使用不同的自定义APIClient
对于特定的服务类,但是如果您出于某些原因不想要额外的副本,或者您确定您总是会使用一个特定的实例(没有自定义)APIClient
- 使其成为单例,但不要,请不要'不要将服务类设为单例。
Then each view controller again with the DI injects the service class it needs, calls appropriate service methods and composes their results with the UI logic. For dependency injection I like to use BloodMagicor a more powerful framework Typhoon. I never use singletons, God APIManagerWhatever
class or other wrong stuff. Because if you call your class WhateverManager
, this indicates than you don't know its purpose and it is a bad design choice. Singletons is also an anti-pattern, and in mostcases (except rare ones) is a wrongsolution. Singleton should be considered only if all three of the following criteria are satisfied:
然后每个带有 DI 的视图控制器再次注入它需要的服务类,调用适当的服务方法并将它们的结果与 UI 逻辑组合起来。对于依赖注入,我喜欢使用BloodMagic或更强大的框架Typhoon。我从不使用单身人士、上帝APIManagerWhatever
课或其他错误的东西。因为如果你给你的 class 打电话WhateverManager
,这表明你不知道它的目的,这是一个糟糕的设计选择。单例也是一种反模式,在大多数情况下(除了罕见的)是错误的解决方案。仅当满足以下所有三个标准时才应考虑单例:
- Ownership of the single instance cannot be reasonably assigned;
- Lazy initialization is desirable;
- Global access is not otherwise provided for.
- 无法合理分配单个实例的所有权;
- 延迟初始化是可取的;
- 没有以其他方式提供全局访问。
In our case ownership of the single instance is not an issue and also we don't need global access after we divided our god manager into services, because now only one or several dedicated controllers need a particular service (e.g. UserProfile
controller needs UserServices
and so on).
在我们的例子中,单个实例的所有权不是问题,而且我们在将我们的上帝管理器划分为服务后也不需要全局访问,因为现在只有一个或几个专用控制器需要特定的服务(例如UserProfile
控制器需求UserServices
等) .
We should always respect S
principle in SOLIDand use separation of concerns, so don't put all your service methods and networks calls in one class, because it's crazy, especially if you develop a large enterprise application. That's why we should consider dependency injection and services approach. I consider this approach as modern and post-OO. In this case we split our application into two parts: control logic (controllers and events) and parameters.
我们应该始终尊重SOLID 中的S
原则并使用关注点分离,因此不要将所有服务方法和网络调用放在一个类中,因为这很疯狂,尤其是在您开发大型企业应用程序时。这就是为什么我们应该考虑依赖注入和服务方法。我认为这种方法是现代的和后面向对象的。在这种情况下,我们将应用程序分为两部分:控制逻辑(控制器和事件)和参数。
One kind of parameters would be ordinary “data” parameters. That's what we pass around functions, manipulate, modify, persist, etc. These are entities, aggregates, collections, case classes. The other kind would be “service” parameters. These are classes which encapsulate business logic, allow communicating with external systems, provide data access.
一种参数是普通的“数据”参数。这就是我们传递函数、操作、修改、持久化等的内容。它们是实体、聚合、集合、案例类。另一种是“服务”参数。这些是封装业务逻辑、允许与外部系统通信、提供数据访问的类。
Here is a general workflow of my architecture by example. Let's suppose we have a FriendsViewController
, which displays list of user's friends and we have an option to remove from friends. I create a method in my FriendsServices
class called:
这是我的架构的一般工作流程示例。假设我们有一个FriendsViewController
,它显示用户的朋友列表,我们可以选择从朋友中删除。我在我的FriendsServices
类中创建了一个方法,名为:
- (RACSignal *)removeFriend:(Friend * const)friend
where Friend
is a model/domain object (or it can be just a User
object if they have similar attributes). Underhood this method parses Friend
to NSDictionary
of JSON parameters friend_id
, name
, surname
, friend_request_id
and so on. I always use Mantlelibrary for this kind of boilerplate and for my model layer (parsing back and forward, managing nested object hierarchies in JSON and so on). After parsing it calls APIClient
DELETE
method to make an actual REST request and returns Response
in RACSignal
to the caller (FriendsViewController
in our case) to display appropriate message for the user or whatever.
哪里Friend
是模型/域对象(或者User
如果它们具有相似的属性,它可以只是一个对象)。引擎盖下此方法分析Friend
,以NSDictionary
JSON的参数friend_id
,name
,surname
,friend_request_id
等等。我总是将Mantle库用于这种样板和我的模型层(来回解析、管理 JSON 中的嵌套对象层次结构等)。解析后它调用APIClient
DELETE
方法以使实际的REST请求并返回Response
在RACSignal
给调用者(FriendsViewController
在我们的例子),以供用户或任何显示适当的消息。
If our application is a very big one, we have to separate our logic even clearer. E.g. it is not alwaysgood to mix Repository
or model logic with Service
one. When I described my approach I had said that removeFriend
method should be in the Service
layer, but if we will be more pedantic we can notice that it better belongs to Repository
. Let's remember what Repository is. Eric Evans gave it a precise description in his book [DDD]:
如果我们的应用程序是一个非常大的应用程序,我们必须将我们的逻辑分离得更清晰。例如,将逻辑与一个混合或建模并不总是好的。当我描述我的方法时,我说过该方法应该在层中,但是如果我们更加迂腐,我们会注意到它更好地属于. 让我们记住什么是存储库。Eric Evans 在他的书 [DDD] 中对其进行了精确的描述:Repository
Service
removeFriend
Service
Repository
A Repository represents all objects of a certain type as a conceptual set. It acts like a collection, except with more elaborate querying capability.
存储库将某种类型的所有对象表示为一个概念集。它就像一个集合,除了具有更复杂的查询功能。
So, a Repository
is essentially a facade that uses Collection style semantics (Add, Update, Remove) to supply access to data/objects. That's why when you have something like: getFriendsList
, getUserGroups
, removeFriend
you can place it in the Repository
, because collection-like semantics is pretty clear here. And code like:
因此,aRepository
本质上是一个使用集合样式语义(添加、更新、删除)来提供对数据/对象的访问的外观。这就是为什么当你有类似: getFriendsList
, 的东西时getUserGroups
,removeFriend
你可以把它放在 中Repository
,因为类集合的语义在这里很清楚。代码如下:
- (RACSignal *)approveFriendRequest:(FriendRequest * const)request;
is definitely a business logic, because it is beyond basic CRUD
operations and connect two domain objects (Friend
and Request
), that's why it should be placed in the Service
layer. Also I want to notice: don't create unnecessary abstractions. Use all these approaches wisely. Because if you will overwhelm your application with abstractions, this will increaseits accidental complexity, and complexity causes more problemsin software systems than anything else
绝对是一个业务逻辑,因为它超越了基本的CRUD
操作,并且连接了两个域对象(Friend
和Request
),这就是为什么它应该放在Service
层中。我还要注意:不要创建不必要的抽象。明智地使用所有这些方法。因为如果你用抽象来压倒你的应用程序,这会增加它的意外复杂性,而复杂性在软件系统中比其他任何事情都
造成更多的问题
I describe you an "old" Objective-C example but this approach can be very easy adapted for Swift language with a lot more improvements, because it has more useful features and functional sugar. I highly recommend to use this library: Moya. It allows you to create a more elegant APIClient
layer (our workhorse as you remember). Now our APIClient
provider will be a value type (enum) with extensions conforming to protocols and leveraging destructuring pattern matching. Swift enums + pattern matching allows us to create algebraic data typesas in classic functional programming. Our microservices will use this improved APIClient
provider as in usual Objective-C approach. For model layer instead of Mantle
you can use ObjectMapper libraryor I like to use more elegant and functional Argolibrary.
我向您描述了一个“旧的”Objective-C 示例,但是这种方法可以很容易地适应 Swift 语言并进行更多改进,因为它具有更多有用的特性和功能糖。我强烈建议使用这个库:Moya。它允许您创建一个更优雅的APIClient
层(您记得是我们的主力)。现在我们的APIClient
提供者将是一个值类型(枚举),扩展符合协议并利用解构模式匹配。Swift 枚举 + 模式匹配允许我们像经典函数式编程一样创建代数数据类型。我们的微服务将APIClient
像通常的 Objective-C 方法一样使用这个改进的提供程序。对于模型层,Mantle
您可以使用ObjectMapper 库代替或者我喜欢使用更优雅和功能更强大的Argo库。
So, I described my general architectural approach, which can be adapted for any application, I think. There can be a lot more improvements, of course. I advice you to learn functional programming, because you can benefit from it a lot, but don't go too far with it too. Eliminating excessive, shared, global mutable state, creating an immutable domain modelor creating pure functions without external side-effects is, generally, a good practice, and new Swift
language encourages this. But always remember, that overloading your code with heavy pure functional patterns, category-theoretical approaches is a badidea, because otherdevelopers will read and support your code, and they can be frustrated or scary of the prismatic profunctors
and such kind of stuff in your immutable model. The same thing with the ReactiveCocoa
: don't RACify
your code too much, because it can become unreadable really fast, especially for newbies. Use it when it can really simplify your goals and logic.
所以,我描述了我的通用架构方法,我认为它可以适用于任何应用程序。当然,可以有更多的改进。我建议你学习函数式编程,因为你可以从中受益很多,但也不要走得太远。消除过多的、共享的、全局可变状态,创建一个不可变的域模型或创建没有外部副作用的纯函数通常是一种很好的做法,新Swift
语言鼓励这样做。但永远记住,用大量的纯函数模式、类别理论方法重载你的代码是一个坏主意,因为其他开发人员会阅读和支持你的代码,他们可能会感到沮丧或害怕prismatic profunctors
以及你的不可变模型中的这种东西。同样的事情ReactiveCocoa
:不要RACify
你的代码太多,因为它会很快变得不可读,特别是对于新手。当它可以真正简化您的目标和逻辑时使用它。
So, read a lot, mix, experiment, and try to pick up the best from different architectural approaches
. It is the best advice I can give you.
所以,read a lot, mix, experiment, and try to pick up the best from different architectural approaches
。这是我能给你的最好的建议。
回答by Alex Petropavlovsky
According to the goal of this question, I'd like to describe our architecture approach.
根据这个问题的目标,我想描述一下我们的架构方法。
Architecture approach
架构方法
Our general iOS application's architecture stands on following patterns: Service layers, MVVM, UI Data Binding, Dependency Injection; and Functional Reactive Programmingparadigm.
我们一般的 iOS 应用程序架构基于以下模式:服务层、MVVM、UI 数据绑定、依赖注入;和函数式反应式编程范式。
We can slice a typical consumer facing application into following logical layers:
我们可以将典型的面向消费者的应用程序分为以下逻辑层:
- Assembly
- Model
- Services
- Storage
- Managers
- Coordinators
- UI
- Infrastructure
- 集会
- 模型
- 服务
- 贮存
- 经理
- 协调员
- 用户界面
- 基础设施
Assembly layeris a bootstrap point of our application. It contains a Dependency Injection container and declarations of application's objects and their dependencies. This layer also might contain application's configuration (urls, 3rd party services keys and so on). For this purpose we use Typhoonlibrary.
组装层是我们应用程序的引导点。它包含一个依赖注入容器和应用程序对象及其依赖项的声明。该层还可能包含应用程序的配置(网址、第 3 方服务密钥等)。为此,我们使用Typhoon库。
Model layercontains domain models classes, validations, mappings. We use Mantlelibrary for mapping our models: it supports serialization/deserialization into JSON
format and NSManagedObject
models. For validation and form representation of our models we use FXFormsand FXModelValidationlibraries.
模型层包含域模型类、验证、映射。我们使用Mantle库来映射我们的模型:它支持序列化/反序列化成JSON
格式和NSManagedObject
模型。对于我们模型的验证和表单表示,我们使用FXForms和FXModelValidation库。
Services layerdeclares services which we use for interacting with external systems in order to send or receive data which is represented in our domain model. So usually we have services for communication with server APIs (per entity), messaging services (like PubNub), storage services (like Amazon S3), etc. Basically services wrap objects provided by SDKs (for example PubNub SDK) or implement their own communication logic. For general networking we use AFNetworkinglibrary.
服务层声明了我们用于与外部系统交互的服务,以便发送或接收在我们的领域模型中表示的数据。所以通常我们有与服务器 API(每个实体)、消息服务(如PubNub)、存储服务(如 Amazon S3)等通信的服务。基本上服务包装由 SDK 提供的对象(例如 PubNub SDK)或实现自己的通信逻辑。对于一般网络,我们使用AFNetworking库。
Storage layer's purpose is to organize local data storage on the device. We use Core Data or Realmfor this (both have pros and cons, decision of what to use is based on concrete specs). For Core Data setup we use MDMCoreDatalibrary and bunch of classes - storages - (similar to services) which provide access to local storage for every entity. For Realm we just use similar storages to have access to local storage.
存储层的目的是组织设备上的本地数据存储。我们为此使用 Core Data 或Realm(两者都有优点和缺点,使用什么是基于具体规范的决定)。对于 Core Data 设置,我们使用MDMCoreData库和一堆类 - 存储 -(类似于服务),它们为每个实体提供对本地存储的访问。对于 Realm,我们只使用类似的存储来访问本地存储。
Managers layeris a place where our abstractions/wrappers live.
管理器层是我们的抽象/包装器所在的地方。
In a manager role could be:
在经理角色中可以是:
- Credentials Manager with its different implementations (keychain, NSDefaults, ...)
- Current Session Manager which knows how to keep and provide current user session
- Capture Pipeline which provides access to media devices (video recording, audio, taking pictures)
- BLE Manager which provides access to bluetooth services and peripherals
- Geo Location Manager
- ...
- 凭证管理器及其不同的实现(钥匙串,NSDefaults,...)
- 当前会话管理器知道如何保持和提供当前用户会话
- 提供对媒体设备(视频录制、音频、拍照)的访问的捕获管道
- 提供对蓝牙服务和外围设备的访问的 BLE 管理器
- 地理位置管理器
- ...
So, in role of manager could be any object which implements logic of a particular aspect or concern needed for application working.
因此,管理器的角色可以是实现应用程序工作所需的特定方面或关注点的逻辑的任何对象。
We try to avoid Singletons, but this layer is a place where they live if they are needed.
我们尽量避免单身人士,但如果需要,这一层是他们居住的地方。
Coordinators layerprovides objects which depends on objects from other layers (Service, Storage, Model) in order to combine their logic into one sequence of work needed for certain module (feature, screen, user story or user experience). It usually chains asynchronous operations and knows how to react on their success and failure cases. As an example you can imagine a messaging feature and corresponding MessagingCoordinator
object. Handling sending message operation might look like this:
协调器层提供依赖于来自其他层(服务、存储、模型)的对象的对象,以便将它们的逻辑组合成特定模块(功能、屏幕、用户故事或用户体验)所需的一系列工作。它通常链接异步操作,并且知道如何对它们的成功和失败案例做出反应。例如,您可以想象一个消息传递功能和相应的MessagingCoordinator
对象。处理发送消息操作可能如下所示:
- Validate message (model layer)
- Save message locally (messages storage)
- Upload message attachment (amazon s3 service)
- Update message status and attachments urls and save message locally (messages storage)
- Serialize message to JSON format (model layer)
- Publish message to PubNub (PubNub service)
- Update message status and attributes and save it locally (messages storage)
- 验证消息(模型层)
- 本地保存消息(消息存储)
- 上传消息附件(亚马逊 s3 服务)
- 更新消息状态和附件 URL 并在本地保存消息(消息存储)
- 将消息序列化为 JSON 格式(模型层)
- 将消息发布到 PubNub(PubNub 服务)
- 更新消息状态和属性并保存在本地(消息存储)
On each of above steps an error is handled correspondingly.
在上述每个步骤中,都会相应地处理一个错误。
UI layerconsists of following sublayers:
UI层由以下子层组成:
- ViewModels
- ViewControllers
- Views
- 视图模型
- 视图控制器
- 观看次数
In order to avoid Massive View Controllers we use MVVM pattern and implement logic needed for UI presentation in ViewModels. A ViewModel usually has coordinators and managers as dependencies. ViewModels used by ViewControllers and some kinds of Views (e.g. table view cells). The glue between ViewControllers and ViewModels is Data Binding and Command pattern. In order to make it possible to have that glue we use ReactiveCocoalibrary.
为了避免大规模视图控制器,我们使用 MVVM 模式并在 ViewModel 中实现 UI 呈现所需的逻辑。ViewModel 通常将协调器和管理器作为依赖项。ViewControllers 使用的 ViewModels 和某些类型的视图(例如表格视图单元格)。ViewControllers 和 ViewModels 之间的粘合剂是数据绑定和命令模式。为了让这种胶水成为可能,我们使用了ReactiveCocoa库。
We also use ReactiveCocoa and its RACSignal
concept as an interface and returning value type of all coordinators, services, storages methods. This allows us to chain operations, run them parallelly or serially, and many other useful things provided by ReactiveCocoa.
我们还使用 ReactiveCocoa 及其RACSignal
概念作为所有协调器、服务、存储方法的接口和返回值类型。这允许我们链接操作,并行或串行运行它们,以及 ReactiveCocoa 提供的许多其他有用的东西。
We try to implement our UI behavior in declarative way. Data Binding and Auto Layout helps a lot to achieve this goal.
我们尝试以声明方式实现我们的 UI 行为。数据绑定和自动布局对实现这一目标有很大帮助。
Infrastructure layercontains all the helpers, extensions, utilities needed for application work.
基础设施层包含应用程序工作所需的所有帮助程序、扩展程序和实用程序。
This approach works well for us and those types of apps we usually build. But you should understand, that this is just a subjective approach that shouldbe adapted/changed for concrete team's purpose.
这种方法非常适合我们和我们通常构建的那些类型的应用程序。但是您应该明白,这只是一种主观方法,应该根据具体团队的目的进行调整/更改。
Hope this will help you!
希望能帮到你!
Also you can find more information about iOS development process in this blog post iOS Development as a Service
您还可以在此博客文章iOS 开发即服务中找到有关 iOS 开发过程的更多信息
回答by Rickye
Because all iOS apps are different, I think there are different approaches here to consider, but I usually go this way:
Create a central manager (singleton) class to handle all API requests (usually named APICommunicator) and every instance method is an API call. And there is one central (non-public) method:
因为所有的 iOS 应用程序都不一样,我认为这里有不同的方法可以考虑,但我通常是这样走的:
创建一个中央管理器(单例)类来处理所有 API 请求(通常命名为 APICommunicator)并且每个实例方法都是一个 API 调用. 并且有一种中央(非公开)方法:
-
(RACSignal *)sendGetToServerToSubPath:(NSString *)path withParameters:(NSDictionary *)params;
——
(RACSignal *)sendGetToServerToSubPath:(NSString *)path withParameters:(NSDictionary *)params;
For the record, I use 2 major libraries/frameworks, ReactiveCocoa and AFNetworking. ReactiveCocoa handles async networking responses perfectly, you can do (sendNext:, sendError:, etc.).
This method calls the API, gets the results and sends them through RAC in 'raw' format (like NSArray what AFNetworking returns).
Then a method like getStuffList:
which called the above method subscribes to it's signal, parses the raw data into objects (with something like Motis) and sends the objects one by one to the caller (getStuffList:
and similar methods also return a signal that the controller can subscribe to).
The subscribed controller receives the objects by subscribeNext:
's block and handles them.
I tried many ways in different apps but this one worked the best out of all so I've been using this in a few apps recently, it fits both small and big projects and it's easy to extend and maintain if something needs to be modified.
Hope this helps, I'd like to hear others' opinions about my approach and maybe how others think this could be maybe improved.
作为记录,我使用了 2 个主要的库/框架,ReactiveCocoa 和 AFNetworking。ReactiveCocoa 完美地处理异步网络响应,你可以这样做(sendNext:、sendError: 等)。
此方法调用 API,获取结果并以“原始”格式(如 AFNetworking 返回的 NSArray)通过 RAC 发送它们。
然后getStuffList:
,调用上述方法的方法订阅它的信号,将原始数据解析为对象(使用 Motis 之类的东西)并将对象一个一个地发送给调用者(getStuffList:
类似的方法也返回控制器可以订阅的信号) )。
订阅的控制器接收对象 bysubscribeNext:
的块并处理它们。
我在不同的应用程序中尝试了很多方法,但这个方法效果最好,所以我最近在一些应用程序中使用了它,它适用于小型和大型项目,如果需要修改某些内容,它很容易扩展和维护。
希望这会有所帮助,我想听听其他人对我的方法的看法,也许其他人认为这可以改进。
回答by Andrew Cherkashyn
In my situation I'm usually using ResKitlibrary to set up the network layer. It provides easy-to-use parsing. It reduces my effort on setting up the mapping for different responses and stuff.
在我的情况下,我通常使用ResKit库来设置网络层。它提供了易于使用的解析。它减少了我为不同的响应和内容设置映射的工作。
I only add some code to setup the mapping automatically. I define base class for my models (not protocol because of lot of code to check if some method is implemented or not, and less code in models itself):
我只添加了一些代码来自动设置映射。我为我的模型定义了基类(不是协议,因为有很多代码来检查某些方法是否被实现,模型本身的代码更少):
MappableEntry.h
可映射条目.h
@interface MappableEntity : NSObject
+ (NSArray*)pathPatterns;
+ (NSArray*)keyPathes;
+ (NSArray*)fieldsArrayForMapping;
+ (NSDictionary*)fieldsDictionaryForMapping;
+ (NSArray*)relationships;
@end
MappableEntry.m
MappableEntry.m
@implementation MappableEntity
+(NSArray*)pathPatterns {
return @[];
}
+(NSArray*)keyPathes {
return nil;
}
+(NSArray*)fieldsArrayForMapping {
return @[];
}
+(NSDictionary*)fieldsDictionaryForMapping {
return @{};
}
+(NSArray*)relationships {
return @[];
}
@end
Relationships are objects which represent nested objects in response:
关系是在响应中表示嵌套对象的对象:
RelationshipObject.h
关系对象.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
关系对象.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
Then I'm setting up the mapping for RestKit like this:
然后我像这样为 RestKit 设置映射:
ObjectMappingInitializer.h
ObjectMappingInitializer.h
@interface ObjectMappingInitializer : NSObject
+(void)initializeRKObjectManagerMapping:(RKObjectManager*)objectManager;
@end
ObjectMappingInitializer.m
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
Some example of MappableEntry implementation:
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:
我有带有块定义的头文件,以减少所有 APIRequest 类中的行长度:
APICallbacks.h
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:
我正在使用的 APIRequest 类的示例:
LoginAPI.h
登录API.h
@interface LoginAPI : NSObject
- (void)loginWithCredentials:(Credentials*)credentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError;
@end
LoginAPI.m
登录API.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:
您需要在代码中执行的所有操作,只需初始化 API 对象并在需要时调用它:
SomeViewController.m
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.
我的代码并不完美,但很容易设置一次并用于不同的项目。如果有人对它感兴趣,我可以花一些时间在 GitHub 和 CocoaPods 上的某个地方为它制定一个通用的解决方案。
回答by Fran K.
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.
例如,我最近为一家本地企业制作了一个照片共享应用程序的快速原型。由于业务需要做一些快速而肮脏的事情,因此架构最终是一些用于弹出摄像头的 iOS 代码和一些附加到发送按钮的网络代码,用于将图像上传到 S3 存储并写入到 SimpleDB 域。代码很简单,成本也很低,客户端有一个可扩展的照片集,可通过 REST 调用通过 Web 访问。便宜又笨,该应用程序有很多缺陷,有时会锁定 UI,但是为原型做更多事情是一种浪费,它允许他们部署到他们的员工并轻松生成数千个测试图像,而没有性能或可扩展性担忧。糟糕的架构,但它完美地满足了需求和成本。
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.
另一个项目涉及实施本地安全数据库,当网络可用时,该数据库在后台与公司系统同步。我创建了一个使用 RestKit 的后台同步器,因为它似乎拥有我需要的一切。但是我必须为 RestKit 编写大量自定义代码来处理特殊的 JSON,我可以通过编写我自己的 JSON 到 CoreData 转换来更快地完成这一切。但是,客户希望将这个应用程序带入内部,我觉得 RestKit 会类似于他们在其他平台上使用的框架。我在等着看这是否是一个好的决定。
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.
同样,对我来说,问题是关注需求并让它决定架构。我极力避免使用第三方软件包,因为它们带来的成本只有在应用程序在现场使用一段时间后才会出现。我尽量避免创建类层次结构,因为它们很少有回报。如果我可以在合理的时间内写出一些东西而不是采用一个不完美的包,那么我就会去做。我的代码结构良好,可以调试并进行适当的注释,但第三方软件包很少。话虽如此,我发现 AF Networking 非常有用,不容忽视,而且结构良好、评论良好、维护良好,我经常使用它!RestKit 涵盖了很多常见的情况,但我觉得我在使用它时一直在战斗,我遇到的大多数数据源都充满了怪癖和问题,最好使用自定义代码来处理。在我最近的几个应用程序中,我只使用内置的 JSON 转换器并编写了一些实用方法。
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.
我一直使用的一种模式是从主线程中获取网络调用。我完成的最后 4-5 个应用程序使用 dispatch_source_create 设置了一个后台计时器任务,该任务每隔一段时间就会唤醒并根据需要执行网络任务。您需要做一些线程安全工作,并确保 UI 修改代码被发送到主线程。它还有助于以用户不会感到负担或延迟的方式进行入职/初始化。到目前为止,这一直运作良好。我建议调查这些事情。
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.
最后,我认为随着我们工作的增多和操作系统的发展,我们倾向于开发更好的解决方案。我花了很多年才克服我必须遵循其他人声称是强制性的模式和设计的信念。如果我在当地宗教的环境中工作,嗯,我的意思是部门最佳工程实践,那么我会严格遵守习俗,这就是他们付钱给我的原因。但我很少发现遵循旧的设计和模式是最佳解决方案。我总是试图通过业务需求的棱镜来看待解决方案,并构建与之匹配的架构,并使事情尽可能简单。当我觉得那里还不够,但一切正常时,我就走在正确的轨道上。
回答by bzz
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:
我使用从这里获得的方法: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}}
}
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.
基本上,有 NSOperation 子类生成 NSURLRequest,解析 JSON 响应并将带有结果的回调块添加到队列中。主 API 类构造 NSURLRequest,初始化 NSOperation 子类并将其添加到队列中。
回答by Martin
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:
我们根据情况使用几种方法。对于大多数事情,AFNetworking 是最简单和最健壮的方法,因为您可以设置标题,上传多部分数据,使用 GET、POST、PUT 和 DELETE,并且 UIKit 有许多其他类别,例如允许您设置图像一个网址。在具有大量调用的复杂应用程序中,我们有时会将其抽象为我们自己的便捷方法,例如:
-(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.
但是在某些情况下 AFNetworking 不合适,例如您正在创建框架或其他库组件,因为 AFNetworking 可能已经在另一个代码库中。在这种情况下,如果您进行单个调用或抽象到请求/响应类中,您将使用 NSMutableURLRequest 内联。
回答by Sandy Chapman
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:
我在设计应用程序时避免使用单例。它们是很多人的典型选择,但我认为您可以在其他地方找到更优雅的解决方案。通常我所做的是在 CoreData 中构建我的实体,然后将我的 REST 代码放在 NSManagedObject 类别中。例如,如果我想创建并发布一个新用户,我会这样做:
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.
我使用 RESTKit 进行对象映射并在启动时对其进行初始化。我发现通过单例路由您的所有调用是浪费时间并添加了许多不需要的样板。
In NSManagedObject+Extensions.m:
在 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:
在 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.
如果您对我的解决方案的更多详细信息感兴趣,请告诉我。我很乐意分享。
回答by Kevin
Try https://github.com/kevin0571/STNetTaskQueue
试试https://github.com/kevin0571/STNetTaskQueue
Create API requests in separated classes.
在分离的类中创建 API 请求。
STNetTaskQueue will deal with threading and delegate/callback.
STNetTaskQueue 将处理线程和委托/回调。
Extendable for different protocols.
可扩展为不同的协议。
回答by Nirav Bhatt
From a purely class design perspective, you will usually have something like this:
从纯粹的类设计角度来看,你通常会有这样的事情:
- Your view controllerscontrolling 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 classwill 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 classesthat 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 classwhich 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.
- 你的视图控制器控制一个或多个视图
数据模型类- 这实际上取决于您正在处理的真实不同实体的数量,以及它们之间的关系。
例如,如果您有一组要以四种不同表示形式(列表、图表、图形等)显示的项目,您将有一个用于项目列表的数据模型类,另一个用于项目。的项目类的列表选项卡栏控制器或导航控制器的所有子-将四个视图控制器共享。
数据模型类不仅可以方便地显示数据,还可以序列化它们,其中每个类都可以通过 JSON/XML/CSV(或任何其他)导出方法公开自己的序列化格式。
重要的是要了解您还需要直接与您的 REST API 端点映射的API 请求构建器类。假设您有一个用于登录用户的 API - 因此您的登录 API 构建器类将为登录 API 创建 POST JSON 有效负载。在另一个示例中,目录项 API 列表的 API 请求构建器类将为相应的 api 创建 GET 查询字符串并触发 REST GET 查询。
这些 API 请求构建器类通常会从视图控制器接收数据,并将相同的数据传递回视图控制器以进行 UI 更新/其他操作。然后视图控制器将决定如何使用该数据更新数据模型对象。
最后,REST 客户端的核心- API 数据获取器类,它不会考虑您的应用程序发出的各种 API 请求。这个类更可能是单例,但正如其他人指出的那样,它不一定是单例。
请注意,该链接只是一个典型的实现,并没有考虑会话、cookie 等场景,但它足以让您在不使用任何 3rd 方框架的情况下继续前进。