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.
当前回答
在移动软件工程中,应用最广泛的是Clean Architecture + MVVM和Redux模式。
Clean Architecture + MVVM由3层组成: 域、表示、数据层。 表示层和数据存储库层依赖于域层:
Presentation Layer -> Domain Layer <- Data Repositories Layer
表示层由视图模型和视图(MVVM)组成:
Presentation Layer (MVVM) = ViewModels + Views
Domain Layer = Entities + Use Cases + Repositories Interfaces
Data Repositories Layer = Repositories Implementations + API (Network) + Persistence DB
在本文中,将对Clean Architecture + MVVM进行更详细的描述 https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3
其他回答
在移动软件工程中,应用最广泛的是Clean Architecture + MVVM和Redux模式。
Clean Architecture + MVVM由3层组成: 域、表示、数据层。 表示层和数据存储库层依赖于域层:
Presentation Layer -> Domain Layer <- Data Repositories Layer
表示层由视图模型和视图(MVVM)组成:
Presentation Layer (MVVM) = ViewModels + Views
Domain Layer = Entities + Use Cases + Repositories Interfaces
Data Repositories Layer = Repositories Implementations + API (Network) + Persistence DB
在本文中,将对Clean Architecture + MVVM进行更详细的描述 https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3
因为所有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.
在我的情况下,我通常使用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上为它做一个通用的解决方案。
我们根据具体情况使用几种方法。对于大多数事情来说,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内联,如果你正在做一个单独的调用或抽象到一个请求/响应类。
试试https://github.com/kevin0571/STNetTaskQueue
在分开的类中创建API请求。
STNetTaskQueue将处理线程和委托/回调。
可针对不同协议进行扩展。
推荐文章
- XSD和WSDL之间的区别是什么?
- Xcode构建失败“架构x86_64未定义的符号”
- 如何使用Xcode创建。ipa文件?
- 动态改变UILabel的字体大小
- 在SSH会话中查找客户端的IP地址
- registerForRemoteNotificationTypes: iOS 8.0及以上版本不支持
- 新的自动引用计数机制是如何工作的?
- 如何测试对象在Objective-C中的类?
- 在iPhone上确定用户是否启用了推送通知
- 是否有可能禁用浮动头在UITableView与UITableViewStylePlain?
- 从Cocoa应用程序执行一个终端命令
- 错误ITMS-9000:“冗余二进制文件上传。火车1.0版本已经有一个二进制版本上传。
- Swift -转换为绝对值
- Swift编译器错误:“框架模块内的非模块化头”
- 从父iOS访问容器视图控制器