ios - ufficio - migliori app produttività ipad




I migliori approcci architettonici per la creazione di applicazioni di rete iOS(client REST) (8)

Nella mia situazione di solito utilizzo la libreria ResKit per configurare il livello di rete. Fornisce l'analisi facile da usare. Riduce il mio impegno nell'impostare la mappatura per diverse risposte e cose.

Aggiungo solo del codice per configurare automaticamente la mappatura. Definisco la classe base per i miei modelli (non protocollo a causa del sacco di codice per verificare se qualche metodo è implementato o meno, e meno codice nei modelli stessi):

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

Le relazioni sono oggetti che rappresentano oggetti nidificati in risposta:

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

Quindi sto configurando la mappatura per RestKit in questo modo:

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

Alcuni esempi di implementazione MappableEntry:

User.h

@interface User : MappableEntity

@property (nonatomic) long userId;
@property (nonatomic, copy) NSString *username;
@property (nonatomic, copy) NSString *email;
@property (nonatomic, copy) NSString *password;
@property (nonatomic, copy) NSString *token;

- (instancetype)initWithUsername:(NSString*)username email:(NSString*)email password:(NSString*)password;

- (NSDictionary*)registrationData;

@end

User.m

@implementation User

- (instancetype)initWithUsername:(NSString*)username email:(NSString*)email password:(NSString*)password {
    if (self = [super init]) {
        self.username = username;
        self.email = email;
        self.password = password;
    }
    return self;
}

- (NSDictionary*)registrationData {
    return @{
        @"username": self.username,
        @"email": self.email,
        @"password": self.password
    };
}

+ (NSArray*)pathPatterns {
    return @[
        [NSString stringWithFormat:@"/api/%@/users/register", APIVersionString],
        [NSString stringWithFormat:@"/api/%@/users/login", APIVersionString]
    ];
}

+ (NSArray*)fieldsArrayForMapping {
    return @[ @"username", @"email", @"password", @"token" ];
}

+ (NSDictionary*)fieldsDictionaryForMapping {
    return @{ @"id": @"userId" };
}

@end

Ora a proposito del wrapping Richieste:

Ho un file di intestazione con la definizione dei blocchi, per ridurre la lunghezza della linea in tutte le classi APIRequest:

APICallbacks.h

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

E un esempio della mia classe APIRequest che sto usando:

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

E tutto ciò che devi fare nel codice, semplicemente inizializza l'oggetto API e chiamalo quando ne hai bisogno:

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

Il mio codice non è perfetto, ma è facile da impostare una volta e utilizzare per diversi progetti. Se è interessante per chiunque, potrei passare un po 'di tempo e creare una soluzione universale da qualche parte su GitHub e CocoaPods.

Sono uno sviluppatore iOS con una certa esperienza e questa domanda è davvero interessante per me. Ho visto molte risorse e materiali diversi su questo argomento, ma tuttavia sono ancora confuso. Qual è la migliore architettura per un'applicazione di rete iOS? Intendo framework e schemi astratti di base che si adattano ad ogni applicazione di rete, sia che si tratti di una piccola app che ha solo poche richieste server o un client REST complesso. Apple consiglia di utilizzare MVC come approccio architettonico di base per tutte le applicazioni iOS, ma né MVC né i modelli MVVM più moderni spiegano dove inserire il codice logico di rete e come organizzarlo in generale.

Devo sviluppare qualcosa come MVCS ( S for Service ) e in questo livello di Service inserire tutte le richieste API e altre logiche di rete, che in prospettiva possono essere davvero complesse? Dopo aver fatto qualche ricerca ho trovato due approcci di base per questo. Here si consigliava di creare una classe separata per ogni richiesta di rete API servizio web (come la classe LoginRequest o la classe PostCommentRequest e così via) che eredita dalla classe astratta della richiesta di base AbstractBaseRequest e oltre a creare un gestore di rete globale che incapsula codice di rete comune e altre preferenze (potrebbe essere la personalizzazione di AFNetworking o RestKit ottimizzazione di RestKit , se si dispone di mappature e persistenza di oggetti complessi o persino di una propria implementazione di comunicazione di rete con API standard). Ma questo approccio mi sembra un sovraccarico. Un altro approccio consiste nell'avere un dispatcher API singleton o una classe manager come nel primo approccio, ma non creare classi per ogni richiesta e invece incapsulare ogni richiesta come metodo pubblico di istanza di questa classe manager come: fetchContacts , metodi loginUser , ecc. Quindi, qual è il modo migliore e corretto? Ci sono altri approcci interessanti che non conosco ancora?

