Лучшие архитектурные подходы для построения сетевых приложений iOS (клиенты REST)

я разработчик iOS с некоторым опытом, и этот вопрос для меня интересен. Я видел много разных ресурсов и материалов по этой теме, но тем не менее я все еще в замешательстве. Какова Лучшая архитектура для сетевого приложения iOS? Я имею в виду основные абстрактные рамки, шаблоны, которые будут соответствовать каждому сетевому приложению, будь то небольшое приложение, которое имеет только несколько запросов сервера или сложный клиент REST. Apple рекомендует использовать MVC как основной архитектурный подход для всех приложений iOS, но ни MVC ни более современный MVVM шаблоны объясняют, куда поместить код сетевой логики и как его организовать в целом.

мне нужно разработать что-то вроде MVCS(S на Service) и в Service слой положить все API запросы и другая Сетевая логика, которая в перспективе может быть действительно сложной? После некоторых исследований я нашел два основных подхода к этому. здесь он был рекомендуется создать отдельный класс для каждого сетевого запроса к web-сервису API (типа LoginRequest класса или PostCommentRequest class и так далее), который все наследует от базового абстрактного класса запроса AbstractBaseRequest и в дополнение к созданию некоторого глобального сетевого менеджера, который инкапсулирует общий сетевой код и другие предпочтения (это может быть AFNetworking настройки или RestKit настройка, если у нас есть сложные сопоставления объектов и постоянство или даже собственная реализация сетевой связи с стандартный API.) Но этот подход кажется мне накладным. Другой подход-иметь какой-то синглтон API класс диспетчера или менеджера, как в первом подходе,а не чтобы создать классы для каждого запроса и вместо этого инкапсулировать каждый запрос в качестве открытого метода экземпляра этого класса менеджера, например:fetchContacts, loginUser методы и т. д. Итак, каков наилучший и правильный путь? Есть ли другие интересные подходы, которых я еще не знаю?

и я должен создать еще один слой для всех этих сетевых вещей, таких как Service или NetworkProvider слой или что-то еще поверх моего MVC архитектура, или этот слой должен быть интегрирован (введен) в существующий MVC слои, например,Model?

я знаю, что существуют красивые подходы, или как тогда такие мобильные монстры, как клиент Facebook или клиент LinkedIn, имеют дело с экспоненциально растущей сложностью сетевой логики?

я знаю, что нет точного и официального ответа на проблему. цель этого вопроса-собрать самые интересные подходы от опытных разработчиков iOS. Лучший предложенный подход будет отмечен как принятый и награжден репутационной щедростью, другие будут поддержаны. Это в основном теоретический и исследовательский вопрос. Я хочу понять базовый, абстрактный и правильный архитектурный подход для сетевых приложений в iOS. Надеюсь на подробное объяснение от опытных разработчиков.

11 ответов


I want to understand basic, abstract and correct architectural approach for networking applications in iOS : есть нет "лучший" или "самый правильный" подход к построению архитектуры приложения. Это очень творческие работы. Вы всегда должны выбирать самую простую и расширяемую архитектуру, которая будет понятна любому разработчику, который начнет работать над вашим проектом или для других разработчиков в вашей команде, но я согласен, что может быть "хорошая" и "плохая" архитектура.

Вы сказали: collect the most interesting approaches from experienced iOS developers, Я не думаю, что мой подход самый интересный или правильный, но я использовал его в нескольких проектах и доволен им. Это гибридный подход из тех, что вы упомянули выше, а также с улучшениями от моих собственных исследований. Мне интересны проблемы построения подходов, сочетающих в себе несколько известных паттернов и идиом. Я думаю много модели предприятия Фаулера может быть успешно применен к мобильным приложениям. Вот список самых интересных те, которые мы можем применить для создания архитектуры приложения iOS (по-моему): Слой Сервиса, Единица Работы, Удаленный Фасад, Объект Передачи Данных, шлюз, Супертип Слоя, Особый Случай, Модель Предметной Области. Вы всегда должны правильно проектировать слой модели и всегда не забывать о постоянстве (это может значительно повышение производительности приложения). Вы можете использовать Core Data для этого. Но ты! .. --85-->не должен забудь, что Core Data - это не ORM или база данных,а диспетчер графов объектов с сохранением в качестве хорошего варианта. Так, очень часто Core Data может быть слишком тяжелой для ваших потребностей, и вы можете посмотреть на новые решения, такие как Realm и Couchbase Lite или создайте свой собственный слой отображения/сохранения облегченных объектов на основе raw SQLite или LevelDB. Также я советую вам ознакомиться с Домен Управляемая Конструкция и CQRS.

