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.


当前回答

在设计应用程序时,我避免使用单例。他们是很多人的典型选择,但我认为你可以在其他地方找到更优雅的解决方案。通常我做的是在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类呢?

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

其他回答

我认为目前中型项目使用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项目

试试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米。

@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。

@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上为它做一个通用的解决方案。

因为所有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.

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

体系结构方法

我们通用的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开发过程的信息