330 votes

Meilleures approches architecturales pour la création d'applications réseau iOS (clients REST)

Je suis un développeur iOS avec une certaine expérience et cette question m'intéresse vraiment. J'ai vu beaucoup de ressources et de matériaux différents sur ce sujet, mais néanmoins je suis toujours confus. Quelle est la meilleure architecture pour une application iOS en réseau ? Je veux dire un cadre abstrait de base, des modèles, qui conviendront à chaque application en réseau, qu'il s'agisse d'une petite application qui n'a que quelques demandes de serveur ou d'un client REST complexe. Apple recommande d'utiliser MVC comme une approche architecturale de base pour toutes les applications iOS, mais ni l'un ni l'autre n'a été retenu. MVC ni le plus moderne MVVM Les schémas expliquent où placer le code logique du réseau et comment l'organiser en général.

Dois-je développer quelque chose comme MVCS ( S para Service ) et dans ce Service couche mettre tous API et d'autres logiques de mise en réseau, ce qui, en perspective, peut être vraiment complexe ? Après avoir fait quelques recherches, j'ai trouvé deux approches de base pour cela. Ici il a été recommandé de créer une classe distincte pour chaque requête réseau au service web API (comme LoginRequest ou PostCommentRequest et ainsi de suite) qui héritent toutes de la classe abstraite de requête de base AbstractBaseRequest et en plus de créer un gestionnaire de réseau global qui encapsule le code de réseau commun et d'autres préférences (il peut s'agir de AFNetworking personnalisation ou RestKit l'accord, si nous avons des mappages d'objets et une persistance complexes, ou même une propre mise en œuvre de communication réseau avec API standard). Mais cette approche me semble être une surcharge. Une autre approche consiste à avoir un singleton API classe de distributeur ou de gestionnaire comme dans la première approche, mais pas de créer des classes pour chaque demande et d'encapsuler chaque demande comme une méthode publique d'instance de cette classe de gestionnaire, comme par exemple : fetchContacts , loginUser méthodes, etc. Alors, quelle est la meilleure et la plus correcte des méthodes ? Existe-t-il d'autres approches intéressantes que je ne connais pas encore ?

Et dois-je créer une autre couche pour tous ces trucs de réseau comme Service o NetworkProvider couche ou autre sur le dessus de mon MVC ou cette couche devrait être intégrée (injectée) dans les systèmes de gestion de l'information existants. MVC couches, par exemple Model ?

Je sais qu'il existe de belles approches, mais comment des monstres mobiles comme le client Facebook ou le client LinkedIn peuvent-ils gérer la complexité croissante de la logique de mise en réseau ?

Je sais qu'il n'y a pas de réponse exacte et formelle à ce problème. L'objectif de cette question est de recueillir les approches les plus intéressantes de développeurs iOS expérimentés. . La meilleure approche suggérée sera marquée comme acceptée et récompensée par une prime de réputation, les autres seront mises en avant. Il s'agit principalement d'une question théorique et de recherche. Je veux comprendre l'approche architecturale de base, abstraite et correcte pour les applications réseau dans iOS. J'espère une explication détaillée de la part de développeurs expérimentés.

16 votes

N'est-ce pas une question de "liste de courses" ? Je viens d'avoir une question qui a été rejetée et fermée parce qu'il a été dit que les questions du type "quel est le meilleur" suscitent trop de débats non constructifs. Qu'est-ce qui fait de cette question de liste de courses une bonne question digne de votes positifs et d'une prime alors que d'autres sont fermées ?

1 votes

En général, la logique de réseau est intégrée au contrôleur, qui modifie un objet de modèle et notifie les délégués ou les observateurs.

1 votes

Des questions et des réponses très intéressantes. Après 4 ans de codage iOS, et en essayant de trouver la plus belle façon d'ajouter une couche réseau à l'application. Quelle classe devrait avoir la responsabilité de gérer une requête réseau ? Les réponses ci-dessous sont vraiment pertinentes. Merci

336voto

Je veux comprendre l'approche architecturale de base, abstraite et correcte des applications de mise en réseau dans iOS.