сначала, я думаю, мы должны создайте еще один слой для сети, потому что мы не хотим, чтобы контроллеры fat или тяжелые, перегруженные модели. Я в это не верю!--8--> вещи. Но Я ... --127-->верю на skinny everything подход, потому что ни один класс не должен быть толстым, никогда. Все сети могут быть вообще абстрагируясь как бизнес-логика, следовательно, мы должны иметь другой слой, где мы можем его поместить. Слой Сервиса что нужно:

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

в нашем MVC realm Service Layer что-то вроде посредника между моделью домена и контроллеров. Существует довольно похожая вариация этого подхода, называемая MVCS где Store на самом деле наш!--13--> слой. Store vends экземпляры модели и обрабатывает сеть, кэширование так далее. Я хочу упомянуть, что вы не должен напишите всю свою сетевую и бизнес-логику на уровне сервиса. Это также можно рассматривать как плохой дизайн. Подробнее смотрите на анемия и богатое модели домена. Некоторые методы обслуживания и бизнес-логику можно обрабатывать в модели, поэтому она будет "богатой" (с поведением) моделью.

я всегда широко использую две библиотеки:AFNetworking 2.0 и ReactiveCocoa. Я думаю, что это должно быть для любого современного приложения, которое взаимодействует с сетью и веб-сервисов или содержит сложную логику интерфейса.

архитектура

сначала я создаю общие APIClient класс, который является подклассом AFHTTPSessionManager. Это рабочая лошадка всей сети в приложении: все классы обслуживания делегируют ему фактические запросы REST. Он содержит все настройки HTTP-клиента, которые мне нужны в конкретном приложении: закрепление SSL, обработка ошибок и создание простого NSError объекты с подробными причинами отказа и описаниями всех API и ошибки подключения (в таком случае контроллер сможет показывать правильные сообщения для пользователя), установка сериализаторов запросов и ответов, заголовков http и других связанных с сетью вещей. Затем я логически разделяю все запросы API на подчиненные службы или, более правильно, микрослужб: UserSerivces, CommonServices, SecurityServices, FriendsServices и так далее, соответственно бизнес-логике они реализуют. Каждый из этих микросервисов является отдельным классом. Они вместе образуют Service Layer. Эти классы содержат методы для каждого запроса API, модели доменного процесса и всегда возвращает RACSignal С анализируемой моделью ответа или NSError для звонящего.

я хочу упомянуть, что если у вас есть сложная логика сериализации модели - тогда создайте еще один слой для него: что-то вроде Маппер Данных но более общий, например, JSON / XML -> Model mapper. Если у вас есть кэш: тогда создайте его как отдельный слой / сервис (вы не должны смешивать бизнес-логику с кэшированием). Почему? Потому что правильный слой кэширования может быть довольно сложным со своими собственными gotchas. Люди реализуют сложную логику для получения допустимого, предсказуемого кэширования, например, моноидальное кэширование с проекциями на основе профункторов. Вы можете прочитать об этой прекрасной библиотеке под названием Карлос чтобы понять больше. И не забывайте, что Core Data действительно может помочь вам со всеми проблемами кэширования и позволит вам писать меньше логики. Кроме того, если у вас есть логика между NSManagedObjectContext и сервер запрашивает модели, вы можете использовать хранилище pattern, который отделяет логику, которая извлекает данные и сопоставляет их с моделью сущности, от бизнес-логики, которая действует на модель. Итак, я советую использовать шаблон репозитория, даже если у вас есть основные данные архитектура. Репозиторий может абстрагировать вещи, например NSFetchRequest,NSEntityDescription, NSPredicate и так далее до простых методов, таких как get или put.

после всех этих действий на уровне сервиса вызывающий (контроллер вида) может выполнять некоторые сложные асинхронные действия с ответом: манипуляции сигналом, цепочка, отображение и т. д. с помощью ReactiveCocoa примитивы , или просто подписаться на него и показать результаты в виде. Я впрыскиваю с Инъекции Зависимостей во всех этих классы обслуживания my APIClient, который переведет конкретный вызов службы в соответствующий GET, POST, PUT, DELETE, etc. запрос к конечной точке REST. В этом случае APIClient передается неявно всем контроллерам, вы можете сделать это явным с параметризованным over APIClient классы обслуживания. Это может иметь смысл, если вы хотите использовать различные настройки APIClient для определенных классов обслуживания, но если вы, по некоторым причинам, не хотите дополнительных копий или вы убедитесь, что вы всегда будете использовать один конкретный экземпляр (без настроек)APIClient - сделайте его синглетоном, но не делайте, пожалуйста, не делайте классы обслуживания как синглеты.