E dovrei creare un altro livello per tutto questo materiale di rete come il Service , il livello NetworkProvider o qualunque altro sulla mia architettura MVC , o questo strato dovrebbe essere integrato (iniettato) in strati MVC esistenti, ad es. Model ?

So che esistono approcci stupendi, o in che modo tali mostri mobili come il client Facebook o il client LinkedIn si occupano di complessità esponenziale della logica di rete?

So che non esiste una risposta esatta e formale al problema. L'obiettivo di questa domanda è raccogliere gli approcci più interessanti da sviluppatori iOS esperti . L'approccio migliore suggerito sarà contrassegnato come accettato e premiato con una taglia della reputazione, altri saranno svalutati. È principalmente una domanda teorica e di ricerca. Voglio capire l'approccio architettonico di base, astratto e corretto per le applicazioni di rete in iOS. Spero una spiegazione dettagliata da parte degli sviluppatori esperti.


Poiché tutte le app iOS sono diverse, penso che ci siano diversi approcci da considerare, ma di solito vado in questo modo:
Creare una classe di gestore centrale (singleton) per gestire tutte le richieste API (in genere denominata APICommunicator) e ogni metodo di istanza è una chiamata API. E c'è un metodo centrale (non pubblico):

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

Per la cronaca, utilizzo 2 librerie / framework principali, ReactiveCocoa e AFNetworking. ReactiveCocoa gestisce perfettamente le risposte asincrone alla rete, puoi farlo (sendNext :, sendError :, ecc.).
Questo metodo chiama l'API, ottiene i risultati e li invia tramite RAC in formato "raw" (come NSArray, cosa restituisce AFNetworking).
Quindi un metodo come getStuffList: che ha chiamato il metodo sopra sottoscrive il suo segnale, analizza i dati grezzi in oggetti (con qualcosa come Motis) e invia gli oggetti uno alla volta al chiamante ( getStuffList: e metodi simili restituiscono anche un segnale che controller può iscriversi a).
Il controllore sottoscritto riceve gli oggetti da subscribeNext: il blocco e li gestisce.

Ho provato molti modi in diverse app, ma questa ha funzionato al meglio, quindi ho utilizzato questo in alcune app di recente, si adatta a piccoli e grandi progetti ed è facile estenderla e mantenerla se qualcosa deve essere modificato.
Spero che questo aiuti, mi piacerebbe sentire le opinioni degli altri sul mio approccio e forse su come gli altri potrebbero pensare che questo potrebbe essere migliorato.


I want to understand basic, abstract and correct architectural approach for networking applications in iOS : non esiste un I want to understand basic, abstract and correct architectural approach for networking applications in iOS "il migliore" o "il più corretto" per costruire un'architettura di applicazione. È un lavoro molto creativo. Dovresti sempre scegliere l'architettura più semplice ed estensibile, che sarà chiara per qualsiasi sviluppatore, che inizi a lavorare sul tuo progetto o per altri sviluppatori nel tuo team, ma sono d'accordo che ci possa essere un "buono" e un "cattivo" " architettura.

Hai detto: collect the most interesting approaches from experienced iOS developers , non penso che il mio approccio sia il più interessante o corretto, ma l'ho usato in diversi progetti e soddisfatto. È un approccio ibrido di quelli che hai menzionato sopra e anche con miglioramenti dai miei sforzi di ricerca. Sono interessante nei problemi degli approcci di costruzione, che combinano diversi modelli e idiomi noti. Penso che molti modelli aziendali di Fowler possano essere applicati con successo alle applicazioni mobili. Ecco una lista delle più interessanti, che possiamo applicare per creare un'architettura di applicazioni iOS ( a mio parere ): Service Layer , Unità di lavoro , Facciata remota , Oggetto di trasferimento dati , Gateway , Supertipo di livello , Caso speciale , Modello di dominio . Dovresti sempre progettare correttamente un livello del modello e non dimenticare mai la persistenza (può aumentare significativamente le prestazioni della tua app). Puoi utilizzare i Core Data per questo. Ma non bisogna dimenticare che Core Data non è un ORM o un database, ma un graph manager di oggetti con persistenza come buona opzione. Quindi, molto spesso i Core Data possono essere troppo pesanti per le tue esigenze e puoi guardare nuove soluzioni come Realm e Couchbase Lite , o creare il tuo oggetto leggero di mappatura / livello di persistenza degli oggetti, basato su SQLite o LevelDB . Inoltre ti consiglio di familiarizzare con il Domain Driven Design e CQRS .

