Mai*_*r00 314 networking web-services objective-c ios ios7
我是一名具有一定经验的iOS开发人员,这个问题对我来说非常有趣.我在这个主题上看到了很多不同的资源和材料,但是我仍然感到困惑.iOS网络应用程序的最佳架构是什么?我的意思是基本的抽象框架,模式,它适合每个网络应用程序,无论它是一个只有少量服务器请求的小应用程序还是复杂的REST客户端.Apple建议将其MVC
用作所有iOS应用程序的基本架构方法,但MVC
现在的MVVM
模式都不能解释网络逻辑代码的放置位置以及如何组织它.
我是否需要开发类似MVCS
(S
for Service
)的东西,并在此Service
层放置所有API
请求和其他网络逻辑,这在视角可能真的很复杂?在做了一些研究后,我找到了两种基本方法.这里建议为Web服务的每个网络请求API
(如LoginRequest
类或PostCommentRequest
类等)创建一个单独的类,它们都继承自基本请求抽象类AbstractBaseRequest
,另外还创建了一个封装了常见网络代码的全局网络管理器.其他首选项(可能是AFNetworking
自定义或RestKit
调优,如果我们有复杂的对象映射和持久性,甚至是使用标准API的自己的网络通信实现).但这种方法对我来说似乎是一个开销.另一种方法是在第一种方法中使用一些单例API
调度程序或管理器类,但不是为每个请求创建类,而是将每个请求封装为此管理器类的实例公共方法,如:fetchContacts
,loginUser
方法等.那么,什么是最好的和正确的方法?还有其他有趣的方法我还不知道吗?
我是否应该为所有这些网络内容创建另一个层,例如Service
,NetworkProvider
层或其他任何在我的MVC
架构之上,或者该层应该集成(注入)到现有MVC
层中,例如Model
?
我知道存在很好的方法,或者像Facebook客户端或LinkedIn客户端这样的移动怪物如何应对指数级增长的网络逻辑复杂性?
我知道这个问题没有确切而正式的答案.这个问题的目标是从经验丰富的iOS开发人员那里收集最有趣的方法.最佳建议方法将被标记为已接受并获得声誉奖励,其他方式将被赞成.这主要是一个理论和研究问题.我想了解iOS中网络应用程序的基本,抽象和正确的架构方法.我希望有经验的开发人员提供详细解释.
Ole*_*rov 319
I want to understand basic, abstract and correct architectural approach for networking applications in iOS
:构建应用程序体系结构没有 "最好的"或"最正确的"方法.这是一项非常有创意的工作.您应该始终选择最直接和可扩展的体系结构,这对于任何开始处理项目或开发团队中其他开发人员的开发人员来说都是明确的,但我同意,可能存在"好"和"坏" "建筑.
你说:collect the most interesting approaches from experienced iOS developers
我不认为我的做法是最有趣的还是正确的,但我已经用它在几个项目和满意.它是您上面提到的那种混合方法,也是我自己研究工作的改进.我对构建方法的问题感兴趣,它结合了几种众所周知的模式和习语.我认为很多福勒的企业模式都可以成功应用于移动应用程序.以下是最有趣的列表,我们可以申请创建iOS应用程序架构(在我看来):服务层,工作单元,远程门面,数据传输对象,网关,层超类型,特殊情况,域模型.您应始终正确设计模型层,并始终不要忘记持久性(它可以显着提高应用程序的性能).你可以用Core Data
它.但是你不应该忘记,这Core Data
不是一个ORM或数据库,而是一个对象图管理器,它具有持久性作为它的一个很好的选择.因此,经常Core Data
对您的需求来说太沉重,您可以查看Realm和Couchbase Lite等新解决方案,或者基于原始SQLite或LevelDB构建自己的轻量级对象映射/持久层.另外,我建议您熟悉域驱动设计和CQRS.
首先,我认为,我们应该为网络创建另一层,因为我们不需要胖控制器或沉重,不堪重负的模型.我不相信那些fat model, skinny controller
东西.但我相信在skinny everything
方法,因为不用上课应该是脂肪,永远.所有网络通常都可以抽象为业务逻辑,因此我们应该有另一层,我们可以把它放在哪里.服务层是我们需要的:
It encapsulates the application's business logic, controlling transactions
and coordinating responses in the implementation of its operations.
Run Code Online (Sandbox Code Playgroud)
在我们的MVC
领域Service Layer
中,类似于域模型和控制器之间的中介.这种方法有一种相当类似的变体,称为MVCS,其中a Store
实际上是我们的Service
层.Store
出售模型实例并处理网络,缓存等.我想提一下,你不应该在服务层编写所有的网络和业务逻辑.这也可以被认为是一个糟糕的设计.有关更多信息,请查看Anemic和Rich域模型.一些服务方法和业务逻辑可以在模型中处理,因此它将是一个"丰富"(带行为)模型.
我总是广泛使用两个库:AFNetworking 2.0和ReactiveCocoa.我认为这对于任何与网络和Web服务交互或包含复杂UI逻辑的现代应用程序都是必须的.
建筑
首先,我创建了一个通用APIClient
类,它是AFHTTPSessionManager的子类.这是应用程序中所有网络的主力:所有服务类都将实际的REST请求委托给它.它包含HTTP客户端的所有自定义,我在特定应用程序中需要它:SSL固定,错误处理和创建NSError
具有详细故障原因的简单对象以及所有API
和连接错误的描述(在这种情况下,控制器将能够显示正确的消息用户),设置请求和响应序列化程序,http标头和其他与网络相关的东西.然后,我在逻辑上将所有的API请求转换的子服务,或者更确切地说,是微服务:UserSerivces
,CommonServices
,SecurityServices
,FriendsServices
等等,因此业务逻辑它们实现.这些微服务中的每一个都是一个单独的类.他们一起组成了一个Service Layer
.这些类包含每个API请求的方法,进程域模型,并始终RACSignal
使用已解析的响应模型或NSError
调用者返回a .
我想提一下,如果你有复杂的模型序列化逻辑 - 那么为它创建另一个层:像Data Mapper,但更通用的例如JSON/XML - > Model mapper.如果你有缓存:那么也将它创建为一个单独的层/服务(你不应该将业务逻辑与缓存混合).为什么?因为正确的缓存层可能与其自身的陷阱相当复杂.人们实现复杂的逻辑以获得有效的,可预测的缓存,例如基于profunctors的投影的monoidal缓存.您可以阅读有关这个名为Carlos的美丽图书馆以了解更多信息.并且不要忘记Core Data可以真正帮助您解决所有缓存问题,并且可以让您编写更少的逻辑.此外,如果在NSManagedObjectContext
服务器请求模型之间存在某种逻辑,则可以使用存储库模式,该模式将检索数据的逻辑与从模型上的业务逻辑映射到实体模型的逻辑分开.因此,即使您拥有基于Core Data的架构,我也建议使用Repository模式.仓库可以抽象的东西,比如NSFetchRequest
,NSEntityDescription
,NSPredicate
等为普通方法,如get
或put
.
在服务层中的所有这些操作之后,调用者(视图控制器)可以使用响应执行一些复杂的异步操作:信号操作,链接,映射等,在ReactiveCocoa
原语的帮助下,或者只是订阅它并在视图中显示结果.我注入与依赖注入在所有这些服务类我APIClient
,这将特定的服务调用转化为相应的GET
,POST
,PUT
,DELETE
,等请求REST端点.在这种情况下APIClient
,隐式传递给所有控制器,您可以使用参数化的APIClient
服务类来显式化.如果您想APIClient
对特定服务类使用不同的自定义,这可能是有意义的,但如果由于某些原因,您不想要额外的副本,或者您确定始终将使用一个特定的实例(没有自定义)APIClient
- 使它成为单身人士,但请不要,请不要将服务类别作为单身人士.
然后,每个视图控制器再次使用DI注入所需的服务类,调用适当的服务方法并使用UI逻辑组合其结果.对于依赖注入,我喜欢使用BloodMagic或更强大的框架Typhoon.我从不使用单身人士,上帝APIManagerWhatever
阶级或其他错误的东西.因为如果你打电话给你的班级WhateverManager
,这表明你不知道它的目的,这是一个糟糕的设计选择.单身人士也是一种反模式,在大多数情况下(罕见的除外)是一种错误的解决方案.只有满足以下所有三个标准时,才应考虑单身人士:
在我们的案例中,单个实例的所有权不是问题,在我们将上帝管理器分成服务之后我们也不需要全局访问,因为现在只有一个或几个专用控制器需要特定服务(例如UserProfile
控制器需求UserServices
等) .
我们应该始终尊重SOLID中的S
原则并使用关注点分离,因此不要将所有服务方法和网络调用放在一个类中,因为它很疯狂,尤其是在开发大型企业应用程序时.这就是我们应该考虑依赖注入和服务方法的原因.我认为这种方法是现代的和后OO.在这种情况下,我们将应用程序分为两部分:控制逻辑(控制器和事件)和参数.
一种参数是普通的"数据"参数.这就是我们传递函数,操作,修改,持久等等.这些是实体,聚合,集合,案例类.另一种是"服务"参数.这些类封装了业务逻辑,允许与外部系统通信,提供数据访问.
以下是我的架构的一般工作流程.假设我们有一个FriendsViewController
显示用户朋友列表,我们可以选择从朋友中删除.我在我的FriendsServices
类中创建了一个名为:
- (RACSignal *)removeFriend:(Friend * const)friend
Run Code Online (Sandbox Code Playgroud)
Friend
模型/域对象在哪里(User
如果它们具有相似的属性,它可以只是一个对象).引擎盖下此方法解析Friend
到NSDictionary
JSON的参数friend_id
,name
,surname
,friend_request_id
等等.我总是将Mantle库用于这种模板和我的模型层(前后解析,在JSON中管理嵌套对象层次结构等).解析后它调用APIClient
DELETE
方法以使实际的REST请求并返回Response
在RACSignal
给调用者(FriendsViewController
在我们的例子),以供用户或任何显示适当的消息.
如果我们的应用程序是一个非常大的应用程序,我们必须更清楚地分离我们的逻辑.例如,将逻辑与逻辑混合或建模并不总是好的.当我说明我的方法,我曾说过,方法应该是在层,但如果我们将更加迂腐,我们可以看到,它更好地属于.让我们记住存储库是什么.Eric Evans在他的书[DDD]中给出了一个精确的描述:Repository
Service
removeFriend
Service
Repository
存储库将特定类型的所有对象表示为概念集.除了具有更精细的查询功能外,它的作用就像一个集合.
因此,一个Repository
本质上是采用集合式的语义(添加,更新,删除)提供访问数据/对象的外观.这就是为什么当你碰到这样的:getFriendsList
,getUserGroups
,removeFriend
你可以将其放置在Repository
,因为集合类语义是很清楚这里.代码如下:
- (RACSignal *)approveFriendRequest:(FriendRequest * const)request;
Run Code Online (Sandbox Code Playgroud)
绝对是一个商业逻辑,因为它是超越了基本的CRUD
操作和连接两个域对象(Friend
和Request
),这就是为什么它应该被放置在Service
层.另外我想注意:不要创建不必要的抽象.明智地使用所有这些方法.因为如果你将与抽象压倒你的应用程序,这将增加其偶然复杂和复杂性会导致更多的问题在软件系统比什么都重要
我向您描述了一个"旧的"Objective-C示例,但这种方法可以非常容易地适应Swift语言并进行更多改进,因为它具有更多有用的功能和功能性糖.我强烈建议使用这个库:Moya.它允许您创建一个更优雅的APIClient
层(我们的主力,你记得).现在,我们的APIClient
提供程序将是一个值类型(枚举),其扩展符合协议并利用解构模式匹配.Swift枚举+模式匹配允许我们像经典函数式编程一样创建代数数据类型.我们的微服务将使用这种改进的APIClient
提供程序,就像通常的Objective-C方法一样.对于模型层而不是Mantle
你可以使用ObjectMapper库或者我喜欢使用更优雅和功能性的Argo库.
因此,我想到了我的一般架构方法,可以适用于任何应用程序.当然,还有很多改进.我建议你学习函数式编程,因为你可以从中受益很多,但也不要太过分了.消除过度,共享,全局可变状态,创建不可变域模型或创建没有外部副作用的纯函数通常是一种很好的做法,新Swift
语言鼓励这样做.但是请记住,使用繁重的纯函数模式重载代码,类别理论方法是一个坏主意,因为其他开发人员会阅读并支持您的代码,并且他们可能会对prismatic profunctors
您的不可变项中的这类内容感到沮丧或害怕模型.同样的事情ReactiveCocoa
:RACify
你的代码不要太多,因为它真的很快就会变得难以理解,特别是对于新手来说.当它可以真正简化您的目标和逻辑时使用它.
所以,read a lot, mix, experiment, and try to pick up the best from different architectural approaches
.这是我能给你的最佳建议.
Ale*_*sky 30
根据这个问题的目标,我想描述一下我们的架构方法.
我们的一般iOS应用程序架构代表以下模式:服务层,MVVM,UI数据绑定,依赖注入 ; 和功能反应式编程范例.
我们可以将典型的面向消费者的应用程序分成以下逻辑层:
汇编层是我们应用程序的引导点.它包含一个Dependency Injection容器和应用程序对象及其依赖项的声明.该层还可能包含应用程序的配置(URL,第三方服务密钥等).为此我们使用Typhoon库.
模型层包含域模型类,验证,映射.我们使用Mantle库来映射我们的模型:它支持序列化/反序列化为JSON
格式和NSManagedObject
模型.对于我们模型的验证和表单表示,我们使用FXForms和FXModelValidation库.
服务层声明我们用于与外部系统交互的服务,以便发送或接收在我们的域模型中表示的数据.因此,通常我们有服务与服务器API(每个实体),消息服务(如PubNub),存储服务(如Amazon S3)等进行通信.基本上服务包装SDK提供的对象(例如PubNub SDK)或实现自己的通信逻辑.对于一般网络,我们使用AFNetworking库.
存储层的目的是在设备上组织本地数据存储.我们使用Core Data或Realm(两者都有利弊,根据具体规格决定使用什么).对于核心数据设置,我们使用MDMCoreData库和一堆类 - 存储 - (类似于服务),它们为每个实体提供对本地存储的访问.对于Realm,我们只使用类似的存储来访问本地存储.
Managers层是我们的抽象/包装器所在的地方.
在经理角色可以是:
因此,管理者的角色可以是实现应用程序工作所需的特定方面或关注点的逻辑的任何对象.
我们尽量避免使用Singletons,但如果需要,这层就是他们居住的地方.
协调器层提供依赖于来自其他层(服务,存储,模型)的对象的对象,以便将它们的逻辑组合成某个模块(特征,屏幕,用户故事或用户体验)所需的一系列工作.它通常链接异步操作,并知道如何对其成功和失败案例做出反应.作为示例,您可以想象消息传递功能和相应的MessagingCoordinator
对象.处理发送消息操作可能如下所示:
在上述每个步骤中,相应地处理错误.
UI层由以下子层组成:
为了避免使用Massive View控制器,我们使用MVVM模式并实现ViewModel中UI表示所需的逻辑.ViewModel通常将协调器和管理器作为依赖项.ViewControllers使用的ViewModels和某些类型的视图(例如表视图单元格).ViewControllers和ViewModels之间的粘合剂是数据绑定和命令模式.为了能够使用这种胶水,我们使用ReactiveCocoa库.
我们还使用ReactiveCocoa及其RACSignal
概念作为接口并返回所有协调器,服务和存储方法的值类型.这允许我们链接操作,并行或串行运行它们,以及ReactiveCocoa提供的许多其他有用的东西.
我们尝试以声明方式实现我们的UI行为.数据绑定和自动布局有助于实现这一目标.
Infrastructure层包含应用程序工作所需的所有帮助程序,扩展,实用程序.
这种方法适用于我们以及我们通常构建的那些类型的应用程序.但是你应该明白,这只是一种主观的方法,应该根据具体团队的目的进行调整/改变.
希望对你有帮助!
您还可以在iOS开发即服务的博客中找到有关iOS开发过程的更多信息
Ric*_*kye 18
因为所有iOS应用程序都不同,我认为这里有不同的方法可以考虑,但我通常采用这种方式:
创建一个中央管理器(单例)类来处理所有API请求(通常命名为APICommunicator),每个实例方法都是一个API调用.还有一种中心(非公开)方法:
-
(RACSignal *)sendGetToServerToSubPath:(NSString *)path withParameters:(NSDictionary *)params;
为了记录,我使用了两个主要的库/框架,ReactiveCocoa和AFNetworking.ReactiveCocoa可以完美地处理异步网络响应,您可以这样做(sendNext:,sendError:等).
此方法调用API,获取结果并以"原始"格式通过RAC发送(如NSArray AFNetworking返回的内容).
然后getStuffList:
,调用上述方法的方法订阅它的信号,将原始数据解析为对象(使用Motis之类的东西)并将对象逐个发送给调用者(getStuffList:
类似方法也返回控制器可以订阅的信号) ).
订阅控制器按subscribeNext:
块接收对象并处理它们.
我在不同的应用程序中尝试了很多方法,但是这个方法最好用,所以我最近在一些应用程序中使用它,它适用于小型和大型项目,如果需要修改某些内容,它很容易扩展和维护.
希望这有帮助,我想听听别人对我的方法的看法,也许其他人认为这可能会有所改善.
小智 8
在我的情况下,我通常使用ResKit库来设置网络层.它提供易于使用的解析.它减少了我为不同的响应和内容设置映射的努力.
我只添加一些代码来自动设置映射.我为我的模型定义了基类(不是协议,因为有很多代码可以检查是否实现了某些方法,而模型本身的代码更少):
MappableEntry.h
@interface MappableEntity : NSObject
+ (NSArray*)pathPatterns;
+ (NSArray*)keyPathes;
+ (NSArray*)fieldsArrayForMapping;
+ (NSDictionary*)fieldsDictionaryForMapping;
+ (NSArray*)relationships;
@end
Run Code Online (Sandbox Code Playgroud)
MappableEntry.m
@implementation MappableEntity
+(NSArray*)pathPatterns {
return @[];
}
+(NSArray*)keyPathes {
return nil;
}
+(NSArray*)fieldsArrayForMapping {
return @[];
}
+(NSDictionary*)fieldsDictionaryForMapping {
return @{};
}
+(NSArray*)relationships {
return @[];
}
@end
Run Code Online (Sandbox Code Playgroud)
关系是表示响应中嵌套对象的对象:
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
然后我像这样设置RestKit的映射:
ObjectMappingInitializer.h
@interface ObjectMappingInitializer : NSObject
+(void)initializeRKObjectManagerMapping:(RKObjectManager*)objectManager;
@end
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
现在关于请求包装:
我有块定义的头文件,以减少所有APIRequest类中的行长度:
APICallbacks.h
typedef void(^SuccessCallback)();
typedef void(^SuccessCallbackWithObjects)(NSArray *objects);
typedef void(^ErrorCallback)(NSError *error);
typedef void(^ProgressBlock)(float progress);
Run Code Online (Sandbox Code Playgroud)
我正在使用的APIRequest类的示例:
LoginAPI.h
@interface LoginAPI : NSObject
- (void)loginWithCredentials:(Credentials*)credentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError;
@end
Run Code Online (Sandbox Code Playgroud)
LoginAPI.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
Run Code Online (Sandbox Code Playgroud)
您需要在代码中完成所有操作,只需初始化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
Run Code Online (Sandbox Code Playgroud)
我的代码并不完美,但很容易设置一次并用于不同的项目.如果它对任何人都很有意思,我可以花一些时间在GitHub和CocoaPods上的某个地方制作一个通用的解决方案.
在我看来,所有软件架构都是由需求驱动的.如果这是出于学习或个人目的,那么决定主要目标并使其具有驱动架构.如果这是一份招聘工作,那么业务需求是至关重要的.诀窍是不要让闪亮的东西分散你对真实需求的注意力.我觉得这很难做到.在这个行业中总会出现新的闪亮的东西,其中很多都没用,但你不能总是预先告诉它.如果可以的话,专注于需要并愿意放弃糟糕的选择.
例如,我最近为本地企业制作了照片共享应用程序的快速原型.由于业务需求是做一些快速而又脏的事情,因此架构最终成为一些iOS代码,用于弹出相机和一些连接到发送按钮的网络代码,将按钮上传到S3存储并写入SimpleDB域.代码很简单,成本最低,客户端可以使用REST调用通过Web访问可扩展的照片集.廉价和愚蠢,该应用程序有很多缺陷,偶尔会锁定用户界面,但为原型做更多工作将是一种浪费,它允许他们部署到他们的员工,轻松生成数千个测试图像,没有性能或可扩展性关注.蹩脚的建筑,但它完全符合需求和成本.
另一个项目涉及实施本地安全数据库,当网络可用时,该数据库在后台与公司系统同步.我创建了一个使用RestKit的后台同步器,因为它似乎拥有我需要的一切.但我必须为RestKit编写这么多自定义代码来处理特殊的JSON,我可以通过将自己的JSON写入CoreData转换来更快地完成它.然而,客户希望将这个应用程序带入内部,我觉得RestKit将类似于他们在其他平台上使用的框架.我等着看这是不是一个好的决定.
同样,我的问题是关注需求并确定架构.我试着避免使用第三方软件包,因为它们带来的成本仅在应用程序进入该领域一段时间后才出现.我尽量避免制作类层次结构,因为它们很少得到回报.如果我能在合理的时间内写一些东西而不是采用不完美的包装,那么我就这样做.我的代码结构良好,可以进行调试并进行适当的评论,但第三方软件包很少.话虽如此,我发现AF网络太有用了,无法忽视,结构良好,评论很好,维护得很好,而且我经常使用它!RestKit涵盖了很多常见的情况,但我觉得我在使用它的时候已经陷入困境,而且我遇到的大多数数据源都充满了怪癖和问题,这些问题最好用自定义代码处理.在我的最后几个应用程序中,我只使用内置的JSON转换器并编写一些实用程序方法.
我一直使用的一种模式是从主线程中获取网络调用.我完成的最后4-5个应用程序使用dispatch_source_create设置后台计时器任务,该任务经常醒来并根据需要执行网络任务.您需要执行一些线程安全工作,并确保将UI修改代码发送到主线程.它还有助于以用户不会感到负担或延迟的方式进行入职/初始化.到目前为止,这一直很好.我建议调查这些事情.
最后,我认为随着我们的工作越来越多,随着操作系统的发展,我们倾向于开发更好的解决方案.我花了好几年的时间来克服我的信念,即我必须遵循其他人声称是强制性的模式和设计.如果我在当地宗教的一个环境中工作,咳咳,我的意思是部门最好的工程实践,那么我就跟着海关写信,那是他们付给我的.但我很少发现遵循较旧的设计和模式是最佳解决方案.我总是试图通过业务需求的棱镜来看待解决方案,并构建与之匹配的架构,并尽可能保持简单.当我觉得那里还不够,但一切正常时,那我就走在正确的轨道上.
归档时间: |
|
查看次数: |
59734 次 |
最近记录: |