затем каждый контроллер представления снова с DI вводит класс службы, который ему нужен, вызывает соответствующие методы службы и составляет их результаты с логикой пользовательского интерфейса. Для инъекции зависимостей мне нравится использовать магия крови или более мощный каркас Тайфун. Я никогда не использую синглтоны, Бога APIManagerWhatever класс или другие неправильные вещи. Потому что если вы называете свой класс WhateverManager, это указывает на то, что вы не знаете его цели, и это плохой дизайн выбор. Синглеты также являются анти-шаблоном, и в большинство случаев (за исключением редких) - это неправильно решение. Синглтон следует рассматривать только в том случае, если удовлетворены все три из следующих критериев:

  1. владение одним экземпляром не может быть разумно назначенный;
  2. желательно ленивая инициализация;
  3. глобальный доступ не предусмотрено иное.

в нашем случае владение одним экземпляром не является проблемой, а также нам не нужен глобальный доступ после того, как мы разделили нашего менеджера Бога на службы, потому что теперь только один или несколько выделенных контроллеров нуждаются в определенной службе (например, provider будет типом значения (перечислением) с расширениями, соответствующими протоколам и использующими сопоставление шаблонов деструкции. Swift enums + pattern matching позволяет нам создавать алгебраические типы данных как в классическом функциональном программировании. Наши микросервисы будут использовать этот улучшенный APIClient провайдер, как и в обычном подходе Objective-C. Для слоя модели вместо Mantle можно использовать библиотека ObjectMapper или мне нравится использовать более элегантный и функциональный Арго библиотека.

Итак, я описал свой общий архитектурный подход, который может быть адаптирован для любого приложения, я думаю. Может быть намного больше улучшений, конечно. Я советую вам изучить функциональное программирование, потому что вы можете извлечь из него большую пользу, но не заходите слишком далеко. Устранение избыточного, общего, глобального изменяемого состояния, создание неизменяемая модель домена или создание чистых функций без внешних побочных эффектов, как правило, хорошая практика, и новые Swift язык поощряет это. Но всегда помните, что перегрузка вашего кода тяжелыми чистыми функциональными шаблонами, категориями-теоретическими подходами является плохо идея, потому что другое разработчики будут читать и поддерживать ваш код, и они могут быть разочарованы или страшно prismatic profunctors и такие вещи в свой неизменный модель. То же самое с ReactiveCocoa: нет RACify код слишком много, потому что он может стать нечитаемым очень быстро, особенно для новичков. Используйте его, когда это действительно может упростить ваши цели и логики.

и read a lot, mix, experiment, and try to pick up the best from different architectural approaches. Это лучший совет, который я могу дать вы.


в соответствии с целью этого вопроса, я хотел бы описать наш архитектурный подход.

архитектура подход

архитектура нашего общего приложения iOS стоит на следующих шаблонах:слои сервиса, MVVM, привязка данных пользовательского интерфейса, Инъекции Зависимостей; и Функциональное Реактивное Программирование парадигмы.

мы можем отрезать типичное применение облицовки потребителя в следующие логические уровни:

  • сборка
  • модель
  • услуги
  • для хранения
  • менеджеры
  • координаторов
  • UI
  • инфраструктура

слой Собрания является начальной точкой нашего приложения. Он содержит контейнер инъекции зависимостей и объявления объектов приложения и их зависимостей. Этот слой также может содержать конфигурация приложения (URL-адреса, ключи сторонних служб и т. д.). Для этого используем Тайфун библиотека.

модель слоя содержит классы моделей домена, проверки, сопоставления. Мы используем плащевой библиотека для сопоставления наших моделей: она поддерживает сериализацию / десериализацию в и NSManagedObject модели. Для проверки и представления формы наших моделей мы используем FXForms и FXModelValidation библиотеки.

услуги слое объявляет сервисы, которые мы используем для взаимодействия с внешними системами для отправки или получения данных, представленных в нашей модели домена. Поэтому обычно у нас есть службы для связи с API сервера (на объект), службы обмена сообщениями (например,PubNub), услуги хранения (например, Amazon S3)и т. д. В основном объекты wrap служб, предоставляемые SDKs (например, PubNub SDK) или реализуют их собственная логика общения. Для общей сети мы используем AFNetworking библиотека.

слой храненияцель заключается в организации локального хранения данных на устройстве. Мы используем основные данные или Realm для этого (оба имеют плюсы и минусы, решение о том, что использовать, основано на конкретных спецификациях). Для настройки основных данных мы используем MDMCoreData библиотека и куча классов-хранилища - (аналогично службам), которые предоставляют доступ к локальным хранение для каждого объекта. Для Realm мы просто используем аналогичные хранилища для доступа к локальному хранилищу.

менеджеры слоя это место, где живут наши абстракции/обертки.

в роли менеджера может быть:

  • диспетчер учетных данных с различными реализациями (keychain, NSDefaults, ...)
  • текущий менеджер сеансов, который знает, как сохранить и предоставить текущий сеанс пользователя
  • захвата обеспечивает доступ к медиа-устройствам (видеозапись, аудио, съемка)
  • BLE Manager, который обеспечивает доступ к услугам bluetooth и периферийных устройств
  • Гео Менеджер Склада
  • ...

таким образом, в роли менеджера может быть любой объект, который реализует логику определенного аспекта или проблемы, необходимые для работы приложения.

мы стараемся избегать Синглетов, но этот слой-это место, где они живут, если они необходимый.

слой координаторов предоставляет объекты, которые зависят от объектов из других слоев (сервис, хранилище, модель), чтобы объединить их логику в одну последовательность работы, необходимую для определенного модуля (функция, экран, история пользователя или пользовательский опыт). Он обычно связывает асинхронные операции и знает, как реагировать на их успех и неудачи. В качестве примера вы можете представить функцию обмена сообщениями и соответствующий


поскольку все приложения iOS разные, я думаю, что здесь есть разные подходы, но я обычно иду этим путем:
Создайте класс central manager (singleton) для обработки всех запросов API (обычно называемый APICommunicator), и каждый метод экземпляра является вызовом API. И есть один центральный (непубличный) метод:

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

для записи я использую 2 основные библиотеки / фреймворки, ReactiveCocoa и AFNetworking. ReactiveCocoa обрабатывает асинхронные сетевые ответы отлично, вы можете сделать (sendNext:, sendError: и т. д.).
Этот метод вызывает API, получает результаты и отправляет их через RAC в формате " raw " (например, NSArray, что возвращает AFNetworking).
Тогда такой метод, как getStuffList: который вызвал вышеуказанный метод подписывается на его сигнал, анализирует необработанные данные в объекты (с чем-то вроде Motis) и отправляет объекты один за другим вызывающему абоненту (getStuffList: и аналогичные методы также возвращают сигнал о том, что контроллер может подписаться).
Подписанный контроллер получает объекты по subscribeNext:'s блок и обрабатывает их.

Я пробовал много способов в разных приложениях, но этот работал лучше всего, поэтому я использовал это в нескольких приложениях в последнее время, он подходит как для небольших, так и для больших проектов, и его легко расширить и поддерживать, если что-то нужно изменить.
Надеюсь, это поможет, я хотел бы услышать мнения других о моем подходе и, возможно, как другие думают, что это может быть возможно улучшенный.


в моей ситуации я обычно использую пакет ресурсов библиотека для настройки сетевого уровня. Он обеспечивает простой в использовании синтаксический анализ. Это уменьшает мои усилия по настройке сопоставления для разных ответов и прочего.

я только добавляю код для автоматической настройки сопоставления. Я определяю базовый класс для своих моделей (не протокол из-за большого количества кода, чтобы проверить, реализован ли какой-либо метод или нет, и меньше кода в самих моделях):

MappableEntry.h

@interface MappableEntity : NSObject

+ (NSArray*)pathPatterns;
+ (NSArray*)keyPathes;
+ (NSArray*)fieldsArrayForMapping;
+ (NSDictionary*)fieldsDictionaryForMapping;
+ (NSArray*)relationships;

@end

MappableEntry.м

@implementation MappableEntity

+(NSArray*)pathPatterns {
    return @[];
}

+(NSArray*)keyPathes {
    return nil;
}

+(NSArray*)fieldsArrayForMapping {
    return @[];
}

+(NSDictionary*)fieldsDictionaryForMapping {
    return @{};
}

+(NSArray*)relationships {
    return @[];
}

@end

отношения-это объекты, которые представляют вложенные объекты в ответе:

RelationshipObject.h

@interface RelationshipObject : NSObject

@property (nonatomic,copy) NSString* source;
@property (nonatomic,copy) NSString* destination;
@property (nonatomic) Class mappingClass;

+(RelationshipObject*)relationshipWithKey:(NSString*)key andMappingClass:(Class)mappingClass;
+(RelationshipObject*)relationshipWithSource:(NSString*)source destination:(NSString*)destination andMappingClass:(Class)mappingClass;

@end

RelationshipObject.м

@implementation RelationshipObject

+(RelationshipObject*)relationshipWithKey:(NSString*)key andMappingClass:(Class)mappingClass {
    RelationshipObject* object = [[RelationshipObject alloc] init];
    object.source = key;
    object.destination = key;
    object.mappingClass = mappingClass;
    return object;
}

+(RelationshipObject*)relationshipWithSource:(NSString*)source destination:(NSString*)destination andMappingClass:(Class)mappingClass {
    RelationshipObject* object = [[RelationshipObject alloc] init];
    object.source = source;
    object.destination = destination;
    object.mappingClass = mappingClass;
    return object;
}

@end

затем я настраиваю отображение для RestKit, как это:

ObjectMappingInitializer.h

@interface ObjectMappingInitializer : NSObject

+(void)initializeRKObjectManagerMapping:(RKObjectManager*)objectManager;

@end

ObjectMappingInitializer.м

@interface ObjectMappingInitializer (Private)

+ (NSArray*)mappableClasses;

@end

@implementation ObjectMappingInitializer

+(void)initializeRKObjectManagerMapping:(RKObjectManager*)objectManager {

    NSMutableDictionary *mappingObjects = [NSMutableDictionary dictionary];

    // Creating mappings for classes
    for (Class mappableClass in [self mappableClasses]) {
        RKObjectMapping *newMapping = [RKObjectMapping mappingForClass:mappableClass];
        [newMapping addAttributeMappingsFromArray:[mappableClass fieldsArrayForMapping]];
        [newMapping addAttributeMappingsFromDictionary:[mappableClass fieldsDictionaryForMapping]];
        [mappingObjects setObject:newMapping forKey:[mappableClass description]];
    }

    // Creating relations for mappings
    for (Class mappableClass in [self mappableClasses]) {
        RKObjectMapping *mapping = [mappingObjects objectForKey:[mappableClass description]];
        for (RelationshipObject *relation in [mappableClass relationships]) {
            [mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:relation.source toKeyPath:relation.destination withMapping:[mappingObjects objectForKey:[relation.mappingClass description]]]];
        }
    }

    // Creating response descriptors with mappings
    for (Class mappableClass in [self mappableClasses]) {
        for (NSString* pathPattern in [mappableClass pathPatterns]) {
            if ([mappableClass keyPathes]) {
                for (NSString* keyPath in [mappableClass keyPathes]) {
                    [objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:[mappingObjects objectForKey:[mappableClass description]] method:RKRequestMethodAny pathPattern:pathPattern keyPath:keyPath statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
                }
            } else {
                [objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:[mappingObjects objectForKey:[mappableClass description]] method:RKRequestMethodAny pathPattern:pathPattern keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
            }
        }
    }

    // Error Mapping
    RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[Error class]];
    [errorMapping addAttributeMappingsFromArray:[Error fieldsArrayForMapping]];
    for (NSString *pathPattern in Error.pathPatterns) {
        [[RKObjectManager sharedManager] addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:errorMapping method:RKRequestMethodAny pathPattern:pathPattern keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)]];
    }
}

