ios - sans - telecharger application iphone gratuit




Meilleures approches architecturales pour créer des applications de réseau iOS(clients REST) (8)

Comme toutes les applications iOS sont différentes, je pense qu'il existe différentes approches à prendre en compte, mais je vais généralement de la manière suivante:
Créez une classe de gestionnaire central (singleton) pour gérer toutes les demandes d'API (généralement appelée APICommunicator) et chaque méthode d'instance est un appel d'API. Et il existe une méthode centrale (non publique):

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

Pour mémoire, j'utilise 2 bibliothèques majeures / frameworks, ReactiveCocoa et AFNetworking. ReactiveCocoa gère parfaitement les réponses réseau asynchrones, vous pouvez le faire (sendNext :, sendError :, etc.).
Cette méthode appelle l'API, obtient les résultats et les envoie via RAC au format brut (comme NSArray ce que AFNetworking renvoie).
Puis une méthode comme getStuffList: qui appelle la méthode ci-dessus s'abonne à son signal, analyse les données brutes en objets (avec quelque chose comme Motis) et envoie les objets un par un à l'appelant ( getStuffList: et des méthodes similaires retournent également un signal le contrôleur peut s'abonner à).
Le contrôleur souscrit reçoit les objets par le bloc subscribeNext: et les gère.

J'ai essayé de nombreuses façons dans différentes applications, mais celle-ci a fonctionné le mieux. J'ai récemment utilisé ceci dans quelques applications, elle convient à de petits et grands projets et il est facile d'étendre et de maintenir si quelque chose doit être modifié.
J'espère que cela aidera, j'aimerais entendre les opinions des autres sur mon approche et peut-être comment les autres pensent que cela pourrait être amélioré.

Je suis un développeur iOS avec de l'expérience et cette question est vraiment intéressante pour moi. J'ai vu beaucoup de ressources et de matériaux sur ce sujet, mais je suis toujours confus. Quelle est la meilleure architecture pour une application en réseau iOS? Je veux dire le cadre abstrait de base, les modèles, qui s'adapteront à chaque application de réseau si c'est une petite application qui a seulement quelques demandes de serveur ou un client REST complexe. Apple recommande d'utiliser MVC comme approche architecturale de base pour toutes les applications iOS, mais ni MVC ni les modèles MVVM plus modernes n'expliquent où placer le code logique réseau et comment l'organiser en général.

Dois-je développer quelque chose comme MVCS ( S for Service ) et, dans cette couche Service , mettre toutes les demandes d' API et 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. Here il a été recommandé de créer une classe distincte pour chaque requête réseau vers l' API service web (comme la classe LoginRequest ou PostCommentRequest etc.) qui hérite de la classe abstraite de demande de base AbstractBaseRequest et crée un gestionnaire de réseau global qui encapsule code de réseau commun et autres préférences (il peut s'agir de la personnalisation AFNetworking ou RestKit réglage RestKit , si nous avons des mappages d'objets complexes et de la persistance, ou même une implémentation de communication réseau propre avec API standard). Mais cette approche semble être un surcoût pour moi. Une autre approche consiste à avoir un répartiteur ou une classe de gestionnaire d' API singleton comme dans la première approche, mais pas de créer des classes pour chaque requête et d'encapsuler chaque requête comme méthode publique d'instance de cette classe de gestionnaire comme: fetchContacts , loginUser , etc. Alors, quelle est la meilleure et la plus juste? Y a-t-il d'autres approches intéressantes que je ne connais pas encore?

Et devrais-je créer une autre couche pour tous ces éléments de mise en réseau comme Service , ou couche NetworkProvider ou autre sur mon architecture MVC , ou cette couche devrait être intégrée (injectée) dans des couches MVC existantes, par exemple Model ?

Je sais qu'il existe de belles approches, ou comment de tels monstres mobiles comme le client Facebook ou le client LinkedIn traitent-ils de la complexité exponentielle de la logique réseau?

Je sais qu'il n'y a pas de réponse exacte et formelle au problème. Le but de cette question est de recueillir les approches les plus intéressantes des développeurs iOS expérimentés . La meilleure approche suggérée sera marquée comme acceptée et récompensée avec une prime de réputation, d'autres seront upvoted. C'est surtout une question théorique et de recherche. Je veux comprendre l'approche architecturale de base, abstraite et correcte pour les applications de réseautage dans iOS. J'espère obtenir des explications détaillées de la part de développeurs expérimentés.


Dans ma situation, j'utilise habituellement la bibliothèque ResKit pour configurer la couche réseau. Il fournit une analyse facile à utiliser. Cela réduit mon effort sur la mise en place de la cartographie pour différentes réponses et d'autres choses.

Je n'ajoute que du code pour configurer le mapping automatiquement. Je définis la classe de base pour mes modèles (pas de protocole à cause de beaucoup de code pour vérifier si une méthode est implémentée ou non, et moins de code dans les modèles):

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

Les relations sont des objets qui représentent des objets imbriqués en réponse:

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

Ensuite, je configure le mapping pour RestKit comme ceci:

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

Some example of MappableEntry implementation:

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

Now about the Requests wrapping:

I have header file with blocks definition, to reduce line length in all APIRequest classes:

APICallbacks.h

typedef void(^SuccessCallback)();
typedef void(^SuccessCallbackWithObjects)(NSArray *objects);
typedef void(^ErrorCallback)(NSError *error);
typedef void(^ProgressBlock)(float progress);

And Example of my APIRequest class that I'm using:

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

And all you need to do in code, simply initialize API object and call it whenever you need it:

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

My code isn't perfect, but it's easy to set once and use for different projects. If it's interesting to anyone, mb I could spend some time and make a universal solution for it somewhere on GitHub and CocoaPods.


I want to understand basic, abstract and correct architectural approach for networking applications in iOS : il n'y a pas de «meilleur», ou «le plus correct» pour construire une architecture d'application. C'est un travail très créatif. Vous devriez 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 d'autres développeurs de votre équipe, mais je suis d'accord, qu'il peut y avoir un "bon" et un "mauvais". " architecture.

Vous avez dit: collect the most interesting approaches from experienced iOS developers , je ne pense pas que mon approche soit la plus intéressante ou correcte, mais je l'ai utilisée dans plusieurs projets et j'en suis satisfait. C'est une approche hybride de ceux que vous avez mentionnés ci-dessus, et aussi avec des améliorations de mes propres efforts de recherche. Je suis intéressant dans les problèmes de construction d'approches, qui combinent plusieurs modèles et idiomes bien connus. Je pense que beaucoup de modèles d'entreprise de Fowler peuvent être appliqués avec succès aux applications mobiles. Voici une liste des plus intéressants, 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 , Gateway , Supertype de couche , Cas particulier , Modèle de domaine . Vous devriez toujours concevoir correctement un calque de modèle et toujours ne pas oublier la persistance (il peut augmenter considérablement les performances de votre application). Vous pouvez utiliser les Core Data pour cela. Mais vous ne devez pas oublier que Core Data n'est pas un ORM ou une base de données, mais un gestionnaire de graphe d'objet avec la persistance comme bonne option. Ainsi, très souvent, les Core Data peuvent être trop lourdes pour vos besoins et vous pouvez regarder de nouvelles solutions telles que Realm et Couchbase Lite , ou créer votre propre couche légère de mappage / persistance d'objets, basée sur SQLite ou LevelDB . Aussi je vous conseille de vous familiariser avec le Domain Driven Design et CQRS .

Au début, je pense, nous devrions créer une autre couche pour la mise en réseau, parce que nous ne voulons pas de gros contrôleurs ou de modèles lourds et dépassés. Je ne crois pas à ces fat model, skinny controller choses fat model, skinny controller . Mais je crois en l'approche du skinny everything , car aucune classe ne devrait être grosse, jamais. Tout le réseautage peut être généralement abstrait comme une logique métier, par conséquent nous devrions avoir une autre couche, où nous pouvons le mettre. Service Layer est ce dont nous avons besoin:

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

Dans notre domaine MVC Service Layer est quelque chose comme un 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 réalité notre couche de Service . Store vends des instances de modèle et gère la mise en réseau, la mise en cache, etc. Je tiens à mentionner que vous ne devez pas écrire toute votre logique réseau et métier dans votre couche de service. Cela peut également être considéré comme un mauvais design. Pour plus d'informations, consultez les modèles de domaine Anemic et Rich . Certaines méthodes de service et la logique métier peuvent être gérées dans le modèle, ce sera donc un modèle "riche" (avec comportement).

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

ARCHITECTURE

Au début, je crée une classe APIClient générale, qui est une sous-classe de AFHTTPSessionManager . Ceci est un bourreau de travail de tous les réseaux dans l'application: toutes les classes de service lui délèguent des requêtes REST réelles. Il contient toutes les personnalisations du client HTTP, dont j'ai besoin dans l'application particulière: épinglage SSL, traitement des erreurs et création d'objets NSError simples avec des raisons d'échec détaillées et des descriptions de toutes les API et erreurs de connexion (dans ce cas, le contrôleur pourra montrer messages pour l'utilisateur), paramétrer les sérialiseurs de requête 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, en microservices : UserSerivces , CommonServices , SecurityServices , FriendsServices et ainsi de suite, en fonction de la logique métier qu'ils implémentent. Chacun de ces microservices est une classe distincte. Ensemble, ils forment une Service Layer . Ces classes contiennent des méthodes pour chaque requête API, traitent les modèles de domaine et retournent toujours un RACSignal avec le modèle de réponse analysé ou NSError à l'appelant.

