Best architectural approaches for building iOS networking applications (REST clients)
我是一个有经验的iOS开发者,这个问题对我来说非常有趣。关于这个话题,我看到了很多不同的资源和材料,但是我还是很困惑。iOS网络化应用程序的最佳体系结构是什么?我指的是基本的抽象框架、模式,它将适合每个网络应用程序,无论是一个只有少量服务器请求的小应用程序,还是一个复杂的REST客户机。苹果建议使用
我是否需要为
我应该为所有这些网络化的东西创建另一个层,比如
我知道有很多漂亮的方法,或者像Facebook客户端或LinkedIn客户端这样的移动怪兽是如何应对网络逻辑指数级增长的复杂性的?
我知道这个问题没有确切而正式的答案。这个问题的目标是从经验丰富的iOS开发人员那里收集最有趣的方法。最好的建议方法将被标记为接受并授予声誉奖励,其他方法将被否决。这主要是一个理论和研究问题。我想了解iOS中网络应用程序的基本、抽象和正确的体系结构方法。我希望有经验的开发人员能给出详细的解释。
你说:
首先,我认为,我们应该为网络创建另一个层,因为我们不需要胖的控制器或沉重的,不知所措的模型。我不相信那些东西。但我确实相信江户一〔6〕的方法,因为从来没有一个班是胖的。所有网络一般都可以抽象为业务逻辑,因此我们应该有另一个层,我们可以把它放在那里。我们需要的是服务层:好的。
1 2 | It encapsulates the application's business logic, controlling transactions and coordinating responses in the implementation of its operations. |
在我们的
我经常使用两个库:AFNetworking 2.0和ReactiveCoCoA。我认为对于任何与网络和Web服务交互或包含复杂的UI逻辑的现代应用程序来说,这是必须具备的。
建筑学
首先,我创建了一个通用的
我想说的是,如果您有复杂的模型序列化逻辑,那么就为它创建另一个层:类似于数据映射器,但更一般,例如json/xml->model mapper。如果您有缓存:那么也将其创建为单独的层/服务(不应该将业务逻辑与缓存混合在一起)。为什么?因为正确的缓存层可能非常复杂,有自己的gotchas。人们实现复杂的逻辑来获得有效的、可预测的缓存,例如基于profuncors的带有投影的单精度缓存。你可以阅读这个叫做卡洛斯的美丽图书馆来了解更多。别忘了,核心数据确实可以帮助您解决所有缓存问题,并且允许您编写更少的逻辑。另外,如果您在
在服务层中完成所有这些操作之后,调用者(视图控制器)可以借助于
然后,每个视图控制器再次使用DI注入它需要的服务类,调用适当的服务方法,并用UI逻辑组合它们的结果。对于依赖注入,我喜欢使用血魔或者更强大的框架台风。我从来没有用过单件的,上帝的埃多克斯(31)类或其他错误的东西。因为如果您调用类
在我们的情况下,单一实例的所有权不是问题,而且在我们将上帝管理器划分为服务之后,我们不需要全局访问,因为现在只有一个或多个专用控制器需要特定的服务(例如,
我们应该始终尊重
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.
Ok.
下面是一个我的体系结构的一般工作流。假设我们有一个
1 | - (RACSignal *)removeFriend:(Friend * const)friend |
其中,
如果我们的应用程序是一个非常大的应用程序,那么我们必须更清楚地分离我们的逻辑。例如,将
A Repository represents all objects of a certain type as a conceptual set. It acts like a collection, except with more elaborate querying capability.
Ok.
因此,
1 | - (RACSignal *)approveFriendRequest:(FriendRequest * const)request; |
当然是一种业务逻辑,因为它超出了基本的
我给你描述了一个"老"的客观C示例,但是这种方法可以非常容易地适应快速语言,并且有更多的改进,因为它有更多有用的特性和功能性。我强烈推荐使用这个图书馆:莫亚。它允许您创建一个更优雅的
所以,我描述了我的一般体系结构方法,我认为它可以适用于任何应用程序。当然,还有很多改进。我建议你学习函数式编程,因为你可以从中受益良多,但也不要做得太过分。一般来说,消除过度的、共享的、全局可变的状态、创建不变的域模型或创建没有外部副作用的纯函数是一种良好的实践,新的
所以,
根据这个问题的目标,我想描述一下我们的体系结构方法。好的。架构方法
我们的一般iOS应用程序体系结构支持以下模式:服务层、MVVM、UI数据绑定、依赖项注入和功能性反应式编程范式。好的。
我们可以将典型的面向消费者的应用程序划分为以下逻辑层:好的。
- 装配
- 模型
- 服务
- 保管部
- 管理者
- 协调员
- 用户界面
- 基础设施
汇编层是我们应用程序的一个引导点。它包含依赖项注入容器和应用程序对象及其依赖项的声明。该层还可能包含应用程序的配置(URL、第三方服务密钥等)。为此,我们使用台风图书馆。好的。
模型层包含域模型类、验证和映射。我们使用mantle库来映射我们的模型:它支持序列化/反序列化为
服务层声明我们用于与外部系统交互的服务,以便发送或接收在我们的域模型中表示的数据。因此,通常我们有与服务器API(每个实体)通信的服务、消息传递服务(如pubnub)、存储服务(如amazon s3)等。基本上,服务包装sdk提供的对象(如pubnub sdk)或实现它们自己的通信逻辑。对于一般网络,我们使用AFNetworking库。好的。
存储层的目的是组织设备上的本地数据存储。我们为此使用核心数据或领域(两者都有优缺点,决定使用什么是基于具体的规范)。对于核心数据设置,我们使用MDMCoreData库和一组类-存储(类似于服务),它们为每个实体提供对本地存储的访问。对于领域,我们只使用类似的存储来访问本地存储。好的。
经理层是我们的抽象/包装器所在的地方。好的。
在经理角色中可以是:好的。
- 具有不同实现的凭据管理器(keychain、nsdefaults…)
- 知道如何保留和提供当前用户会话的当前会话管理器
- 提供访问媒体设备(视频录制、音频、拍照)的捕获管道
- BLE管理器,提供对蓝牙服务和外围设备的访问
- 地理位置经理
- …
所以,在管理器角色中,可以是实现应用程序工作所需特定方面或关注点逻辑的任何对象。好的。
我们尽量避免单身,但这一层是他们生活的地方,如果需要的话。好的。
Coordinator层提供依赖于其他层(服务、存储、模型)对象的对象,以便将它们的逻辑组合成特定模块(功能、屏幕、用户故事或用户体验)所需的一系列工作。它通常链接异步操作,并知道如何对它们的成功和失败案例做出反应。例如,您可以想象一个消息传递特性和对应的
在上述每个步骤中,都会相应地处理错误。好的。
UI层由以下子层组成:好的。
为了避免大规模视图控制器,我们使用MVM模式,并在视图中应用逻辑。一个典型的视野通常有协调人和经理作为依赖者。Viewmodels used by viewcontrollers and some kind of views(E.G.Table view cells).视图控制器和视图模型之间的胶水是数据结合和命令模式。为了使这一切成为可能,我们使用了活性生态库。
okay.
我们还使用了活性生态COA及其EDOCX1&0作为所有协调人、服务人员、存储方法的界面和返回值类型。这使我们能够同时运行或连续运行,并通过反应性生态COA提供了许多其他有用的东西。
okay.
我们试图以宣言的方式执行我们的国际联盟的行为。数据结合和自动布置有助于实现这一目标。
okay.
基础设施层包括所有地板、扩展、应用工作所需的公用事业。
okay.
这一方法对我们和我们通常建造的应用程序类型都很好。但你应该明白,这只是一个主观的方法,应该适应/改变具体的团队的目的。
okay.
希望这能帮助你!
okay.
也可以在这个博客中找到更多关于IOS开发过程的信息,作为一个服务。
okay.好吧
因为所有的iOS应用程序都是不同的,我认为这里有不同的方法可以考虑,但我通常是这样做的:创建一个中心管理器(singleton)类来处理所有API请求(通常称为apicommunicator),并且每个实例方法都是一个API调用。有一种中心(非公共)方法:
-
(RACSignal *)sendGetToServerToSubPath:(NSString *)path withParameters:(NSDictionary *)params;
作为记录,我使用了两个主要的库/框架,reactiveCocoa和afNetworking。reactiveCocoa完美地处理异步网络响应,您可以这样做(sendNext:,sendError:,等等)。此方法调用API,获取结果并以"raw"格式通过rac发送(如nsarray,afnetworking返回)。然后调用上述方法的
在我的情况下,我通常使用reskit库来设置网络层。它提供了易于使用的解析。它减少了我为不同的响应和内容设置映射的工作量。
我只添加一些代码来自动设置映射。我为我的模型定义了基类(不是协议,因为要检查某个方法是否实现的代码太多,而且模型本身的代码更少):
地图输入
1 2 3 4 5 6 7 8 9 | @interface MappableEntity : NSObject + (NSArray*)pathPatterns; + (NSArray*)keyPathes; + (NSArray*)fieldsArrayForMapping; + (NSDictionary*)fieldsDictionaryForMapping; + (NSArray*)relationships; @end |
映射输入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @implementation MappableEntity +(NSArray*)pathPatterns { return @[]; } +(NSArray*)keyPathes { return nil; } +(NSArray*)fieldsArrayForMapping { return @[]; } +(NSDictionary*)fieldsDictionaryForMapping { return @{}; } +(NSArray*)relationships { return @[]; } @end |
关系是表示嵌套对象响应的对象:
关系对象.h
1 2 3 4 5 6 7 8 9 10 | @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 |
关系对象.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @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的映射,如下所示:
对象映射初始化器.h
1 2 3 4 5 | @interface ObjectMappingInitializer : NSObject +(void)initializeRKObjectManagerMapping:(RKObjectManager*)objectManager; @end |
对象映射初始化器.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | @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实现的一些示例:
用户,h
1 2 3 4 5 6 7 8 9 10 11 12 13 | @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 |
用户,m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | @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类中的行长度:
APICLALBACK
1 2 3 4 | typedef void(^SuccessCallback)(); typedef void(^SuccessCallbackWithObjects)(NSArray *objects); typedef void(^ErrorCallback)(NSError *error); typedef void(^ProgressBlock)(float progress); |
以及我正在使用的apiRequest类的示例:
LogiAPI
1 2 3 4 5 | @interface LoginAPI : NSObject - (void)loginWithCredentials:(Credentials*)credentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError; @end |
LogiAPI
1 2 3 4 5 6 7 8 9 10 11 | @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对象,并在需要时调用它:
SomeView控制器.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | @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代码弹出一个摄像头和一些网络代码附加到一个发送按钮,上传图像到S3存储并写入simpledb域。代码很简单,成本也很低,客户机有一个可扩展的照片集合,可以通过Web通过REST调用进行访问。这个应用程序既便宜又愚蠢,有很多缺陷,有时会锁定用户界面,但如果为原型做更多的工作,那将是浪费,而且它允许用户部署到自己的员工身上,轻松生成数千个测试图像,而无需考虑性能或可伸缩性。糟糕的架构,但它完全符合需求和成本。好的。
另一个项目涉及实现本地安全数据库,当网络可用时,该数据库在后台与公司系统同步。我创建了一个后台同步器,使用restkit,因为它似乎拥有我需要的一切。但是为了处理特殊的JSON,我不得不为restkit编写大量定制代码,这样我就可以通过编写自己的JSON到coredata转换来更快地完成这一切。但是,客户希望将这个应用程序带到自己的内部,我觉得restkit将类似于他们在其他平台上使用的框架。我等着看这是不是个好决定。好的。
同样,我面临的问题是集中于需求,让它决定体系结构。我尽量避免使用第三方软件包,因为它们带来的成本只有在应用程序在该领域出现一段时间后才会出现。我尽量避免使用类层次结构,因为它们很少有回报。如果我能在一段合理的时间内写一些东西,而不是采用一个不完美的包,那么我就做了。我的代码具有良好的调试结构和适当的注释,但很少有第三方包。有了这句话,我发现自动对焦网络太有用了,不能忽视,结构良好,评论良好,维护良好,我经常使用它!restkit涵盖了很多常见的情况,但我觉得我在使用它的时候遇到了麻烦,我遇到的大多数数据源都充满了怪癖和问题,这些问题最好用自定义代码处理。在我最近的几个应用程序中,我只是使用内置的JSON转换器并编写一些实用方法。好的。
我经常使用的一种模式是从主线程获取网络调用。我所做的最后4-5个应用程序使用Dispatch_Source_Create设置了一个后台计时器任务,它每隔一段时间就会唤醒一次,并根据需要执行网络任务。您需要做一些线程安全工作,并确保将修改代码的UI发送到主线程。它也有助于以用户不会感到负担或延迟的方式进行您的入职/初始化。到目前为止,这已经相当有效。我建议调查一下这些东西。好的。
最后,我认为随着我们的工作越来越多,随着操作系统的发展,我们倾向于开发更好的解决方案。我花了好几年的时间来克服我的信念,即我必须遵循别人声称是强制性的模式和设计。如果我的工作环境是当地宗教的一部分,嗯,我指的是部门的最佳工程实践,那么我会按照惯例行事,这就是他们付钱给我的。但我很少发现遵循旧的设计和模式是最佳解决方案。我总是试图通过业务需求的棱镜来看待解决方案,构建与之匹配的架构,并尽可能保持简单。当我觉得那里不够,但一切都正常时,我就走上了正确的轨道。好的。好啊。
我使用的方法是:https://github.com/constantine-fry/foursquare-api-v2。我已经用swift重写了这个库,您可以从代码的这些部分看到架构方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | 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}} } |
基本上,有nsOperation子类可以生成nsurlRequest,解析JSON响应,并将带有结果的回调块添加到队列中。主API类构造nsurlrequest,初始化nsoperation子类并将其添加到队列中。
根据情况,我们使用一些方法。对于大多数情况,afnetworking是最简单和最可靠的方法,因为您可以设置头文件、上载多部分数据、使用get、post、put&delete,而且uikit还有一系列附加类别,允许您例如从URL设置图像。在一个有很多调用的复杂应用程序中,我们有时会将其抽象为我们自己的一个方便方法,类似于:
1 | -(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类别中。例如,如果我想创建和发布一个新用户,我会这样做:
1 2 | User* newUser = [User createInManagedObjectContext:managedObjectContext]; [newUser postOnSuccess:^(...) { ... } onFailure:^(...) { ... }]; |
我使用restkit进行对象映射,并在启动时对其进行初始化。我发现把你所有的电话通过一个单一的方式路由是浪费时间的,并且添加了很多不需要的样板文件。
在nsmanagedObject+extensions.m中:
1 2 3 4 5 | + (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中:
1 2 3 4 5 | - (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]; } |
当您可以通过类别扩展公共基类的功能时,为什么还要添加额外的助手类?
如果您对我的解决方案的详细信息感兴趣,请告诉我。我很乐意分享。
这个问题已经有很多非常好和广泛的答案,但我觉得我必须提到它,因为没有其他人。
Alamoire代表Swift。网址:https://github.com/alamofire/alamofire
它是由与AFNetworking相同的人创建的,但更直接的设计思想是迅捷的。
从纯类设计的角度来看,您通常会有这样的情况:
- 控制一个或多个视图的视图控制器
数据模型类——它实际上取决于您处理的实际不同实体的数量以及它们之间的关系。
例如,如果要以四种不同的表示形式(列表、图表、图形等)显示一个项目数组,则项目列表将有一个数据模型类,项目列表将有一个数据模型类。项目类列表将由四个视图控制器共享-选项卡栏控制器或导航控制器的所有子级。
数据模型类不仅可以方便地显示数据,还可以序列化数据,其中每个类都可以通过json/xml/csv(或任何其他)导出方法公开自己的序列化格式。
重要的是要理解,您还需要直接映射到RESTAPI端点的API请求生成器类。假设您有一个将用户登录的API,那么您的登录API构建器类将为登录API创建后JSON负载。在另一个示例中,目录项列表api的api请求生成器类将为相应的api创建get查询字符串,并触发其余get查询。
这些API请求生成器类通常会从视图控制器接收数据,并将相同的数据传回视图控制器进行UI更新/其他操作。然后,视图控制器将决定如何用该数据更新数据模型对象。
最后,REST客户机的核心——API数据获取器类,它忽略了应用程序发出的各种API请求。这个类更可能是单例,但正如其他人指出的,它不必是单例。
请注意,链接只是一个典型的实现,不考虑会话、cookie等场景,但它足以让您在不使用任何第三方框架的情况下继续工作。
尝试https://github.com/kevin0571/stnettaskqueue
在单独的类中创建API请求。
stnettaskqueue将处理线程和委托/回调。
可扩展到不同的协议。