首页 文章

构建iOS网络应用程序的最佳架构方法(REST客户端)

提问于
浏览
298

我仍然感到困惑 . iOS网络应用程序的最佳架构是什么?我的意思是基本的抽象框架,模式,它适合每个网络应用程序,无论它是一个只有少量服务器请求的小应用程序还是复杂的REST客户端 . Apple建议使用 MVC 作为所有iOS应用程序的基本架构方法,但是 MVC 和更现代的 MVVM 模式都不能解释网络逻辑代码的放置位置以及如何组织它 .

我是否需要开发像 MVCSS for Service )这样的 Service 层放置所有 API 请求和其他网络逻辑,这在视角上可能真的很复杂?在做了一些研究后,我找到了两种基本方法 . Here建议为Web服务 API (如 LoginRequest 类或 PostCommentRequest 类等)的每个网络请求创建一个单独的类,它们都继承自基本请求抽象类 AbstractBaseRequest ,并且除了创建一些封装的全局网络管理器之外常见的网络代码和其他首选项(如果我们有复杂的对象映射和持久性,或者甚至是使用标准API的自己的网络通信实现,它可能是 AFNetworking 自定义或 RestKit 调优) . 但这种方法对我来说似乎是一种开销 . 另一种方法是在第一种方法中使用一些单例 API 调度程序或管理器类, but not 为每个请求创建类,而是将每个请求封装为此管理器类的实例公共方法,如: fetchContactsloginUser 方法等 . 所以什么是最好和正确的方法?还有其他有趣的方法我还不知道吗?

我是否应该为所有这些网络内容创建另一个层,例如 Service ,或 NetworkProvider 层或我的 MVC 架构之上的任何内容,或者应该将此层集成(注入)到现有的 MVC 层中,例如 Model

我知道存在很好的方法,或者像Facebook客户端或LinkedIn客户端这样的移动怪物如何处理网络逻辑的指数级复杂性?

我知道这个问题没有确切而正式的答案 . The goal of this question is to collect the most interesting approaches from experienced iOS developers . 最好的建议方法将被标记为已接受并获得声誉奖励,其他方式将被赞成 . 这主要是一个理论和研究问题 . 我想了解iOS中网络应用程序的基本,抽象和正确的架构方法 . 我希望有经验的开发人员提供详细解释 .