All'inizio, penso, dovremmo creare un altro livello per il networking, perché non vogliamo controller grassi o modelli pesanti e sopraffatti. Non credo in quei fat model, skinny controller cose fat model, skinny controller . Ma io credo nel skinny everything avvicina, perché nessuna classe dovrebbe essere grassa, mai. Tutte le reti possono essere generalmente astratte come logica aziendale, di conseguenza dovremmo avere un altro livello, dove possiamo metterlo. Il livello di servizio è ciò di cui abbiamo bisogno:

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

Nel nostro ambito MVC Service Layer è qualcosa di simile a un mediatore tra il modello di dominio e i controller. Esiste una variazione piuttosto simile di questo approccio, chiamato MVCS cui lo Store è in realtà il nostro livello di Service . Store istanze del modello di vendita e gestisce il networking, il caching, ecc. Voglio ricordare che non devi scrivere tutta la tua rete e la tua logica di business nel tuo livello di servizio. Anche questo può essere considerato un cattivo design. Per maggiori informazioni guarda i modelli di dominio Anemic e Rich . Alcuni metodi di servizio e logica aziendale possono essere gestiti nel modello, quindi sarà un modello "ricco" (con comportamento).

Uso sempre estensivamente due librerie: AFNetworking 2.0 e ReactiveCocoa . Penso che sia un must per qualsiasi applicazione moderna che interagisca con la rete e i servizi web o contenga una complessa logica dell'interfaccia utente.

ARCHITETTURA

Inizialmente creo una classe APIClient generale, che è una sottoclasse di AFHTTPSessionManager . Questo è un cavallo di battaglia di tutte le reti nell'applicazione: tutte le classi di servizio delegano ad esso richieste di REST reali. Contiene tutte le personalizzazioni del client HTTP, di cui ho bisogno nella specifica applicazione: NSError SSL, elaborazione degli errori e creazione di semplici oggetti NSError con dettagliati motivi di errore e descrizioni di tutti gli errori API e di connessione (in tal caso il controller sarà in grado di mostrare correttamente messaggi per l'utente), impostazione di serializzatori di richiesta e risposta, intestazioni http e altre informazioni relative alla rete. Quindi divido logicamente tutte le richieste API in sottoservizi o, più correttamente, in microservices : UserSerivces , CommonServices , SecurityServices , FriendsServices e così via, in base alla logica aziendale che implementano. Ognuno di questi microservizi è una classe separata. Loro, insieme, formano un Service Layer . Queste classi contengono metodi per ogni richiesta API, modelli di domini di processo e restituiscono sempre un RACSignal con il modello di risposta analizzato o NSError al chiamante.

Voglio dire che se si dispone di una logica di serializzazione del modello complessa, quindi creare un altro livello per esso: qualcosa come Data Mapper ma più generale, ad esempio JSON / XML -> Model mapper. Se disponi di cache: quindi creala anche come livello / servizio separato (non dovresti mescolare la logica di business con la memorizzazione nella cache). Perché? Perché il corretto livello di memorizzazione nella cache può essere abbastanza complesso con i suoi trucchi. Le persone implementano una logica complessa per ottenere un caching valido e prevedibile come ad esempio il caching monoidale con proiezioni basate sui profunctors. Puoi leggere su questa bella libreria chiamata Carlos per capire di più. E non dimenticare che i Core Data possono davvero aiutarti con tutti i problemi di caching e ti permetteranno di scrivere meno logica. Inoltre, se si dispone di una logica tra NSManagedObjectContext e i modelli di richieste del server, è possibile utilizzare il modello di Repository , che separa la logica che recupera i dati e li associa al modello di entità dalla logica aziendale che agisce sul modello. Quindi, consiglio di usare il pattern Repository anche quando hai un'architettura basata sui Core Data. Il repository può astrarre cose, come NSFetchRequest , NSEntityDescription , NSPredicate e così via per metodi semplici come get o put .