Je veux mentionner que si vous avez une logique de sérialisation de modèle complexe - alors créez une autre couche pour cela: quelque chose comme Data Mapper mais plus général, par exemple JSON / XML -> Model mapper. Si vous avez un cache: créez-le en tant que couche / service séparé (vous ne devez pas mélanger la logique métier avec la mise en cache). Pourquoi? Parce que la couche de mise en cache correcte peut être assez complexe avec ses propres pièges. Les personnes implémentent une logique complexe pour obtenir une mise en cache valide et prévisible, comme par exemple une mise en cache monoïdale avec des projections basées sur des profoncteurs. Vous pouvez lire sur cette belle bibliothèque appelée Carlos pour en savoir plus. Et n'oubliez pas que les données de base peuvent vraiment vous aider avec tous les problèmes de mise en cache et vous permettront d'écrire moins de logique. En outre, si vous avez une certaine logique entre les modèles de requêtes NSManagedObjectContext et serveur, vous pouvez utiliser le modèle Repository , qui sépare la logique qui récupère les données et les mappe au modèle d'entité à partir de la logique métier qui agit sur le modèle. Donc, je conseille d'utiliser le modèle Repository même lorsque vous avez une architecture basée sur les données de base. Le référentiel peut NSFetchRequest choses, comme NSFetchRequest , NSEntityDescription , NSPredicate et ainsi de suite à des méthodes simples comme get ou put .