Il y a pas de "la meilleure", ou "la plus correcte" approche pour construire une architecture d'application. Il s'agit d'une très emploi créatif. Vous devez toujours choisir l'architecture la plus simple et la plus extensible, qui sera claire pour tout développeur qui commence à travailler sur votre projet ou pour les autres développeurs de votre équipe, mais je suis d'accord qu'il peut y avoir une "bonne" et une "mauvaise" architecture.

Tu as dit :

recueillir les approches les plus intéressantes de développeurs iOS expérimentés

Je ne pense pas que mon approche soit la plus intéressante ou la plus correcte, mais je l'ai utilisée dans plusieurs projets et j'en suis satisfait. Il s'agit d'une approche hybride de celles que vous avez mentionnées ci-dessus, et aussi d'améliorations issues de mes propres efforts de recherche. Je m'intéresse aux problèmes de construction d'approches qui combinent plusieurs modèles et idiomes bien connus. Je pense que beaucoup de Les modèles d'entreprise de Fowler peut être appliquée avec succès aux applications mobiles. Voici une liste des plus intéressantes, que nous pouvons appliquer pour créer une architecture d'application iOS ( à mon avis ) : Couche de service , Unité de travail , Façade distante , Objet de transfert de données , Passerelle , Supertype de couche , Cas particulier , Modèle de domaine . Vous devez toujours concevoir correctement une couche de modèle et ne pas oublier la persistance (elle peut augmenter considérablement les performances de votre application). Vous pouvez utiliser Core Data pour ça. Mais vous ne devrait pas oublier, que Core Data n'est pas un ORM ou une base de données, mais un gestionnaire de graphes d'objets dont la persistance est une bonne option. Ainsi, très souvent Core Data peuvent être trop lourdes pour vos besoins et vous pouvez envisager de nouvelles solutions comme le Realm y Couchbase Lite Vous pouvez également créer votre propre couche légère de mappage et de persistance des objets, basée sur des données SQLite brutes ou des données de base. LevelDB . Je vous conseille également de vous familiariser avec les Conception pilotée par le domaine y CQRS .

Au début, je pense, nous devrait créer une autre couche pour la mise en réseau, car nous ne voulons pas de gros contrôleurs ou de modèles lourds et surchargés. Je ne crois pas en ces fat model, skinny controller choses. Mais je croit en skinny everything parce qu'aucune classe ne devrait être grosse, jamais. Toute la mise en réseau peut être généralement abstraite en tant que logique d'entreprise, par conséquent nous devrions avoir une autre couche, où nous pouvons la mettre. Couche de service est ce dont nous avons besoin :

Il encapsule la logique métier de l'application, contrôlant les transactions et coordonnant les réponses dans la mise en œuvre de ses opérations.

Dans notre MVC domaine Service Layer est une sorte de médiateur entre le modèle de domaine et les contrôleurs. Il existe une variante assez similaire de cette approche appelée MVCS où un Store est en fait notre Service couche. Store vend des instances de modèles et gère la mise en réseau, la mise en cache, etc. Je tiens à mentionner que vous ne devrait pas écrire toute votre mise en réseau et votre logique commerciale dans votre couche service. Cela peut également être considéré comme une mauvaise conception. Pour plus d'informations, consultez le Anémique y Rich les modèles de domaine. Certaines méthodes de service et la logique commerciale peuvent être traitées dans le modèle, il s'agira donc d'un modèle "riche" (avec comportement).

J'utilise toujours de manière extensive deux bibliothèques : AFNetworking 2.0 y ReactiveCocoa . Je pense que c'est un doit avoir pour toute application moderne qui interagit avec le réseau et les services web ou qui contient une logique d'interface utilisateur complexe.

ARCHITECTURE