Dopo tutte queste azioni nel livello Servizio, il chiamante (controller di visualizzazione) può eseguire alcune complesse operazioni asincrone con la risposta: manipolazioni del segnale, concatenamento, mappatura, ecc. Con l'aiuto delle primitive ReactiveCocoa , o semplicemente iscriversi ad esso e mostrare i risultati nel vista. Inietto con l' Iniezione delle dipendenze in tutte queste classi di servizio il mio APIClient , che tradurrà una particolare chiamata di servizio nella richiesta GET , POST , PUT , DELETE , ecc. Corrispondente all'endpoint REST. In questo caso, APIClient viene passato implicitamente a tutti i controller, è possibile renderlo esplicito con una parametrizzazione sulle classi di servizio APIClient . Questo può avere senso se si desidera utilizzare personalizzazioni differenti APIClient per particolari classi di servizio, ma se, per qualche motivo, non si desiderano copie aggiuntive o si è certi che si userà sempre una particolare istanza (senza personalizzazioni) di l' APIClient - APIClient un singleton, ma NON, per favore NON fare classi di servizio come single.

Quindi, ogni controller di vista di nuovo con il DI immette la classe di servizio richiesta, chiama i metodi di servizio appropriati e compone i risultati con la logica dell'interfaccia utente. Per l'iniezione di dipendenza mi piace usare BloodMagic o un framework Typhoon più potente. Non uso mai singleton, Dio APIManagerWhatever classe o altra roba sbagliata. Perché se chiami la tua classe WhateverManager , questo indica che non conosci il suo scopo ed è una cattiva scelta di design . Singletons è anche un anti-pattern, e nella maggior parte dei casi (eccetto quelli rari) è una soluzione sbagliata . Singleton dovrebbe essere considerato solo se tutti e tre i seguenti criteri sono soddisfatti:

  1. La proprietà della singola istanza non può essere ragionevolmente assegnata;
  2. L'inizializzazione pigra è desiderabile;
  3. L'accesso globale non è altrimenti previsto.

Nel nostro caso la proprietà della singola istanza non è un problema e inoltre non abbiamo bisogno di accesso globale dopo aver diviso il nostro dio manager in servizi, perché ora solo uno o più controller dedicati necessitano di un particolare servizio (es. UserProfile controller ha bisogno di UserServices e così sopra).

Dovremmo sempre rispettare il principio S in SOLID e utilizzare la separazione delle preoccupazioni , quindi non mettere tutti i metodi di servizio e le chiamate di rete in un'unica classe, perché è pazzesco, soprattutto se si sviluppa un'applicazione aziendale di grandi dimensioni. Ecco perché dovremmo prendere in considerazione l'iniezione di dipendenza e l'approccio ai servizi. Considero questo approccio moderno e post-OO . In questo caso dividiamo la nostra applicazione in due parti: logica di controllo (controllori ed eventi) e parametri.

Un tipo di parametri sarebbero normali parametri "dati". Questo è ciò che passiamo attorno a funzioni, manipolazioni, modifiche, persistenza, ecc. Si tratta di entità, aggregati, raccolte, classi di casi. L'altro tipo sarebbe parametri di "servizio". Si tratta di classi che incapsulano la logica aziendale, consentono la comunicazione con sistemi esterni, forniscono l'accesso ai dati.

Ecco un flusso di lavoro generale della mia architettura con l'esempio. Supponiamo di avere un FriendsViewController , che mostra l'elenco degli amici dell'utente e abbiamo un'opzione da rimuovere dagli amici. Creo un metodo nella mia classe FriendsServices chiamato:

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

dove Friend è un oggetto modello / dominio (o può essere solo un oggetto User se hanno attributi simili). Underhood questo metodo analizza Friend in NSDictionary dei parametri JSON friend_id , name , surname , friend_request_id e così via. Io uso sempre la libreria di Mantle per questo tipo di file standard e per il mio livello di modello (analizzando avanti e indietro, gestendo gerarchie di oggetti nidificati in JSON e così via). Dopo l'analisi, chiama il metodo APIClient DELETE per effettuare una richiesta REST effettiva e restituisce la Response in RACSignal al chiamante ( FriendsViewController nel nostro caso) per visualizzare il messaggio appropriato per l'utente o qualsiasi altra cosa.