@end

@implementation ObjectMappingInitializer (Private)

+ (NSArray*)mappableClasses {
    return @[
        [FruiosPaginationResults class],
        [FruioItem class],
        [Pagination class],
        [ContactInfo class],
        [Credentials class],
        [User class]
    ];
}

@end

некоторый пример реализации MappableEntry:

пользователей.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

пользователей.м

@implementation User

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

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

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

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

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

@end

теперь о запросе упаковки:

у меня есть файл заголовка с определением блоков, чтобы уменьшить длину строки во всех APIRequest классы:

APICallbacks.h

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

и пример моего класса APIRequest, который я использую:

LoginAPI.h

@interface LoginAPI : NSObject

- (void)loginWithCredentials:(Credentials*)credentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError;

@end

LoginAPI.м

@implementation LoginAPI

- (void)loginWithCredentials:(Credentials*)credentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError {
    [[RKObjectManager sharedManager] postObject:nil path:[NSString stringWithFormat:@"/api/%@/users/login", APIVersionString] parameters:[credentials credentialsData] success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
        onSuccess(mappingResult.array);
    } failure:^(RKObjectRequestOperation *operation, NSError *error) {
        onError(error);
    }];
}

@end

и все, что вам нужно сделать в коде, просто инициализировать объект API и вызывать его, когда вам это нужно:

SomeViewController.м

@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

мой код не идеален, но это легко установить один раз и использовать для разных проектов. Если это кому-то интересно, mb я мог бы потратить некоторое время и сделать универсальное решение для него где-нибудь на GitHub и CocoaPods.


на мой взгляд вся архитектура программного обеспечения-это нужда. Если это для обучения или личных целей, а затем решить основную цель и иметь, что диск архитектуры. Если это работа по найму, то бизнес-потребность имеет первостепенное значение. Фокус в том, чтобы не позволить блестящим вещам отвлекать вас от реальных потребностей. Мне трудно это сделать. В этом бизнесе всегда появляются новые блестящие вещи, и многие из них бесполезны, но вы не всегда можете сказать это заранее. Сосредоточьтесь на необходимости и будьте готовы отказаться от неверного выбора, если сможете.

например, недавно я сделал быстрый прототип приложения для обмена фотографиями для местного бизнеса. Поскольку бизнес должен был сделать что-то быстрое и грязное, архитектура в конечном итоге стала некоторым кодом iOS для всплывающей камеры и некоторым сетевым кодом, прикрепленным к кнопке отправки, которая загрузила изображение в магазин S3 и написала в домен SimpleDB. Код был тривиальным, а стоимость минимальной, и клиент имеет масштабируемую коллекцию фотографий, доступную через интернет с звонки. Дешевое и глупое приложение имело много недостатков и иногда блокировало пользовательский интерфейс, но было бы бесполезно делать больше для прототипа, и это позволяет им развертывать свои сотрудники и легко генерировать тысячи тестовых изображений без проблем с производительностью или масштабируемостью. Дерьмовая архитектура, но она идеально подходит и стоит.