Après toutes ces actions dans la couche Service, l'appelant (view controller) peut faire des choses asynchrones complexes avec la réponse: manipulations de signal, chaînage, mapping, etc. à l'aide des primitives ReactiveCocoa , ou simplement s'y abonner et afficher les résultats dans le vue. J'injecte à l' injection de dépendances dans toutes ces classes de service mon APIClient , qui traduira un appel de service particulier en POST GET , POST , PUT , DELETE , etc. correspondante au point de terminaison REST. Dans ce cas, APIClient est transmis implicitement à tous les contrôleurs, vous pouvez le rendre explicite avec des classes de service APIClient paramétrées. Cela peut être utile si vous souhaitez utiliser différentes personnalisations d' APIClient pour des classes de service particulières, mais si, pour certaines raisons, vous ne souhaitez pas de copies supplémentaires ou si vous êtes sûr de toujours utiliser une instance particulière (sans personnalisations) de APIClient - en faire un singleton, mais NE PAS, s'il vous plaît NE PAS faire des classes de service en tant que singletons.

Ensuite, chaque contrôleur de vue avec le DI injecte la classe de service dont il a besoin, appelle les méthodes de service appropriées et compose ses résultats avec la logique de l'interface utilisateur. Pour l'injection de dépendance, j'aime utiliser BloodMagic ou un framework Typhoon plus puissant. Je n'utilise jamais de singletons, de Dieu APIManagerWhatever , APIManagerWhatever classe ou autre mauvaise chose. Parce que si vous appelez votre classe WhateverManager , cela indique que vous ne connaissez pas son but et que c'est un mauvais choix de conception . Singletons est également un anti-pattern, et dans la plupart des cas (sauf rares) est une mauvaise solution. Singleton ne devrait être considéré que si les trois critères suivants sont satisfaits:

  1. La propriété de l'instance unique ne peut pas être attribuée de manière raisonnable.
  2. L'initialisation paresseuse est souhaitable.
  3. L'accès global n'est pas prévu autrement.