Se la nostra applicazione è molto grande, dobbiamo separare ulteriormente la nostra logica. Ad esempio, non è sempre bene mescolare il Repository o la logica del modello con il Service one. Quando ho descritto il mio approccio, ho detto che il metodo removeFriend dovrebbe essere nel livello Service , ma se saremo più pedanti possiamo notare che è meglio che appartenga al Repository . Ricordiamo cos'è il repository. Eric Evans ha fornito una descrizione precisa nel suo libro [DDD]:

Un repository rappresenta tutti gli oggetti di un certo tipo come un insieme concettuale. Funziona come una raccolta, tranne che con funzionalità di query più elaborate.

Quindi, un Repository è essenzialmente una facciata che utilizza la semantica dello stile Collection (Aggiungi, Aggiorna, Rimuovi) per fornire accesso a dati / oggetti. Ecco perché quando hai qualcosa come: getFriendsList , getUserGroups , removeFriend puoi metterlo nel Repository , perché la semantica della raccolta è abbastanza chiara qui. E codice come:

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

è sicuramente una logica di business, perché è oltre le operazioni di base di CRUD e connette due oggetti di dominio ( Friend and Request ), ecco perché dovrebbe essere collocato nel livello di Service . Voglio anche notare: non creare astrazioni inutili . Usa tutti questi approcci con saggezza. Perché se si sommergerà la tua applicazione con astrazioni, ciò aumenterà la sua accidentale complessità e la complessità causa più problemi nei sistemi software di qualsiasi altra cosa

Ti descrivo un "vecchio" esempio di Objective-C ma questo approccio può essere adattato molto facilmente al linguaggio Swift con molti più miglioramenti, perché ha caratteristiche più utili e zucchero funzionale. Consiglio vivamente di utilizzare questa libreria: Moya . Ti consente di creare uno strato APIClient più elegante (il nostro cavallo di battaglia come te lo ricordi). Ora il nostro provider APIClient sarà un tipo di valore (enum) con estensioni conformi ai protocolli e che sfruttano la corrispondenza di modelli destrutturanti. L'enumerazione rapida + la corrispondenza dei modelli ci consente di creare tipi di dati algebrici come nella programmazione funzionale classica. I nostri microservizi utilizzeranno questo fornitore APIClient migliorato come nel consueto approccio Objective-C. Per il livello del modello invece di Mantle puoi usare la libreria ObjectMapper o mi piace usare una libreria Argo più elegante e funzionale.

Così, ho descritto il mio approccio architettonico generale, che può essere adattato per qualsiasi applicazione, penso. Ci possono essere molti più miglioramenti, ovviamente. Ti consiglio di imparare la programmazione funzionale, perché puoi trarne molti vantaggi, ma non esagerare con essa. Eliminare uno stato mutevole eccessivo, condiviso, globale, creare un modello di dominio immutabile o creare funzioni pure senza effetti collaterali esterni è, generalmente, una buona pratica e il nuovo linguaggio Swift incoraggia. Ma ricordati sempre che sovraccaricare il tuo codice con schemi funzionali puramente pesanti, approcci categoria-teorici è una cattiva idea, perché altri sviluppatori leggeranno e supporteranno il tuo codice, e possono essere frustrati o paurosi dei prismatic profunctors e di quel tipo di cose in il tuo modello immutabile. La stessa cosa con ReactiveCocoa : non RACify troppo il tuo codice, perché può diventare illeggibile molto velocemente, specialmente per i principianti. Usalo quando può davvero semplificare i tuoi obiettivi e la logica.

Quindi read a lot, mix, experiment, and try to pick up the best from different architectural approaches . È il miglior consiglio che posso darti.