другой проект, связанный с реализацией локальной защищенной базы данных, которая синхронизируется с системой компании в фоновом режиме, когда сеть доступна. Я создал фоновый синхронизатор, который использовал RestKit, поскольку у него было все, что мне нужно. Но мне пришлось написать так много пользовательского кода для RestKit, чтобы иметь дело с идиосинкразическим JSON, что я мог бы сделать все это быстрее, написав свой собственный JSON для CoreData преобразований. Однако клиент хотел принести это приложение в дом, и я чувствовал, что RestKit будет похож на фреймворки, которые они использовали на других платформах. Я жду, чтобы увидеть, было ли это хорошим решением.

опять же, проблема для меня заключается в том, чтобы сосредоточиться на необходимости и позволить этому определить архитектуру. Я изо всех сил стараюсь избегать использования сторонних пакетов, поскольку они приносят затраты, которые появляются только после того, как приложение было в поле некоторое время. Я стараюсь избегать создания иерархий классов, поскольку они редко окупаются. Если я могу написать что-то в разумные сроки вместо того, чтобы принять пакет, который не подходит идеально, тогда я это делаю. Мой код хорошо структурирован для отладки и соответствующим образом комментируется, но сторонние пакеты редко бывают. С учетом сказанного, я нахожу сеть AF слишком полезной, чтобы игнорировать и хорошо структурированной, хорошо прокомментированной и поддерживаемой, и я использую ее много! RestKit охватывает много распространенных случаев, но я чувствую, что я был в борьбе, когда я использую его, и большинство источников данных, с которыми я сталкиваюсь, полны причуд и проблем, которые лучше всего обрабатываются с помощью пользовательского кода. В моих последних нескольких приложениях я просто использую встроенные конвертеры JSON и пишу несколько методов утилиты.