Dans notre cas, la propriété de l'instance unique n'est pas un problème et nous n'avons pas besoin d'accès global après avoir divisé notre gestionnaire de dieu en services, car seulement un ou plusieurs contrôleurs dédiés ont besoin d'un service particulier (par exemple UserProfile nécessite UserServices . sur).

Nous devons toujours respecter le principe S dans SOLID et utiliser la séparation des préoccupations , donc ne mettez pas tous vos appels de méthodes de service et de réseaux dans une classe, parce que c'est fou, surtout si vous développez une grande application d'entreprise. C'est pourquoi nous devrions envisager l'injection de dépendance 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 "données" ordinaires. C'est ce que nous passons autour des fonctions, manipuler, modifier, persister, etc. Ce sont des entités, des agrégats, des collections, des classes de cas. L'autre type serait "service" paramètres. 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 un flux de travail général de mon architecture par l'exemple. Supposons que nous ayons un FriendsViewController , qui affiche la liste des amis de l'utilisateur et que nous ayons une option à supprimer de ses amis. Je crée une méthode dans ma classe FriendsServices appelée:

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

Friend est un objet modèle / domaine (ou peut être juste un objet User s'il a des attributs similaires). Underhood cette méthode analyse Friend à NSDictionary des paramètres JSON friend_id , name , name surname , friend_request_id et ainsi de suite. J'utilise toujours la bibliothèque Mantle pour ce type de passe-partout et pour ma couche modèle (analyse syntaxique en arrière et en avant, gestion des hiérarchies d'objets imbriquées dans JSON et ainsi de suite). Après analyse, elle appelle la méthode APIClient DELETE pour effectuer une requête REST réelle et renvoie Response in RACSignal à l'appelant ( FriendsViewController dans notre cas) pour afficher le message approprié pour l'utilisateur ou autre.

Si notre application est très importante, nous devons séparer notre logique encore plus clairement. Par exemple, il n'est pas toujours bon de mélanger Repository ou la logique du modèle avec Service One. Quand j'ai décrit mon approche, j'avais dit que la méthode removeFriend devrait être dans la couche Service , mais si nous voulons être plus pédantes, nous pouvons remarquer qu'elle appartient mieux au Repository . Souvenons-nous de ce qu'est le dépôt. Eric Evans lui a donné une description précise dans son livre [DDD]:

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

Ainsi, un Repository est essentiellement une façade qui utilise la sémantique du style Collection (Ajouter, Mettre à jour, Supprimer) pour fournir l'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 de type collection est assez claire ici. Et code comme:

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

est certainement une logique métier, car elle va au-delà des opérations CRUD base et permet de connecter deux objets de domaine ( Friend et Request ), c'est pourquoi elle doit être placée dans la couche Service . Aussi je veux remarquer: ne créez pas d'abstractions inutiles . Utilisez toutes ces approches avec sagesse. Parce que si vous surchargez votre application avec des abstractions, cela augmentera sa complexité accidentelle, et la complexité causera plus de problèmes dans les systèmes logiciels qu'autre chose

Je vous décris un "ancien" exemple Objective-C mais cette approche peut être très facile à adapter pour le langage Swift avec beaucoup plus d'améliorations, car elle a des fonctionnalités plus utiles et du sucre fonctionnel. Je recommande fortement d'utiliser cette bibliothèque: Moya . Il vous permet de créer un calque APIClient plus élégant (notre cheval de bataille comme vous vous en souvenez). Maintenant, notre fournisseur APIClient sera un type de valeur (enum) avec des extensions conformes aux protocoles et tirant parti de la correspondance de modèles destructurants. Swift enums + pattern matching nous permet de créer des types de données algébriques comme dans la programmation fonctionnelle classique. Nos microservices utiliseront ce fournisseur APIClient amélioré comme dans l'approche Objective-C habituelle. Pour la couche de modèle au lieu de Mantle vous pouvez utiliser la bibliothèque ObjectMapper ou j'aime utiliser la bibliothèque Argo plus élégante et fonctionnelle.

Donc, j'ai décrit mon approche architecturale générale, qui peut être adaptée à toute application, je pense. Il peut y avoir beaucoup plus d'améliorations, bien sûr. Je vous conseille d'apprendre la programmation fonctionnelle, car vous pouvez en tirer beaucoup d'avantages, mais n'allez pas trop loin avec cela. L'élimination d'un état mutable excessif, partagé et global, la création d'un 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 le nouveau langage Swift encourage. Mais rappelez-vous toujours que surcharger votre code avec des patterns fonctionnels pures et purs, des approches théoriques de catégorie est une mauvaise idée, car d' autres développeurs liront et supporteront votre code, et ils peuvent être frustrés ou effrayants des prismatic profunctors et de ce genre de choses. votre modèle immuable. La même chose avec le ReactiveCocoa : ne pas trop RACify votre code, car il peut devenir illisible très rapidement, surtout pour les débutants. Utilisez-le quand il peut vraiment simplifier vos objectifs et votre logique.

Donc, read a lot, mix, experiment, and try to pick up the best from different architectural approaches . C'est le meilleur conseil que je puisse vous donner.


From a purely class design perspective, you will usually have something like this:

  • Your view controllers controlling one or more views
  • Data model class - It really depends upon how many real distinct entities you are dealing with, and how they are related.

    For example, if you have an array of items to be displayed in four different representations (list, chart, graph etc), you will have one data model class for list of items, one more for an item. The list of item class will be shared by four view controllers - all children of a tab bar controller or a nav controller.

    Data model classes will come handy in not only displaying data, but also serializing them wherein each of them can expose their own serialization format through JSON / XML / CSV (or anything else) export methods.

  • It is important to understand that you also need API request builder classes that map directly with your REST API endpoints. Let's say you have an API that logs the user in - so your Login API builder class will create POST JSON payload for login api. In another example, an API request builder class for list of catalog items API will create GET query string for corresponding api and fire the REST GET query.

    These API request builder classes will usually receive data from view controllers and also pass the same data back to view controllers for UI update / other operations. View controllers will then decide how to update Data Model objects with that data.

  • Finally, the heart of the REST client - API data fetcher class which is oblivious to all sorts of API requests your app makes. This class will more likely be a singleton, but as others pointed out, it doesn't have to be a singleton.

    Note that the link is just a typical implementation and does not take into consideration scenarios like session, cookies etc, but it is enough to get you going without using any 3rd party frameworks.


I use the approach that I've gotten from here: https://github.com/Constantine-Fry/Foursquare-API-v2 . I've rewritten that library in Swift and you can see the architectural approach from these parts of the 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}}
}

Basically, there is NSOperation subclass that makes the NSURLRequest, parses JSON response and adds the callback block with the result to the queue. The main API class constructs NSURLRequest, initialises that NSOperation subclass and adds it to the queue.


This question has a lot of excellent and extensive answers already, but I feel I have to mention it since no one else has.

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

Il est créé par les mêmes personnes que AFNetworking, mais est plus directement conçu avec Swift.


Try github.com/kevin0571/STNetTaskQueue

Create API requests in separated classes.

STNetTaskQueue will deal with threading and delegate/callback.

Extendable for different protocols.


We use a few approaches depending on the situation. For most things AFNetworking is the simplest and most robust approach in that you can set headers, upload multipart data, use GET, POST, PUT & DELETE and there are a bunch of additional categories for UIKit which allow you to for example set an image from a url. In a complex app with a lot of calls we sometimes abstract this down to a convenience method of our own which would be something like:

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

There are a few situations where AFNetworking isn't appropriate however such as where you are creating a framework or other library component as AFNetworking may already be in another code base. In this situation you would use an NSMutableURLRequest either inline if you are making a single call or abstracted into a request / response class.





ios7