Da una prospettiva di progettazione puramente di classe, di solito hai qualcosa di simile a questo:

  • I tuoi controller di visualizzazione controllano una o più viste
  • Classe del modello di dati - Dipende davvero da quante entità distinte e reali sono trattate e da come sono correlate.

    Ad esempio, se si dispone di una serie di elementi da visualizzare in quattro diverse rappresentazioni (elenco, grafico, grafico, ecc.), Si avrà una classe del modello di dati per l'elenco di articoli, uno in più per un articolo. L' elenco delle classi di articoli sarà condiviso da quattro controller di vista, tutti i figli di un controller di barra delle linguette o di un controller di navigazione.

    Le classi dei modelli di dati saranno utili non solo nella visualizzazione dei dati, ma anche nella loro serializzazione in cui ognuno di essi può esporre il proprio formato di serializzazione tramite metodi di esportazione JSON / XML / CSV (o qualsiasi altra cosa).

  • È importante capire che sono necessarie anche classi di build di richieste API che si associano direttamente agli endpoint dell'API REST. Supponiamo che tu abbia un'API che registra l'utente, quindi la classe di builder dell'API di accesso creerà il payload POST JSON per l'API di accesso. In un altro esempio, una classe di build di richieste API per l'elenco degli elementi del catalogo API creerà la stringa di query GET per l'API corrispondente e genererà la query REST GET.

    Queste classi di build di richieste API di solito ricevono i dati dai controller di visualizzazione e restituiscono anche gli stessi dati per visualizzare i controller per l'aggiornamento dell'interfaccia utente / altre operazioni. I controller di vista decideranno quindi come aggiornare gli oggetti del modello dati con tali dati.

  • Infine, il cuore del client REST - classe data fetcher API che è ignaro di tutte le specie di richieste API effettuate dall'app. Questa classe sarà più probabilmente un singleton, ma come altri hanno sottolineato, non deve essere un singleton.

    Si noti che il collegamento è solo un'implementazione tipica e non prende in considerazione scenari come la sessione, i cookie, ecc., Ma è sufficiente per andare avanti senza utilizzare strutture di terze parti.


A mio avviso, tutta l'architettura del software è guidata dal bisogno. Se questo è per scopi di apprendimento o personali, quindi decidere l'obiettivo primario e avere che guidare l'architettura. Se si tratta di un lavoro a noleggio, la necessità aziendale è fondamentale. Il trucco è non lasciare che le cose lucide ti distolgano dai reali bisogni. Lo trovo difficile da fare. Ci sono sempre nuove cose brillanti che appaiono in questo business e molte di queste non sono utili, ma non puoi sempre dirlo in anticipo. Concentrati sull'esigenza e sii disposto ad abbandonare le scelte sbagliate se puoi.

Ad esempio, di recente ho fatto un rapido prototipo di un'applicazione di condivisione di foto per un'azienda locale. Poiché l'esigenza aziendale era di fare qualcosa di veloce e sporco, l'architettura finiva con l'essere un codice iOS per far apparire una telecamera e un codice di rete collegato a un pulsante di invio che caricava l'immagine in un negozio S3 e scriveva su un dominio SimpleDB. Il codice era banale e il costo minimo e il client ha una collezione di foto scalabile accessibile via web con chiamate REST. Economica e stupida, l'app aveva molti difetti e avrebbe bloccato l'interfaccia utente in alcune occasioni, ma sarebbe stato inutile fare di più per un prototipo e consentirgli di implementare il proprio personale e generare facilmente migliaia di immagini di test senza prestazioni o scalabilità preoccupazioni. Architettura scadente, ma si adatta perfettamente al bisogno e al costo.

Un altro progetto prevedeva l'implementazione di un database sicuro locale che si sincronizza con il sistema aziendale in background quando la rete è disponibile. Ho creato un sincronizzatore di sfondo che utilizzava RestKit perché sembrava avere tutto ciò di cui avevo bisogno. Ma ho dovuto scrivere tanto codice personalizzato per RestKit per gestire JSON idiosincratico che avrei potuto fare tutto più velocemente scrivendo le mie trasformazioni JSON in CoreData. Tuttavia, il cliente voleva portare questa app in casa e ritenevo che RestKit sarebbe stato simile ai framework utilizzati su altre piattaforme. Aspetto di vedere se è stata una buona decisione.