один шаблон, который я всегда использую, - это получить сетевые вызовы от основного потока. Последние 4-5 приложений, которые я сделал, создали фоновую задачу таймера с помощью dispatch_source_create, которая просыпается так часто и выполняет сетевые задачи по мере необходимости. Вам нужно выполнить некоторую работу по безопасности потоков и убедиться, что код изменения пользовательского интерфейса отправляется в основной поток. Это также помогает сделать вашу загрузку / инициализацию таким образом, чтобы пользователь не чувствовал себя обремененным или задержанным. До сих пор это работало довольно хорошо. Я предлагаю изучаю эти вещи.

наконец, я думаю, что по мере того, как мы работаем больше и как развивается ОС, мы склонны разрабатывать лучшие решения. Мне потребовались годы, чтобы избавиться от убеждения, что я должен следовать образцам и схемам, которые другие люди считают обязательными. Если я работаю в контексте, где это является частью местной религии, ГМ, я имею в виду лучшие инженерные практики департамента, тогда я следую обычаям до буквы, вот за что они мне платят. Но я редко нахожу это следовать более старыми дизайнами и картинами оптимальное решение. Я всегда стараюсь смотреть на решение через призму потребностей бизнеса и строить архитектуру, чтобы соответствовать ему и держать вещи так просто, как они могут быть. Когда я чувствую, что там недостаточно, но все работает правильно, тогда я на правильном пути.


Я использую подход, который я получил отсюда:https://github.com/Constantine-Fry/Foursquare-API-v2. Я переписал эту библиотеку в Swift, и вы можете увидеть архитектурный подход из этих частей кода:

typealias OpertaionCallback = (success: Bool, result: AnyObject?) -> ()

class Foursquare{
    var authorizationCallback: OperationCallback?
    var operationQueue: NSOperationQueue
    var callbackQueue: dispatch_queue_t?

    init(){
        operationQueue = NSOperationQueue()
        operationQueue.maxConcurrentOperationCount = 7;
        callbackQueue = dispatch_get_main_queue();
    }

    func checkIn(venueID: String, shout: String, callback: OperationCallback) -> NSOperation {
        let parameters: Dictionary <String, String> = [
            "venueId":venueID,
            "shout":shout,
            "broadcast":"public"]
        return self.sendRequest("checkins/add", parameters: parameters, httpMethod: "POST", callback: callback)
    }

    func sendRequest(path: String, parameters: Dictionary <String, String>, httpMethod: String, callback:OperationCallback) -> NSOperation{
        let url = self.constructURL(path, parameters: parameters)
        var request = NSMutableURLRequest(URL: url)
        request.HTTPMethod = httpMethod
        let operation = Operation(request: request, callbackBlock: callback, callbackQueue: self.callbackQueue!)
        self.operationQueue.addOperation(operation)
        return operation
    }

    func constructURL(path: String, parameters: Dictionary <String, String>) -> NSURL {
        var parametersString = kFSBaseURL+path
        var firstItem = true
        for key in parameters.keys {
            let string = parameters[key]
            let mark = (firstItem ? "?" : "&")
            parametersString += "\(mark)\(key)=\(string)"
            firstItem = false
        }
    return NSURL(string: parametersString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding))
    }
}

class Operation: NSOperation {
    var callbackBlock: OpertaionCallback
    var request: NSURLRequest
    var callbackQueue: dispatch_queue_t

    init(request: NSURLRequest, callbackBlock: OpertaionCallback, callbackQueue: dispatch_queue_t) {
        self.request = request
        self.callbackBlock = callbackBlock
        self.callbackQueue = callbackQueue
    }