11 回答

  • 310

    I want to understand basic, abstract and correct architectural approach for networking applications in iOS :没有用于构建应用程序体系结构的"the best"或"the most correct"方法 . 这是一项非常有创意的工作 . 您应该始终选择最直接和可扩展的体系结构,这对于任何开始处理项目或开发团队中其他开发人员的开发人员来说都是明确的,但我同意,可以有"good"和"bad"体系结构 .

    你说: collect the most interesting approaches from experienced iOS developers ,我没有在几个项目中使用它并对它感到满意 . 它是您上面提到的混合方法,也是我自己研究工作的改进 . 我对构建方法的问题很感兴趣,它结合了几种众所周知的模式和习语 . 我认为很多Fowler's enterprise patterns都可以成功应用于移动应用程序 . 这是一个最有趣的列表,我们可以申请创建一个iOS应用程序架构(在我看来):Service LayerUnit Of WorkRemote FacadeData Transfer ObjectGatewayLayer SupertypeSpecial CaseDomain Model . 你应该总是正确地设计一个模型层,并始终不要表现出色 . 你可以使用 Core Data . 但是你不应该忘记, Core Data 不是一个ORM或数据库,而是一个对象图管理器,它具有持久性作为它的一个很好的选择 . 因此,经常 Core Data 可能对您的需求来说过于沉重,您可以查看新的解决方案,例如RealmCouchbase Lite,或者基于原始SQLite或LevelDB构建您自己的轻量级对象映射/持久层 . 另外,我建议你熟悉Domain Driven DesignCQRS .

    首先,我认为,我们应该为网络创建另一层,因为我们不相信那些事物.610178_ . 但是我在 skinny everything 的方法中,因为任何阶级都不应该胖 . 所有网络通常都可以抽象为业务逻辑,因此我们应该有另一层,我们可以把它放在哪里 . Service Layer是我们需要的:

    It encapsulates the application's business logic,  controlling transactions 
    and coordinating responses in the implementation of its operations.
    

    在我们的 MVC 领域 Service Layer 就像是域模型和控制器之间的中介 . 这种方法有一个相当类似的变体叫MVCS,其中 Store 实际上是我们的 Service 层 . Store 出售模型实例并处理网络,缓存等 . 我想提一下,你不应该写所有的服务层中的网络和业务逻辑 . 这也可以被视为糟糕的设计 . 有关更多信息,请查看AnemicRich域模型 . 一些服务方法和业务逻辑可以在模型中处理,因此它将是一个"rich"(带行为)模型 .

    我总是广泛使用两个库:AFNetworking 2.0ReactiveCocoa . 我认为对于任何与网络和Web服务交互或包含复杂UI逻辑的现代应用程序来说,它都是必须的 .

    ARCHITECTURE

    首先,我创建了一个通用的 APIClient 类,它是AFHTTPSessionManager的子类 . 这是应用程序中所有网络的主力:所有服务类都将实际的REST请求委托给它 . 它包含HTTP客户端的所有自定义,我需要在特定应用程序中:SSL固定,错误处理和创建简单的 NSError 对象,详细的故障原因和所有 API 和连接错误的描述(在这种情况下控制器将能够显示正确用户的消息),设置请求和响应序列化程序,http标头和其他与网络相关的东西 . 然后我逻辑上将所有API请求划分为子服务,或者更准确地说,microservicesUserSerivcesCommonServicesSecurityServicesFriendsServices 等等,相应于它们实现的业务逻辑 . 这些微服务中的每一个都是一个单独的类 . 他们一起组成了一个 Service Layer . 这些类包含每个API请求的方法,进程域模型,并始终使用已解析的响应模型返回 RACSignal 或向调用者返回 NSError .

    我想提一下,如果你有复杂的模型序列化逻辑 - 然后为它创建另一个层:类似于Data Mapper,但更一般的例如JSON / XML - >模型映射器 . 如果你有缓存:那么也将它创建为一个单独的层/服务(你不应该将业务逻辑与缓存混合) . 为什么?因为正确的缓存层可能与其自身的陷阱相当复杂 . 人们实现复杂的逻辑以获得有效的,可预测的缓存,例如基于profunctors的投影的monoidal缓存 . 您可以阅读有关这个名为Carlos的美丽图书馆以了解更多信息 . 并且不要忘记Core Data可以真正帮助您解决所有缓存问题,并且可以让您编写更少的逻辑 . 此外,如果 NSManagedObjectContext 与服务器请求模型之间存在某种逻辑,则可以使用Repository模式,该模式分离检索数据的逻辑,并将其从作用于模型的业务逻辑映射到实体模型 . 因此,即使您拥有基于Core Data的架构,我也建议使用Repository模式 . 存储库可以抽象事物,如 NSFetchRequestNSEntityDescriptionNSPredicate 等等,以简单的方法,如 getput .

    在服务层中执行所有这些操作之后,调用者(视图控制器)可以使用响应执行一些复杂的异步操作:信号操作,链接,映射等,在 ReactiveCocoa 基元的帮助下,或者只是订阅它并在结果中显示结果视图 . 我在 APIClient 所有这些服务类中注入了Dependency Injection,它将特定的服务调用转换为对应的 GETPOSTPUTDELETE 等请求到REST endpoints . 在这种情况下, APIClient 被隐式传递给所有控制器,您可以使用参数化的 APIClient 服务类进行显式化 . 如果您想对特定服务类使用 APIClient 的不同自定义,这可能是有意义的,但如果由于某些原因,您不想要额外的副本,或者您确定始终使用一个特定实例(无需自定义) APIClient - 使它成为单身,但是DON 'T, please DON' T使服务类成为单身人士 .

    然后每个视图控制器再次使用DI注入所需的服务类,调用适当的服务方法并使用UI逻辑组合其结果 . 对于依赖注入,我喜欢使用BloodMagic或更强大的框架Typhoon . 我从不使用单身人士,上帝或其他错误的东西 . 因为如果你打电话给你的 class WhateverManager ,这表明你不知道它的目的,它是bad design choice . 单身人士也是一种反模式,在 most 案例中(罕见的除外)是一种错误的解决方案 . 只有满足以下三个条件时才应考虑单身人士:

    • 无法合理分配单个实例的所有权;

    • 延迟初始化是可取的;

    • 未另行规定全球访问权限 .

    在我们的案例中,单个实例的所有权不是问题,在我们将神经理分成服务之后我们也不需要全局访问,因为现在只有一个或几个专用控制器需要特定服务(例如 UserProfile 控制器需要 UserServices 等等)上) .

    我们应该始终尊重SOLID中的 S 原则并使用separation of concerns,所以不要疯狂,特别是如果您开发了一个大型企业应用程序 . 这就是我们应该考虑依赖的原因注射和服务方法 . 我认为这种方法是现代的和post-OO . 在这种情况下,我们将应用程序分为两部分:控制逻辑(控制器和事件)和参数 .

    一种参数是普通的“数据”参数 . 这就是我们传递函数,操作,修改,持久等等 . 这些是实体,聚合,集合,案例类 . 另一种是“服务”参数 . 这些类封装了业务逻辑,允许与外部系统通信,提供数据访问 .

    以下是我的架构的一般工作流程 . 假设我们有 FriendsViewController ,显示用户朋友列表,我们可以选择从朋友中删除 . 我在 FriendsServices 类中创建了一个名为:

    - (RACSignal *)removeFriend:(Friend * const)friend
    

    其中 Friend 是模型/域对象(如果它们具有相似的属性,它可以只是 User 对象) . Underhood此方法将 Friend 解析为 friend_id 的JSON参数 friend_idnamesurnamefriend_request_id 等等 . 我总是将Mantle库用于这种模板和我的模型层(前后解析,在JSON中管理嵌套对象层次结构等) . 解析之后,调用 APIClient DELETE 方法来发出实际的REST请求,并将 RACSignal 中的 Response 返回给调用者(在我们的例子中为 FriendsViewController ),以便为用户或其他任何内容显示相应的消息 .

    如果我们的应用程序是一个非常大的应用程序,我们必须更清楚地分离我们的逻辑 . 例如 . 将 Repository 或模型逻辑与 Service 一个混合并不总是好的 . 当我描述我的方法时,我说 removeFriend 方法应该在 Service 层,但如果我们会更迂腐,我们可以注意到它更好地属于 Repository . 让我们记住存储库是什么 . Eric Evans在他的书[DDD]中给出了一个精确的描述:

    存储库将特定类型的所有对象表示为概念集 . 它的作用就像一个集合,除了更精细的查询功能 .

    因此, Repository 本质上是一个使用集合样式语义(添加,更新,删除)来提供对数据/对象的访问的外观 . 这就是为什么你有这样的东西: getFriendsListgetUserGroupsremoveFriend 你可以把它放在 Repository 中,因为类似集合的语义在这里很清楚 . 代码如下:

    - (RACSignal *)approveFriendRequest:(FriendRequest * const)request;
    

    绝对是一个业务逻辑,因为它超出了基本的 CRUD 操作并连接了两个域对象( FriendRequest ),这就是为什么它应该放在 Service 层中 . 另外我想注意: don't create unnecessary abstractions . 明智地使用所有这些方法 . 因为如果你用抽象的方式压倒你的应用程序,这将增加其在软件系统中的偶然复杂性和复杂性causes more problems

    我向你描述了一个"old" Objective-C示例,但是这种方法非常容易适应Swift语言并且有很多改进,因为它具有更多有用的功能和功能性糖 . 我强烈建议使用这个库:Moya . 它允许您创建一个更优雅的 APIClient 层(我们的主力,你记得) . 现在我们的 APIClient 提供程序将是一个值类型(枚举),其扩展符合协议并利用解构模式匹配 . Swift枚举模式匹配允许我们像经典函数式编程一样创建algebraic data types . 我们的微服务将使用这种改进的 APIClient 提供程序,就像通常的Objective-C方法一样 . 对于模型层而不是 Mantle ,您可以使用ObjectMapper library或者我喜欢使用更优雅和功能更强的Argo库 .

    所以,我想了解我的一般架构方法,可以适用于任何应用程序 . 当然,还有很多改进 . 我建议你学习函数式编程,因为你可以从中受益很多,但也不要太过分了 . 消除过度,共享,全局可变状态,创建immutable domain model或创建没有外部副作用的纯函数通常是一种很好的做法,并且新的语言鼓励这样做 . 但总是记住,使用繁重的纯函数模式重载代码,类别理论方法是一个坏主意,因为其他开发人员会阅读并支持您的代码,并且他们可能会感到沮丧或对您的这些东西感到沮丧和恐惧不可变模型 . 与 ReactiveCocoa 相同:不要 RACify 你的代码too much,因为它真的很快就会变得难以理解,特别是对于新手来说 . 当它可以真正简化您的目标和逻辑时使用它 .

    所以, read a lot, mix, experiment, and try to pick up the best from different architectural approaches . 这是我能给你的最佳建议 .

  • 0

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

    架构方法

    我们的一般iOS应用程序架构代表以下模式:Service layersMVVMUI Data BindingDependency Injection;和Functional Reactive Programming范例 .

    我们可以将典型的面向消费者的应用程序分成以下逻辑层:

    • 部件

    • 型号

    • 服务

    • 存储

    • 经理

    • 协调员

    • UI

    • 基础设施

    Assembly layer 是我们应用程序的引导点 . 它包含一个依赖注入容器和应用程序对象及其依赖项的声明 . 该层还可能包含应用程序的配置(URL,第三方服务密钥等) . 为此,我们使用Typhoon库 .

    Model layer 包含域模型类,验证,映射 . 我们使用Mantle库来映射我们的模型:它支持序列化/反序列化为 JSON 格式和 NSManagedObject 模型 . 对于模型的验证和表单表示,我们使用FXFormsFXModelValidation库 .

    Services layer 声明我们用于与外部系统交互的服务,以便发送或接收在我们的域模型中表示的数据 . 因此,通常我们有服务与服务器API(每个实体),消息服务(如PubNub),存储服务(如Amazon S3)等进行通信 . 基本上服务包装SDK提供的对象(例如PubNub SDK)或实现自己的通信逻辑 . 对于一般网络,我们使用AFNetworking库 .

    Storage layer 的目的是在设备上组织本地数据存储 . 我们使用Core Data或Realm(两者都有利弊,根据具体规格决定使用什么) . 对于核心数据设置,我们使用MDMCoreData库和一堆类 - 存储 - (类似于服务),它们为每个实体提供对本地存储的访问 . 对于Realm,我们只使用类似的存储来访问本地存储 .

    Managers layer 是我们抽象/包装的地方 .

    经理角色可以是:

    • 凭据管理器及其不同的实现(keychain,NSDefaults,...)

    • 当前会话管理器,它知道如何保留和提供当前用户会话

    • 捕获管道,提供对媒体设备的访问(视频录制,音频,拍照)

    • BLE Manager,提供对蓝牙服务和外围设备的访问

    • 地理位置管理员

    • ......

    因此,管理者的角色可以是实现应用程序工作所需的特定方面或关注点的逻辑的任何对象 .

    我们尽量避免使用Singletons,但如果需要,这层就是他们居住的地方 .

    Coordinators layer 提供依赖于来自其他层(服务,存储,模型)的对象的对象,以便将它们的逻辑组合成某个模块(特征,屏幕,用户故事或用户体验)所需的一系列工作 . 它通常链接异步操作,并知道如何对其成功和失败案例做出反应 . 作为示例,您可以想象消息传递功能和相应的 MessagingCoordinator 对象 . 处理发送消息操作可能如下所示:

    • 验证消息(模型层)

    • 本地保存消息(消息存储)

    • 上传邮件附件(亚马逊s3服务)

    • 更新邮件状态和附件网址并在本地保存邮件(邮件存储)

    • 将消息序列化为JSON格式(模型层)

    • 将消息发布到PubNub(PubNub服务)

    • 更新消息状态和属性并将其保存在本地(消息存储)

    在上述每个步骤中,相应地处理错误 .

    UI layer 由以下子图层组成:

    • ViewModels

    • ViewControllers

    • 观点

    为了避免使用Massive View控制器,我们使用MVVM模式并实现ViewModel中UI表示所需的逻辑 . ViewModel通常将协调器和管理器作为依赖项 . ViewControllers使用的ViewModels和某些类型的视图(例如表格视图单元格) . ViewControllers和ViewModels之间的 Binders 是数据绑定和命令模式 . 为了能够使用这种胶水,我们使用ReactiveCocoa库 .

    我们还使用ReactiveCocoa及其 RACSignal 概念作为接口并返回所有协调器,服务和存储方法的值类型 . 这允许我们链接操作,并行或串行运行它们,以及ReactiveCocoa提供的许多其他有用的东西 .

    我们尝试以声明方式实现我们的UI行为 . 数据绑定和自动布局有助于实现这一目标 .

    Infrastructure layer 包含应用程序工作所需的所有帮助程序,扩展,实用程序 .


    这种方法适用于我们和我们通常构建的那些类型的应用程序 . 但是你应该明白,这只是一种主观的方法,可以根据具体团队的目的进行调整/改变 .

    希望对你有帮助!

    您也可以在此博客文章中找到有关iOS开发过程的更多信息iOS Development as a Service

  • 1

    由于所有iOS应用程序都不同,我认为这里有不同的方法可以考虑,但我通常会这样:
    创建一个中央管理器(单例)类来处理所有API请求(通常名为APICommunicator),每个实例方法都是一个API调用 . 并且有一种中央(非公开)方法:

    • (RACSignal*)sendGetToServerToSubPath:(NSString *)path withParameters:(NSDictionary *)params;

    为了记录,我使用了两个主要的库/框架,ReactiveCocoa和AFNetworking . ReactiveCocoa可以完美地处理异步网络响应,您可以这样做(sendNext:,sendError:等) .
    此方法调用API,获取结果并通过RAC以'raw'格式发送(如NSArray AFNetworking返回的内容) .
    然后像 getStuffList: 这样调用上述方法的方法订阅它的信号,将原始数据解析为对象(类似于Motis)并将对象逐个发送给调用者( getStuffList: 和类似方法也返回控制器可以发出的信号)订阅) .
    订阅控制器通过 subscribeNext: 的块接收对象并处理它们 .

    我在不同的应用程序中尝试了很多方法,但是这个方法效果最好,所以如果需要修改某些内容,我很容易扩展和维护 .
    希望这有帮助,我对我的方法有所了解,也许其他人认为这可能会有所改善 .

  • 17

    在我的情况下,我通常使用ResKit库来设置网络层 . 它提供易于使用的解析 . 它减少了我为不同的响应和内容设置映射的工作量 .

    我只添加一些代码来自动设置映射 . 我为我的模型定义了基类(不是协议,因为有很多代码可以检查是否实现了某些方法,并且模型本身的代码更少):

    MappableEntry.h

    @interface MappableEntity : NSObject
    
    + (NSArray*)pathPatterns;
    + (NSArray*)keyPathes;
    + (NSArray*)fieldsArrayForMapping;
    + (NSDictionary*)fieldsDictionaryForMapping;
    + (NSArray*)relationships;
    
    @end
    

    MappableEntry.m

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

    您需要在代码中完成所有操作,只需初始化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上为它做一个通用的解决方案 .

  • 29

    在我看来,所有软件架构都是由需求驱动的 . 如果这是出于学习或个人目的,那么决定主要目标并使其具有驱动架构的能力 . 如果这是一份招聘工作,那么业务需求是至关重要的 . 诀窍是不要让闪亮的东西分散你对真实需求的注意力 . 我发现这很难做到 . 在这个行业中总会出现新的闪亮的东西,其中很多都没有用,但你不能总是预先告诉它 . 如果可以的话,专注于需要并愿意放弃糟糕的选择 .

    例如,我最近为本地企业制作了照片共享应用程序的快速原型 . 由于业务需求是做一些快速和肮脏的事情,因此架构最终成为一些iOS代码,用于弹出相机和一些连接到发送按钮的网络代码,将图像上传到S3存储并写入SimpleDB域 . 代码很简单,成本最低,客户端可以使用REST调用通过Web访问可扩展的照片集 . 廉价和愚蠢,该应用程序有很多缺陷,有时会锁定用户界面,但为原型做更多事情是浪费,它允许他们部署到他们的员工,轻松生成数千个测试图像,没有性能或可扩展性关注 . 蹩脚的建筑,但它完全符合需求和成本 .

    另一个项目涉及实施本地安全数据库,当网络可用时,该数据库在后台与公司系统同步 . 我创建了一个使用RestKit的后台同步器,因为它似乎拥有我需要的一切 . 但我必须为RestKit编写如此多的自定义代码来处理特殊的JSON,我可以通过将自己的JSON写入CoreData转换来更快地完成它 . 然而,客户希望将这个应用程序带入内部,我觉得RestKit将类似于他们在其他平台上使用的框架 . 我等着看这是不是一个好的决定 .

    同样,对我来说问题是关注需求并确定架构 . 我试着避免使用第三方软件包,因为它们带来的成本仅在应用程序进入该领域一段时间后才出现 . 我尽量避免制作类层次结构,因为它们很少得到回报 . 如果我能在合理的时间内写一些东西而不是采用不完美的包装,那么我就这样做 . 我的代码结构良好,用于调试和适当的评论,但第三方软件包很少 . 话虽如此,我发现AF网络太有用了,无法忽视,结构良好,评论很好,并且维护得很好,而且我使用它很多! RestKit涵盖了很多常见的情况,但我觉得我在使用它时已经陷入困境,而且我遇到的大多数数据源都充满了怪癖和问题,这些问题最好用自定义代码处理 . 在我的最后几个应用程序中,我只使用内置的JSON转换器并编写一些实用程序方法 .

    我一直使用的一种模式是从主线程中获取网络调用 . 我完成的最后4-5个应用程序使用dispatch_source_create设置后台计时器任务,该任务经常唤醒并根据需要执行网络任务 . 您需要执行一些线程安全工作,并确保将UI修改代码发送到主线程 . 它还有助于以用户不会感到负担或延迟的方式进行入职/初始化 . 到目前为止,这一直很好 . 我建议调查这些事情 .

    最后,我认为随着我们的工作越来越多,随着操作系统的发展,我们倾向于开发更好的解决方案 . 我花了好几年时间才克服自己的信念,即我必须遵循其他人声称必须遵守的模式和设计 . 如果我在当地宗教的一部分工作,咳咳,我的意思是部门最好的工程实践,那么我就遵循海关的信,这就是他们付给我的 . 但我很少发现遵循较旧的设计和模式是最佳解决方案 . 我总是试图通过业务需求的棱镜来看待解决方案,并构建与之匹配的架构,并尽可能保持简单 . 当我觉得那里还不够,但一切正常时,那我就走在正确的轨道上 .

  • 0

    我使用的方法是从这里得到的:https://github.com/Constantine-Fry/Foursquare-API-v2 . 我在Swift中重写了这个库,你可以从代码的这些部分看到架构方法:

    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子类并将其添加到队列中 .

  • 0

    我们根据具体情况采用一些方法 . 对于大多数事情来说,AFNetworking是最简单和最强大的方法,你可以设置 Headers ,上传多部分数据,使用GET,POST,PUT和DELETE,还有一堆额外的UIKit类别允许你设置一个图像来自一个网址 . 在一个有很多调用的复杂应用程序中,我们有时会将它抽象为我们自己的方便方法,如下所示:

    -(void)makeRequestToUrl:(NSURL *)url withParameters:(NSDictionary *)parameters success:(void (^)(id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
    

    在某些情况下,AFNetworking不合适,例如您在创建框架或其他库组件的位置,因为AFNetworking可能已经在另一个代码库中 . 在这种情况下,如果要进行单个调用或将其抽象为请求/响应类,则可以使用内联NSMutableURLRequest .

  • 4

    我在设计应用程序时避免使用单例 . 它们是很多人的典型代表,但我认为你可以在其他地方找到更优雅的解决方案 . 通常我所做的是在CoreData中构建我的实体,然后将我的REST代码放在NSManagedObject类别中 . 例如,如果我想创建并发布一个新用户,我会这样做:

    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];
    }
    

    为什么在可以通过类别扩展公共基类的功能时添加额外的辅助类?

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

  • 7

    试试https://github.com/kevin0571/STNetTaskQueue

    在分离的类中创建API请求 .

    STNetTaskQueue将处理线程和委托/回调 .

    可扩展用于不同的协议 .

  • 6

    从纯粹的类设计角度来看,您通常会遇到以下情况:

    • 您的 view controllers 控制一个或多个视图

    • Data model class - 这实际上取决于您正在处理多少真正不同的实体,以及它们之间的关系 .

    例如,如果要以四种不同的表示形式显示项目数组(列表,图表,图形等),则将为项目列表提供一个数据模型类,为项目提供一个数据模型类 . 项目类列表将由四个视图控制器共享 - 标签栏控制器或导航控制器的所有子项 .

    数据模型类不仅可以显示数据,还可以序列化它们,其中每个类都可以通过JSON / XML / CSV(或其他任何)导出方法公开自己的序列化格式 .

    • 重要的是要了解您还需要直接映射到REST API endpoints 的 API request builder classes . 假设您有一个记录用户的API - 因此您的Login API构建器类将为登录api创建POST JSON有效负载 . 在另一个示例中,API请求构建器类用于列表目录项API将为相应的api创建GET查询字符串并触发REST GET查询 .

    这些API请求构建器类通常会从视图控制器接收数据,并将相同的数据传递回视图控制器以进行UI更新/其他操作 . 然后,视图控制器将决定如何使用该数据更新数据模型对象 .

    请注意,链接只是一个典型的实现,并没有考虑会话,cookie等方案,但它足以让您不使用任何第三方框架 .

  • 3

    这个问题已经有很多优秀和广泛的答案,但我觉得我必须提到它,因为没有其他人有 .

    Alamofire for Swift . https://github.com/Alamofire/Alamofire

    它由与AFNetworking相同的人创建,但更直接地设计了Swift .

相关问题