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




telecharger application iphone gratuit (9)

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.


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.


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.


I avoid singletons when designing my applications. They are a typical go to for a lot of people but I think you can find more elegant solutions elsewhere. Typically what I do is a build out my entities in CoreData and then put my REST code in an NSManagedObject category. If for instance I wanted to create and POST a new User, I'd do this:

User* newUser = [User createInManagedObjectContext:managedObjectContext];
[newUser postOnSuccess:^(...) { ... } onFailure:^(...) { ... }];

I use RESTKit for the object mapping and initialize it at start up. I find routing all of your calls through a singleton to be a waste of time and adds a lot of boilerplate that isn't needed.

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

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

Why add extra helper classes when you can extend the functionality of a common base class through categories?

If you're interested in more detailed info on my solution let me know. I'm happy to share.


Selon l'objectif de cette question, j'aimerais décrire notre approche de l'architecture.

Approche d'architecture

L'architecture de notre application iOS générale repose sur les modèles suivants: couches de service , MVVM , liaison de données d'interface utilisateur , injection de dépendances ; et le paradigme de la programmation réactive fonctionnelle .

Nous pouvons découper une application typique orientée consommateur en couches logiques suivantes:

  • Assemblée
  • Modèle
  • Prestations de service
  • Espace de rangement
  • Les gestionnaires
  • Coordinateurs
  • UI
  • Infrastructure

La couche d'assemblage est un point d'amorçage de notre application. Il contient un conteneur d'injection de dépendances et des déclarations des objets de l'application et de leurs dépendances. Cette couche peut également contenir la configuration de l'application (URL, clés de services tiers, etc.). Pour ce faire, nous utilisons la bibliothèque Typhoon .

La couche modèle contient des classes de modèles de domaine, des validations et des mappages. Nous utilisons la bibliothèque Mantle pour cartographier nos modèles: elle prend en charge la sérialisation / désérialisation au format JSON et les modèles NSManagedObject . Pour la validation et la représentation de forme de nos modèles, nous utilisons les bibliothèques FXForms et FXModelValidation .

La couche Services déclare les services que nous utilisons pour interagir avec des systèmes externes afin d'envoyer ou de recevoir des données représentées dans notre modèle de domaine. Ainsi, nous avons généralement des services de communication avec les API serveur (par entité), les services de messagerie (comme PubNub ), les services de stockage (comme Amazon S3), etc. Les services enveloppent les objets fournis par les SDK (par exemple PubNub SDK) ou implémentent leur propre communication. logique. Pour la mise en réseau générale, nous utilisons la bibliothèque AFNetworking .

Le but de la couche de stockage est d'organiser le stockage de données local sur le périphérique. Nous utilisons des données de base ou un Realm pour cela (les deux ont des avantages et des inconvénients, la décision de quoi utiliser est basée sur des spécifications concrètes). Pour la configuration des données de base, nous utilisons la bibliothèque MDMCoreData et un tas de classes - stockages - (similaires aux services) qui fournissent un accès au stockage local pour chaque entité. Pour le Royaume, nous utilisons simplement des stockages similaires pour avoir accès au stockage local.

La couche des gestionnaires est un endroit où vivent nos abstractions / wrappers.

Dans un rôle de gestionnaire pourrait être:

  • Credentials Manager avec ses différentes implémentations (keychain, NSDefaults, ...)
  • Gestionnaire de session actuel qui sait comment conserver et fournir la session utilisateur en cours
  • Capture Pipeline qui permet d'accéder aux périphériques multimédia (enregistrement vidéo, audio, prise de photos)
  • BLE Manager qui fournit un accès aux services et périphériques bluetooth
  • Gestionnaire de géolocalisation
  • ...

Ainsi, dans le rôle de gestionnaire pourrait être n'importe quel objet qui implémente la logique d'un aspect particulier ou une préoccupation nécessaire au fonctionnement de l'application.

Nous essayons d'éviter Singletons, mais cette couche est un endroit où ils vivent s'ils sont nécessaires.

La couche Coordinateurs fournit des objets qui dépendent des objets d'autres couches (Service, Stockage, Modèle) afin de combiner leur logique en une séquence de travail nécessaire pour certains modules (fonctionnalité, écran, user story ou expérience utilisateur). Il enchaîne généralement les opérations asynchrones et sait comment réagir sur leurs cas de réussite et d'échec. À titre d'exemple, vous pouvez imaginer une fonctionnalité de messagerie et l'objet MessagingCoordinator correspondant. La gestion de l'envoi d'un message peut ressembler à ceci:

  1. Valider le message (couche de modèle)
  2. Enregistrer le message localement (stockage des messages)
  3. Télécharger la pièce jointe du message (service amazon s3)
  4. Mettre à jour l'état des messages et les URL des pièces jointes et enregistrer le message localement (stockage des messages)
  5. Sérialiser le message au format JSON (couche modèle)
  6. Publier un message sur PubNub (service PubNub)
  7. Mettre à jour l'état et les attributs du message et l'enregistrer localement (stockage des messages)