Dans un premier temps, je crée un APIClient qui est une sous-classe de AFHTTPSessionManager . C'est le cheval de bataille de tous les réseaux de l'application : toutes les classes de service lui délèguent les demandes REST réelles. Il contient toutes les personnalisations du client HTTP, dont j'ai besoin dans cette application particulière : Le pinning SSL, le traitement des erreurs et la création de liens simples. NSError avec les raisons détaillées de l'échec et les descriptions de tous les API et les erreurs de connexion (dans ce cas, le contrôleur sera en mesure d'afficher des messages corrects pour l'utilisateur), en définissant les sérialiseurs de demande et de réponse, les en-têtes http et d'autres éléments liés au réseau. Ensuite, je divise logiquement toutes les demandes d'API en sous-services ou, plus exactement, microservices : UserSerivces , CommonServices , SecurityServices , FriendsServices et ainsi de suite, en fonction de la logique commerciale qu'ils mettent en œuvre. Chacun de ces microservices est une classe distincte. Ensemble, ils forment un Service Layer . Ces classes contiennent des méthodes pour chaque demande d'API, traitent les modèles de domaine et renvoient toujours un message de type RACSignal avec le modèle de réponse analysé ou NSError à l'appelant.

Je tiens à mentionner que si vous avez une logique complexe de sérialisation de modèle - alors créez une autre couche pour cela : quelque chose comme Mappage de données mais plus général, par exemple JSON/XML -> mappeur de modèle. Si vous disposez d'un cache, créez-le également en tant que couche/service distinct (vous ne devez pas mélanger la logique commerciale avec le cache). Pourquoi ? Parce qu'une couche de mise en cache correcte peut être assez complexe avec ses propres problèmes. Les gens mettent en œuvre une logique complexe pour obtenir une mise en cache valide et prévisible, comme par exemple la mise en cache monoidale avec des projections basées sur des profuncteurs. Vous pouvez vous renseigner sur cette magnifique bibliothèque appelée Carlos pour en savoir plus. Et n'oubliez pas que Core Data peut vraiment vous aider à résoudre tous les problèmes de mise en cache et vous permettra d'écrire moins de logique. De plus, si vous avez une logique entre NSManagedObjectContext et les modèles de demandes de serveur, vous pouvez utiliser Référentiel qui sépare la logique qui récupère les données et les mappe sur le modèle d'entité de la logique métier qui agit sur le modèle. Ainsi, je conseille d'utiliser le pattern Repository même lorsque vous avez une architecture basée sur Core Data. Repository peut abstraire des choses, comme NSFetchRequest , NSEntityDescription , NSPredicate et ainsi de suite jusqu'aux méthodes simples comme get o put .

Après toutes ces actions dans la couche Service, l'appelant (contrôleur de vue) peut faire des choses asynchrones complexes avec la réponse : manipulations de signaux, chaînage, mappage, etc. à l'aide de la fonction ReactiveCocoa ou simplement s'y abonner et afficher les résultats dans la vue. J'injecte avec le Injection de dépendances dans toutes ces classes de service mon APIClient qui traduira un appel de service particulier en un service correspondant. GET , POST , PUT , DELETE etc. au point de terminaison REST. Dans ce cas APIClient est passé implicitement à tous les contrôleurs, vous pouvez le rendre explicite avec un paramètre sur APIClient les classes de service. Cela peut s'avérer utile si vous souhaitez utiliser différentes personnalisations de la classe de service APIClient pour des classes de services particulières, mais si, pour certaines raisons, vous ne voulez pas de copies supplémentaires ou si vous êtes sûr de toujours utiliser une instance particulière (sans personnalisation) de l'interface utilisateur de l'interface utilisateur. APIClient - faites-en un singleton, mais NE FAITES PAS, s'il vous plaît, NE FAITES PAS de classes de service des singletons.

Ensuite, chaque contrôleur de vue, à nouveau avec le DI, injecte la classe de service dont il a besoin, appelle les méthodes de service appropriées et compose leurs résultats avec la logique de l'IU. Pour l'injection de dépendances, j'aime utiliser BloodMagic ou un cadre plus puissant Typhon . Je n'utilise jamais de singletons, Dieu APIManagerWhatever ou d'autres mauvaises choses. Parce que si vous appelez votre classe WhateverManager cela indique que vous ne connaissez pas son utilité et que c'est un mauvais choix de conception . Les singletons sont également un anti-modèle, et en le plus cas (sauf rares) est un mauvais solution. Le singleton ne doit être envisagé que si les trois critères suivants sont satisfaits :

  1. La propriété de l'instance unique ne peut être raisonnablement attribuée ;
  2. Une initialisation paresseuse est souhaitable ;
  3. L'accès global n'est pas prévu par ailleurs.