    override func main() {
        var error: NSError?
        var result: AnyObject?
        var response: NSURLResponse?

        var recievedData: NSData? = NSURLConnection.sendSynchronousRequest(self.request, returningResponse: &response, error: &error)

        if self.cancelled {return}

        if recievedData{
            result = NSJSONSerialization.JSONObjectWithData(recievedData, options: nil, error: &error)
            if result != nil {
                if result!.isKindOfClass(NSClassFromString("NSError")){
                    error = result as? NSError
            }
        }

        if self.cancelled {return}

        dispatch_async(self.callbackQueue, {
            if (error) {
                self.callbackBlock(success: false, result: error!);
            } else {
                self.callbackBlock(success: true, result: result!);
            }
            })
    }

    override var concurrent:Bool {get {return true}}
}

в принципе, есть подкласс NSOperation, который делает NSURLRequest, анализирует ответ JSON и добавляет блок обратного вызова с результатом в очередь. Основной класс API создает NSURLRequest, инициализирует подкласс NSOperation и добавляет его в очередь.


мы используем несколько подходов в зависимости от ситуации. Для большинства вещей AFNetworking является самым простым и надежным подходом в том, что вы можете установить заголовки, загружать составные данные, использовать GET, POST, PUT & DELETE, и есть куча дополнительных категорий для UIKit, которые позволяют, например, установить изображение из url. В сложном приложении с большим количеством вызовов мы иногда абстрагируем это до собственного метода удобства, который будет чем-то вроде:

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

есть несколько ситуаций, когда AFNetworking не подходит, однако, например, где вы создаете фреймворк или другой библиотечный компонент, как AFNetworking может уже быть в другой базе кода. В этом случае можно использовать nsmutableurlrequest либо встроенный, если вы делаете один вызов или абстрагируются в класс запроса / ответа.


Я избегаю синглетов при разработке моих приложений. Они типичны для многих людей, но я думаю, что вы можете найти более элегантные решения в другом месте. Обычно я создаю свои сущности в CoreData, а затем помещаю код REST в категорию NSManagedObject. Если бы, например, я хотел создать и опубликовать нового пользователя, я бы сделал это:

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

Я использую RESTKit для сопоставления объектов и инициализирую его при запуске. Я нахожу маршрутизацию всех твоих звонков через синглтон. чтобы быть пустой тратой времени и добавляет много шаблонных, которые не нужны.

В Расширениях NSManagedObject+.м:

+ (instancetype)createInContext:(NSManagedObjectContext*)context
{
    NSAssert(context.persistentStoreCoordinator.managedObjectModel.entitiesByName[[self entityName]] != nil, @"Entity with name %@ not found in model. Is your class name the same as your entity name?", [self entityName]);
    return [NSEntityDescription insertNewObjectForEntityForName:[self entityName] inManagedObjectContext:context];
}

В Сети NSManagedObject+.м:

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

зачем добавлять дополнительные вспомогательные классы, когда вы можете расширить функциональность базового класса через категории?

Если вас интересует более подробная информация о мое решение, дайте мне знать. Я рад поделиться.


попробуйте https://github.com/kevin0571/STNetTaskQueue

создание запросов API в отдельных классах.

STNetTaskQueue будет заниматься потоковой передачей и делегированием / обратным вызовом.

Extendable для различных протоколов.


с чисто классовой точки зрения дизайна у вас обычно будет что-то вроде этого:

  • код посмотреть контроллеры управление одним или несколькими видами
  • класс модели данных

    например, если у вас есть массив элементов для отображения в четырех различных представлениях (список, диаграмма, график и т. д.), Вы имейте один класс модели данных для списка элементов, Еще один для элемента. The список элементов класса будет совместно использоваться четырьмя контроллерами вида - все дочерние элементы контроллера панели вкладок или контроллера навигации.

    классы моделей данных пригодятся не только для отображения данных, но и для их сериализации, где каждый из них может предоставить свой собственный формат сериализации через методы экспорта JSON / XML / CSV (или что-либо еще).

  • важно поймите, что вам тоже нужно классы построителя запросов API эта карта непосредственно с конечными точками REST API. Предположим, у вас есть API, который регистрирует пользователя, поэтому ваш класс Login API builder создаст полезную нагрузку POST JSON для login api. В другом примере класс построителя запросов API для списка элементов каталога API создаст строку запроса GET для соответствующего api и запустит запрос GET REST.

    эти классы построителя запросов API обычно получают данные из представления контроллеры, а также передавать те же данные обратно для просмотра контроллеров для обновления пользовательского интерфейса / других операций. Затем контроллеры представления решат, как обновлять объекты модели данных с этими данными.

  • наконец, сердце остальных клиентов - API Data fetcher class который не обращает внимания на все виды запросов API, которые делает ваше приложение. Этот класс, скорее всего, будет одноэлементным, но, как указывали другие, он не должен быть одиночка.

    обратите внимание, что ссылка является просто типичной реализацией и не учитывает такие сценарии, как сессия, куки и т. д., Но этого достаточно, чтобы вы могли работать без использования каких-либо сторонних фреймворков.


на этот вопрос уже есть много отличных и обширных ответов, но я чувствую, что должен упомянуть об этом, так как никто другой не имеет.

Alamofire для Swift. https://github.com/Alamofire/Alamofire

Он создан теми же людьми, что и AFNetworking, но более непосредственно разработан с учетом Swift.