Sur chacune des étapes ci-dessus, une erreur est gérée de manière correspondante.

La couche d'interface utilisateur comprend les sous-couches suivantes:

  1. ViewModels
  2. ViewControllers
  3. Vues

Afin d'éviter les contrôleurs Massive View, nous utilisons le modèle MVVM et la logique d'implémentation nécessaire pour la présentation de l'interface utilisateur dans ViewModels. Un ViewModel a généralement des coordinateurs et des gestionnaires en tant que dépendances. ViewModels utilisés par ViewControllers et certains types de vues (par exemple, les cellules de vue de table). La liaison entre ViewControllers et ViewModels est la liaison de données et le modèle de commande. Afin de rendre cette colle possible, nous utilisons la bibliothèque ReactiveCocoa .

Nous utilisons également ReactiveCocoa et son concept RACSignal tant RACSignal et en renvoyant le type de valeur de tous les coordinateurs, services, méthodes de stockage. Cela nous permet de chaîner les opérations, les exécuter parallèlement ou en série, et bien d'autres choses utiles fournies par ReactiveCocoa.

Nous essayons d'implémenter notre comportement d'interface utilisateur de manière déclarative. Data Binding et Auto Layout aide beaucoup à atteindre cet objectif.

La couche Infrastructure contient tous les helpers, extensions, utilitaires nécessaires au travail d'application.

Cette approche fonctionne bien pour nous et pour les types d'applications que nous construisons habituellement. Mais vous devez comprendre, que c'est juste une approche subjective qui devrait être adaptée / modifiée pour l'objectif concret de l'équipe.

J'espère que ceci vous aidera!

Vous pouvez également trouver plus d'informations sur le processus de développement iOS dans cet article de blog iOS Development as a Service


Try github.com/kevin0571/STNetTaskQueue

Create API requests in separated classes.

STNetTaskQueue will deal with threading and delegate/callback.

Extendable for different protocols.


To my mind all software architecture is driven by need. If this is for learning or personal purposes, then decide the primary goal and have that drive the architecture. If this is a work for hire, then the business need is paramount. The trick is to not let shiny things distract you from the real needs. I find this hard to do. There are always new shiny things appearing in this business and lots of them are not useful, but you can't always tell that up front. Focus on the need and be willing to abandon bad choices if you can.

For example, I recently did a quick prototype of a photo sharing app for a local business. Since the business need was to do something quick and dirty, the architecture ended up being some iOS code to pop up a camera and some network code attached to a Send Button that uploaded the image to a S3 store and wrote to a SimpleDB domain. The code was trivial and the cost minimal and the client has an scalable photo collection accessible over the web with REST calls. Cheap and dumb, the app had lots of flaws and would lock the UI on occasion, but it would be a waste to do more for a prototype and it allows them to deploy to their staff and generate thousands of test images easily without performance or scalability concerns. Crappy architecture, but it fit the need and cost perfectly.

Another project involved implementing a local secure database which synchronizes with the company system in the background when the network is available. I created a background synchronizer that used RestKit as it seemed to have everything I needed. But I had to write so much custom code for RestKit to deal with idiosyncratic JSON that I could have done it all quicker by writing my own JSON to CoreData transformations. However, the customer wanted to bring this app in house and I felt that RestKit would be similar to the frameworks that they used on other platforms. I waiting to see if that was a good decision.

Again, the issue to me is to focus on the need and let that determine the architecture. I try like hell to avoid using third party packages as they bring costs that only appears after the app has been in the field for a while. I try to avoid making class hierarchies as they rarely pay off. If I can write something in a reasonable period of time instead of adopting a package that doesn't fit perfectly, then I do it. My code is well structured for debugging and appropriately commented, but third party packages rarely are. With that said, I find AF Networking too useful to ignore and well structured, well commented, and maintained and I use it a lot! RestKit covers a lot of common cases, but I feel like I've been in a fight when I use it, and most of the data sources I encounter are full of quirks and issues that are best handled with custom code. In my last few apps I just use the built in JSON converters and write a few utility methods.

One pattern I always use is to get the network calls off the main thread. The last 4-5 apps I've done set up a background timer task using dispatch_source_create that wakes up every so often and does network tasks as needed. You need to do some thread safety work and make sure that UI modifying code gets sent to the main thread. It also helps to do your onboarding/initialization in such a way that the user doesn't feel burdened or delayed. So far this has been working rather well. I suggest looking into these things.

Finally, I think that as we work more and as the OS evolves, we tend to develop better solutions. It has taken me years to get over my belief that I have to follow patterns and designs that other people claim are mandatory. If I am working in a context where that is part of the local religion, ahem, I mean the departmental best engineering practices, then I follow the customs to the letter, that's what they are paying me for. But I rarely find that following older designs and patterns is the optimal solution. I always try to look at the solution through the prism of the business needs and build the architecture to match it and keep things as simple as they can be. When I feel like there isn't enough there, but everything works correctly, then I'm on the right track.


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.


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.


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





ios7