Dans notre cas, la propriété de l'instance unique n'est pas un problème et nous n'avons pas non plus besoin d'un accès global après avoir divisé notre gestionnaire de dieu en services, car maintenant, seuls un ou plusieurs contrôleurs dédiés ont besoin d'un service particulier (par ex. UserProfile besoins du contrôleur UserServices et ainsi de suite).

Nous devons toujours respecter S principe dans SOLIDE et utiliser séparation des préoccupations Ne mettez donc pas toutes vos méthodes de service et vos appels de réseaux dans une seule classe, car c'est de la folie, surtout si vous développez une grande application d'entreprise. C'est pourquoi nous devrions envisager l'injection de dépendances et l'approche des services. Je considère cette approche comme moderne et post-OO . Dans ce cas, nous divisons notre application en deux parties : la logique de contrôle (contrôleurs et événements) et les paramètres.

Un type de paramètres serait les paramètres ordinaires de "données". C'est ce que nous passons autour des fonctions, manipulons, modifions, persistons, etc. Ce sont des entités, des agrégats, des collections, des classes de cas. L'autre type de paramètres est celui des paramètres de "service". Ce sont des classes qui encapsulent la logique métier, permettent de communiquer avec des systèmes externes, fournissent un accès aux données.

Voici par exemple un flux de travail général de mon architecture. Supposons que nous ayons un FriendsViewController qui affiche la liste des amis de l'utilisateur et nous avons une option pour les supprimer. Je crée une méthode dans mon FriendsServices classe appelée :

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

