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. 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. Here it 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 not to 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? 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? 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? 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.
当前回答
我们根据具体情况使用几种方法。对于大多数事情来说,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内联,如果你正在做一个单独的调用或抽象到一个请求/响应类。
其他回答
我认为目前中型项目使用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项目
我们根据具体情况使用几种方法。对于大多数事情来说,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类呢?
如果你对我的解决方案更详细的信息感兴趣,请告诉我。我很乐意分享。
根据这个问题的目的,我想描述一下我们的架构方法。
体系结构方法
我们通用的iOS应用的架构基于以下模式:服务层、MVVM、UI数据绑定、依赖注入;函数式响应式编程范式。
我们可以将一个典型的面向消费者的应用程序划分为以下逻辑层:
组装 模型 服务 存储 经理 协调员 用户界面 基础设施
组装层是应用程序的引导点。它包含一个依赖注入容器和应用程序对象及其依赖项的声明。这一层还可能包含应用程序的配置(url,第三方服务密钥等)。为此,我们使用了Typhoon库。
模型层包含域模型类、验证、映射。我们使用Mantle库来映射我们的模型:它支持序列化/反序列化到JSON格式和NSManagedObject模型。对于模型的验证和表单表示,我们使用FXForms和FXModelValidation库。
服务层声明了用于与外部系统交互的服务,以便发送或接收在域模型中表示的数据。因此,通常我们有用于与服务器api(每个实体)通信的服务、消息传递服务(如PubNub)、存储服务(如Amazon S3)等。基本上,服务包装SDK(例如PubNub SDK)提供的对象或实现它们自己的通信逻辑。对于一般的网络,我们使用AFNetworking库。
存储层的目的是组织设备上的本地数据存储。为此我们使用Core Data或Realm(两者都有优缺点,使用哪个取决于具体的规格)。对于CoreData设置,我们使用MDMCoreData库和一堆类-存储-(类似于服务),它们为每个实体提供对本地存储的访问。对于Realm,我们只是使用类似的存储来访问本地存储。
管理层是我们的抽象/包装器所在的地方。
经理角色可以是:
凭据管理器及其不同的实现(keychain, NSDefaults,…) 当前会话管理器,知道如何保持和提供当前用户会话 捕获管道,提供对媒体设备的访问(视频录制,音频,拍照) BLE管理器,提供对蓝牙服务和外围设备的访问 地理位置管理器 ...
因此,管理器的角色可以是任何实现应用程序工作所需的特定方面或关注点的逻辑的对象。
我们尽量避免singleton,但是如果需要的话,这一层是他们居住的地方。
协调器层提供依赖于其他层(服务、存储、模型)对象的对象,以便将它们的逻辑组合成特定模块(功能、屏幕、用户故事或用户体验)所需的一个工作序列。它通常链接异步操作,并知道如何对它们的成功和失败情况作出反应。作为一个例子,您可以想象一个消息传递特性和对应的MessagingCoordinator对象。处理发送消息操作可能是这样的:
验证消息(模型层) 本地保存消息(消息存储) 上传消息附件(amazon s3服务) 更新消息状态和附件url并在本地保存消息(消息存储) 将消息序列化为JSON格式(模型层) 向PubNub发布消息(PubNub服务) 更新消息状态和属性并将其保存在本地(消息存储)
在上述每一个步骤中,都会相应地处理一个错误。
UI层由以下子层组成:
视图模型 视图控制器 视图
为了避免大量的视图控制器,我们使用MVVM模式,并在视图模型中实现UI表示所需的逻辑。ViewModel通常有协调器和管理器作为依赖项。viewcontroller和某些类型的视图(例如表视图单元格)使用的viewmodel。视图控制器和视图模型之间的粘合剂是数据绑定和命令模式。为了使它有可能有胶水,我们使用ReactiveCocoa库。
我们还使用ReactiveCocoa及其RACSignal概念作为所有协调器、服务和存储方法的接口和返回值类型。这允许我们链式操作,并行或串行运行它们,以及ReactiveCocoa提供的许多其他有用的东西。
我们尝试以声明式的方式实现UI行为。数据绑定和自动布局有助于实现这一目标。
基础设施层包含应用程序工作所需的所有帮助程序、扩展和实用程序。
这种方法对我们和我们通常构建的那些类型的应用都很有效。但你应该明白,这只是一种主观的方法,应该根据具体的团队目的进行调整/改变。
希望这对你有所帮助!
你也可以在这篇文章中找到更多关于iOS开发过程的信息
因为所有iOS应用程序都是不同的,所以我认为这里有不同的方法可以考虑,但我通常是这样做的: 创建一个中央管理器(单例)类来处理所有API请求(通常命名为apiccommunicator),每个实例方法都是一个API调用。有一个中心(非公共)方法:
-(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.
推荐文章
- 更改UITextField和UITextView光标/插入符颜色
- 'Project Name'是通过优化编译的——步进可能会表现得很奇怪;变量可能不可用
- 如何设置回退按钮文本在Swift
- 模拟器慢动作动画现在打开了吗?
- 如何为TableView创建NSIndexPath
- 滑动删除和“更多”按钮(就像iOS 7的邮件应用程序)
- 如何比较两个nsdate:哪个是最近的?
- 使UINavigationBar透明
- 如何改变推和弹出动画在一个基于导航的应用程序
- 删除/重置核心数据中的所有条目?
- Swift to Objective-C头未在Xcode 6中创建
- setNeedsLayout vs. setNeedsUpdateConstraints和layoutIfNeeded vs. updateConstraintsIfNeeded
- 不区分大小写的比较
- 我怎么能得到一个uiimage的高度和宽度?
- 我如何模仿地图应用程序的底部表格?