Ancora una volta, il problema per me è concentrarsi sulla necessità e lasciare che determinano l'architettura. Provo a malapena di evitare l'uso di pacchetti di terze parti poiché portano dei costi che appaiono solo dopo che l'app è stata sul campo per un po '. Cerco di evitare di fare gerarchie di classi poiché raramente danno i loro frutti. Se posso scrivere qualcosa in un ragionevole periodo di tempo invece di adottare un pacchetto che non si adatta perfettamente, allora lo faccio. Il mio codice è ben strutturato per il debug e commentato in modo appropriato, ma raramente i pacchetti di terze parti lo sono. Detto questo, trovo che AF Networking sia troppo utile da ignorare e ben strutturato, ben commentato e mantenuto e lo uso spesso! RestKit copre un sacco di casi comuni, ma mi sento come se avessi litigato quando lo uso,e la maggior parte delle fonti di dati che incontro sono piene di stranezze e problemi che sono meglio gestiti con codice personalizzato. Nelle mie ultime app uso solo i convertitori JSON incorporati e scrivo alcuni metodi di utilità.

Uno schema che uso sempre è quello di togliere le chiamate di rete dal thread principale. Le ultime app 4-5 che ho creato impostano un'attività di timer in background utilizzando dispatch_source_create che si attiva ogni tanto e svolge attività di rete secondo necessità. È necessario eseguire alcune operazioni di sicurezza dei thread e assicurarsi che il codice di modifica dell'interfaccia utente venga inviato al thread principale. Aiuta anche a fare il tuo onboarding / inizializzazione in modo tale che l'utente non si senta gravato o in ritardo. Finora questo ha funzionato piuttosto bene. Suggerisco di esaminare queste cose.

Infine, penso che mentre lavoriamo di più e man mano che il sistema operativo si evolve, tendiamo a sviluppare soluzioni migliori. Mi ci sono voluti anni per superare la mia convinzione che devo seguire schemi e disegni che altre persone sostengono siano obbligatori. Se sto lavorando in un contesto in cui questo è parte della religione locale, ehm, intendo le migliori pratiche di ingegneria dipartimentale, poi seguo le abitudini alla lettera, è per questo che mi pagano. Raramente però trovo che seguire schemi e schemi più vecchi sia la soluzione ottimale. Cerco sempre di guardare la soluzione attraverso il prisma delle esigenze aziendali e costruire l'architettura per abbinarla e mantenere le cose il più semplici possibile. Quando sento che non ce n'è abbastanza, ma tutto funziona correttamente, allora sono sulla strada giusta.


Evito i singleton durante la progettazione delle mie applicazioni. Sono tipici per molte persone, ma penso che tu possa trovare soluzioni più eleganti altrove. In genere quello che faccio è un build delle mie entità in CoreData e quindi inserire il mio codice REST in una categoria NSManagedObject. Se ad esempio volessi creare e postare un nuovo utente, lo farei:

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

Io uso RESTKit per la mappatura dell'oggetto e lo inizializzo all'avvio. Trovo che il routing di tutte le chiamate attraverso un singleton sia una perdita di tempo e aggiunge un sacco di informazioni che non sono necessarie.

In NSManagedObject + Estensioni.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];
}

Perché aggiungere classi helper aggiuntive quando è possibile estendere la funzionalità di una classe base comune attraverso le categorie?

Se sei interessato a informazioni più dettagliate sulla mia soluzione fammi sapere. Sono felice di condividere.


Usiamo alcuni approcci a seconda della situazione. Per la maggior parte delle cose AFNetworking è l'approccio più semplice e robusto in quanto è possibile impostare le intestazioni, caricare dati multiparte, utilizzare GET, POST, PUT & DELETE e ci sono un sacco di categorie aggiuntive per UIKit che consentono di impostare ad esempio un'immagine da un url. In un'app complessa con molte chiamate a volte la estendiamo a un metodo di convenienza che sarebbe qualcosa di simile:

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

Esistono alcune situazioni in cui AFNetworking non è appropriato, ad esempio dove si sta creando un framework o un altro componente della libreria poiché AFNetworking potrebbe già trovarsi in un'altra base di codice. In questa situazione si utilizzerà NSMutableURLRequest in linea se si effettua una singola chiamata o si è astratti in una classe richiesta / risposta.


Uso l'approccio che ho ottenuto da qui: https://github.com/Constantine-Fry/Foursquare-API-v2 . Ho riscritto quella libreria in Swift e puoi vedere l'approccio architettonico da queste parti del codice:

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

Fondamentalmente, esiste una sottoclasse NSOperation che rende NSURLRequest, analizza la risposta JSON e aggiunge il blocco callback con il risultato alla coda. La classe API principale costruisce NSURLRequest, inizializza la sottoclasse NSOperation e la aggiunge alla coda.





ios7