donde Friend est un objet de modèle/domaine (ou peut être simplement un User s'ils ont des attributs similaires). En dessous, cette méthode analyse Friend a NSDictionary de paramètres JSON friend_id , name , surname , friend_request_id et ainsi de suite. J'utilise toujours Manteau pour ce genre de gabarit et pour ma couche modèle (analyse syntaxique en amont et en aval, gestion des hiérarchies d'objets imbriqués en JSON, etc.) Après l'analyse, il appelle APIClient DELETE pour effectuer une demande REST réelle et renvoie la méthode Response en RACSignal à l'appelant ( FriendsViewController dans notre cas) pour afficher un message approprié pour l'utilisateur ou autre.

Si notre application est très grande, nous devons séparer notre logique de manière encore plus claire. Par exemple, il n'est pas toujours bon de mélanger la logique du `Repository` ou du modèle avec celle du `Service`. Quand j'ai décrit mon approche, j'ai dit que la méthode `removeFriend` devrait être dans la couche `Service`, mais si nous sommes plus pédants, nous pouvons remarquer qu'elle appartient plutôt au `Repository`. Rappelons-nous ce qu'est un Repository. Eric Evans en a donné une description précise dans son livre [DDD] :

Un référentiel représente tous les objets d'un certain type comme un ensemble conceptuel. Il agit comme une collection, mais avec une capacité d'interrogation plus élaborée.

Donc, un Repository est essentiellement une façade qui utilise la sémantique du style Collection (Add, Update, Remove) pour fournir un accès aux données/objets. C'est pourquoi lorsque vous avez quelque chose comme : getFriendsList , getUserGroups , removeFriend vous pouvez le placer dans le Repository car la sémantique des collections est assez claire ici. Et du code comme :

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

est définitivement une logique d'entreprise, parce qu'elle va au-delà de l'élémentaire CRUD et de connecter deux objets de domaine ( Friend y Request ), c'est pourquoi il doit être placé dans la section Service couche. Je veux aussi remarquer : ne pas créer d'abstractions inutiles . Utilisez toutes ces approches à bon escient. Parce que si vous submergez votre application avec des abstractions, cela va augmentation de sa complexité accidentelle, et la complexité cause plus de problèmes dans les systèmes logiciels que tout autre chose

Je vous décris un "vieil" exemple Objective-C mais cette approche peut être très facilement adaptée au langage Swift avec beaucoup plus d'améliorations, car il a plus de fonctionnalités utiles et de sucre fonctionnel. Je vous recommande vivement d'utiliser cette bibliothèque : Moya . Il vous permet de créer une APIClient couche (notre cheval de bataille, comme vous vous en souvenez). Maintenant, notre APIClient Le fournisseur sera un type de valeur (enum) avec des extensions conformes aux protocoles et tirant parti du filtrage par déstructuration. Les enums Swift + le pattern matching nous permettent de créer des types de données algébriques comme dans la programmation fonctionnelle classique. Nos microservices utiliseront cette APIClient comme dans l'approche Objective-C habituelle. Pour la couche modèle, au lieu de Mantle vous pouvez utiliser Bibliothèque ObjectMapper ou j'aime utiliser un système plus élégant et fonctionnel Argo bibliothèque.

J'ai donc décrit mon approche architecturale générale, qui peut être adaptée à n'importe quelle application, je pense. Il peut y avoir beaucoup d'autres améliorations, bien sûr. Je vous conseille d'apprendre la programmation fonctionnelle, parce que vous pouvez en tirer un grand profit, mais n'allez pas trop loin avec elle aussi. Éliminer les états mutables globaux, partagés et excessifs, créer un système de gestion de l'information, etc. modèle de domaine immuable ou la création de fonctions pures sans effets secondaires externes est, en général, une bonne pratique, et les nouvelles Swift La langue encourage cela. B mauvais l'idée, car autre les développeurs liront et soutiendront votre code, et ils peuvent être frustrés ou effrayés par la prismatic profunctors et ce genre de choses dans votre modèle immuable. La même chose avec le ReactiveCocoa Ne le faites pas. RACify votre code trop car il peut devenir très vite illisible, surtout pour les débutants. Utilisez-le quand il peut vraiment simplifier vos objectifs et votre logique.

Donc, lisez beaucoup, mélangez, expérimentez, et essayez de tirer le meilleur de différentes approches architecturales. C'est le meilleur conseil que je puisse vous donner.

0 votes

Bonjour @alexander, merci pour votre réponse. Vos microservices (CommonServices, UserServices...) sont-ils des classes statiques, singleton ou en instanciez-vous un à chaque fois que vous devez appeler une requête réseau ?

1 votes

@darksider Comme je l'ai déjà écrit dans ma réponse : "`Je n'utilise jamais les singletons, la classe God APIManagerWhatever ou d'autres trucs incorrects, parce que le singleton est un anti-pattern, et dans la plupart des cas (sauf rares) est une mauvaise solution. ". I don't like singletons. I have an opinion that if you decided to use singletons in your project you should have at least three criteria why you do this (I edited my answer). So I inject them (lazy of course and not each time, but une fois ") dans chaque contrôleur.

0 votes

Vous n'avez donc qu'une seule instance de APIClient que vous injectez dans vos services. Comme APIClient n'est pas un singleton, l'instance d'APIClient est-elle une propriété de l'AppDelegate ou est-elle transmise chaque fois que vous créez un nouveau UIViewController ?

18voto

Rickye Points 541

Parce que toutes les applications iOS sont différents, je pense qu'il y a différentes approches à envisager de prendre, mais je vont habituellement de cette façon:
Créer un centre de gestion (singleton) classe permettant de gérer toutes les requêtes à l'API (généralement nommé APICommunicator) et chaque méthode d'instance est un appel d'API. Et il y a une centrale (non publique) méthode:

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

Pour l'enregistrement, j'utilise 2 grandes librairies/frameworks, ReactiveCocoa et AFNetworking. ReactiveCocoa poignées async réseau réponses parfaitement, vous pouvez le faire (méthodes sendnext:, sendError:, etc.).
Cette méthode appelle l'API, obtient les résultats et les envoie à travers le car en "brut" format (comme NSArray ce AFNetworking retours).
Puis une méthode comme getStuffList: qui a appelé à la méthode ci-dessus, souscrit à son signal, analyse les données brutes dans des objets (avec quelque chose comme Motis) et envoie les objets un par un à l'appelant (getStuffList: et des méthodes similaires également renvoyer un signal que le contrôleur peut vous abonner).
L'abonné contrôleur reçoit les objets en subscribeNext:'s de bloc et les poignées.

J'ai essayé de nombreuses façons dans différentes applications mais celui-ci a travaillé le meilleur de tous, donc j'ai été en utilisant cette dans un peu d'applications récemment, il s'adapte à la fois les petits et les gros projets, et il est facile à étendre et à maintenir si quelque chose doit être modifié.
Espérons que cela aide, je voudrais entendre les opinions des autres sur mon approche et peut-être la façon dont d'autres pensent que ce pourrait être peut-être améliorée.

1 votes

Cette approche est simple, mais comme le nombre d'API augmente, il devient plus difficile de maintenir le gestionnaire d'API singleton. De plus, chaque nouvelle API ajoutée sera liée au gestionnaire, quel que soit le module auquel elle appartient. Essayez d'utiliser github.com/kevin0571/STNetTaskQueueue pour gérer les demandes d'API.

0 votes

Mis à part la question de savoir pourquoi vous faites de la publicité pour votre bibliothèque, qui est aussi éloignée que possible de ma solution et beaucoup plus compliquée, j'ai essayé cette approche sur d'innombrables projets, petits et grands, et je l'utilise exactement de la même manière depuis que j'ai écrit cette réponse. Avec des conventions de nommage intelligentes, ce n'est pas du tout difficile à maintenir.

7voto

Fran K. Points 330

À mon avis, toute architecture logicielle est dictée par le besoin. S'il s'agit d'un projet d'apprentissage ou d'un projet personnel, il faut décider de l'objectif principal et faire en sorte qu'il détermine l'architecture. S'il s'agit d'un travail pour le compte d'autrui, alors les besoins de l'entreprise sont primordiaux. L'astuce consiste à ne pas laisser les objets brillants vous distraire des besoins réels. Je trouve cela difficile à faire. Il y a toujours de nouvelles choses brillantes qui apparaissent dans ce domaine et beaucoup d'entre elles ne sont pas utiles, mais vous ne pouvez pas toujours le savoir dès le départ. Concentrez-vous sur le besoin et soyez prêt à abandonner les mauvais choix si vous le pouvez.

Par exemple, j'ai récemment réalisé un prototype rapide d'une application de partage de photos pour une entreprise locale. Comme le besoin de l'entreprise était de faire quelque chose de rapide et sale, l'architecture a fini par être un peu de code iOS pour faire apparaître un appareil photo et un peu de code réseau attaché à un bouton d'envoi qui télécharge l'image vers un magasin S3 et écrit dans un domaine SimpleDB. Le code était trivial et le coût minime et le client dispose d'une collection de photos évolutive accessible sur le web avec des appels REST. Bon marché et stupide, l'application avait beaucoup de défauts et bloquait l'interface utilisateur à l'occasion, mais ce serait un gaspillage de faire plus pour un prototype et cela leur permet de déployer à leur personnel et de générer des milliers d'images de test facilement sans problèmes de performance ou d'évolutivité. L'architecture est merdique, mais elle répond parfaitement aux besoins et au coût.

Un autre projet a consisté à mettre en œuvre une base de données locale sécurisée qui se synchronise avec le système de l'entreprise en arrière-plan lorsque le réseau est disponible. J'ai créé un synchroniseur en arrière-plan qui utilisait RestKit, car il semblait avoir tout ce dont j'avais besoin. Mais j'ai dû écrire tellement de code personnalisé pour RestKit afin de traiter le JSON idiosyncratique que j'aurais pu faire tout cela plus rapidement en écrivant mes propres transformations JSON vers CoreData. Cependant, le client voulait apporter cette application en interne et j'ai pensé que RestKit serait similaire aux frameworks qu'ils utilisaient sur d'autres plateformes. J'attends de voir si c'était une bonne décision.

Encore une fois, il s'agit pour moi de me concentrer sur le besoin et de laisser celui-ci déterminer l'architecture. Je m'efforce d'éviter d'utiliser des paquets tiers, car ils entraînent des coûts qui n'apparaissent qu'après que l'application a été utilisée pendant un certain temps. J'essaie d'éviter de faire des hiérarchies de classes car elles sont rarement rentables. Si je peux écrire quelque chose dans un laps de temps raisonnable au lieu d'adopter un paquetage qui ne convient pas parfaitement, alors je le fais. Mon code est bien structuré pour le débogage et commenté de manière appropriée, mais les paquets tiers le sont rarement. Cela dit, je trouve AF Networking trop utile pour être ignoré et bien structuré, bien commenté et maintenu et je l'utilise beaucoup ! RestKit couvre beaucoup de cas courants, mais j'ai l'impression de m'être battu quand je l'utilise, et la plupart des sources de données que je rencontre sont pleines de bizarreries et de problèmes qui sont mieux gérés avec du code personnalisé. Dans mes dernières applications, j'ai simplement utilisé les convertisseurs JSON intégrés et écrit quelques méthodes utilitaires.

Un modèle que j'utilise toujours est de faire sortir les appels réseau du fil principal. Les 4 ou 5 dernières applications que j'ai réalisées ont mis en place une tâche de temporisation en arrière-plan en utilisant dispatch_source_create qui se réveille de temps en temps et effectue des tâches réseau si nécessaire. Il faut faire un peu de thread safety et s'assurer que le code modifiant l'interface utilisateur est envoyé au thread principal. Il est également utile de procéder à l'embarquement et à l'initialisation de manière à ce que l'utilisateur ne se sente pas accablé ou retardé. Jusqu'à présent, cela a plutôt bien fonctionné. Je vous suggère de vous pencher sur ces questions.

Enfin, je pense que plus nous travaillons et plus le système d'exploitation évolue, plus nous avons tendance à développer de meilleures solutions. Il m'a fallu des années pour me défaire de ma conviction que je dois suivre les modèles et les conceptions que d'autres personnes prétendent être obligatoires. Si je travaille dans un contexte où cela fait partie de la religion locale, ahem, je veux dire les meilleures pratiques d'ingénierie du département, alors je suis les coutumes à la lettre, c'est pour cela qu'ils me paient. Mais je trouve rarement que suivre les anciens modèles et schémas est la solution optimale. J'essaie toujours de considérer la solution à travers le prisme des besoins de l'entreprise et de construire l'architecture en conséquence, en gardant les choses aussi simples que possible. Lorsque j'ai l'impression qu'il n'y en a pas assez, mais que tout fonctionne correctement, alors je suis sur la bonne voie.

4voto

bzz Points 61

J'utilise l'approche que j'ai obtenue d'ici : https://github.com/Constantine-Fry/Foursquare-API-v2 . J'ai réécrit cette bibliothèque en Swift et vous pouvez voir l'approche architecturale à partir de ces parties du code :

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

Fondamentalement, il existe une sous-classe NSOperation qui crée la NSURLRequest, analyse la réponse JSON et ajoute le bloc de rappel avec le résultat à la file d'attente. La classe principale de l'API construit la NSURLRequest, initialise la sous-classe NSOperation et l'ajoute à la file d'attente.

3voto

Martin Points 589

Nous utilisons plusieurs approches en fonction de la situation. Pour la plupart des choses, AFNetworking est l'approche la plus simple et la plus robuste dans la mesure où vous pouvez définir des en-têtes, télécharger des données multipart, utiliser GET, POST, PUT & DELETE et il existe un tas de catégories supplémentaires pour UIKit qui vous permettent par exemple de définir une image à partir d'une url. Dans une application complexe avec beaucoup d'appels, nous faisons parfois abstraction de tout cela en utilisant une méthode de commodité qui nous est propre et qui pourrait ressembler à ceci :

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

Il existe cependant quelques situations où AFNetworking n'est pas approprié, par exemple lorsque vous créez un cadre ou un autre composant de bibliothèque, car AFNetworking peut déjà se trouver dans une autre base de code. Dans cette situation, vous utiliserez un NSMutableURLRequest, soit en ligne si vous effectuez un appel unique, soit abstrait dans une classe de demande/réponse.

0 votes

Pour moi, c'est la meilleure et la plus claire des réponses, merci. "C'est aussi simple que ça". @martin, personnellement nous utilisons NSMutableURLRequest tout le temps ; y a-t-il une vraie raison d'utiliser AFNetworking ?

0 votes

AFNetworking est juste pratique, vraiment. Pour moi, les blocs de succès et d'échec en valent la peine car ils rendent le code plus facile à gérer. Je suis d'accord pour dire que c'est parfois exagéré.

0 votes

Un point superbe sur les blocs, merci pour cela. Je suppose que la nature spécifique de tout cela va changer avec Swift.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X