Що се отнася до архитектурните модели на iOS, моделът на модел-изглед-контролер (MVC) е чудесен за дълголетието и поддръжката на кодовата база на приложението. Позволява класовете лесно да бъдат използвани повторно или заменени, за да поддържат различни изисквания, като ги отделят един от друг. Това помага да се максимизират предимствата на обектно-ориентираното програмиране (ООП).
Въпреки че тази архитектура на приложения за iOS работи добре на микро ниво (отделни екрани / секции на приложение), може да откриете, че добавяте подобни функции към множество модели, докато приложението ви расте. В случаи като работа в мрежа, преместването на обща логика от класовете на моделите ви в помощни класове за единичен модел може да бъде по-добър подход. В този урок за AFNetworking iOS ще ви науча как да настроите централизиран единичен обективен мрежов обект, който отделен от компонентите на MVC на микро ниво, може да бъде използван повторно във вашето приложение за отделена архитектура.
направено не е функция
Apple е свършила чудесна работа, като е абстрахирала много от сложността на управлението на мобилния хардуер в лесни за използване iOS SDK, но в някои случаи, като работа в мрежа, Bluetooth, OpenGL и обработка на мултимедия, класовете могат да бъдат тромави поради целта им да запазят гъвкавите SDK. За щастие, богатите разработчик на iOS общността е създала рамки на високо ниво, за да опрости най-често използваните случаи в опит да опрости дизайна и структурата на приложенията. Добрият програмист, използващ най-добрите практики за архитектура на приложения на ios, знае кои инструменти да използва, защо да ги използва и кога е по-добре да пишете свои собствени инструменти и класове от нулата.
AFNetworking е чудесен пример за работа в мрежа и една от най-често използваните рамки с отворен код, която опростява ежедневните задачи на разработчика. Той опростява RESTful API мрежата и създава модулни модели на заявка / отговор с блокове за успех, напредък и неуспех. Това елиминира необходимостта от внедрени от разработчика методи за делегиране и персонализирани настройки за заявка / връзка и могат да бъдат включени във всеки клас много бързо.
AFNetworking е страхотен, но неговата модулност също може да доведе до използването му по фрагментирани начини. Честото неефективно изпълнение може да включва:
Множество мрежови заявки, използващи подобни методи и свойства в един контролер за изглед
Почти идентични заявки в множество контролери за изглед, които водят до разпределени общи променливи, които могат да излязат от синхронизация
Мрежови заявки в клас за данни, несвързани с този клас
За приложения с ограничен брой изгледи, няколко API повиквания за изпълнение и такива, които е малко вероятно да се променят често, това може да не е от голямо безпокойство. По-вероятно обаче мислите мащабно и планирате много години актуализации. Ако вашият случай е последният, най-вероятно ще се наложи да се справите:
Версиониране на API за поддържане на множество поколения приложения
Добавяне на нови параметри или промени на съществуващи параметри с течение на времето за разширяване на възможностите
Внедряване на напълно нови API
Ако вашият мрежов код е разпръснат из цялата ви кодова база, това вече е потенциален кошмар. Надяваме се, че поне някои от вашите параметри са дефинирани статично в общ хедър, но дори тогава можете да докоснете дузина класове дори за най-малките промени.
Създайте мрежов сингълтон за централизиране на обработката на заявки, отговори и техните параметри.
Един единичен обект осигурява глобална точка за достъп до ресурсите на своя клас. Единичните се използват в ситуации, когато тази единична контролна точка е желателна, като например при класове, които предлагат някаква обща услуга или ресурс. Получавате глобалния екземпляр от единичен клас чрез фабричен метод. - Apple
И така, сингълтонът е клас, който бихте имали само един екземпляр в приложение, което съществува през целия живот на приложението. Освен това, тъй като знаем, че има само един екземпляр, той е лесно достъпен от всеки друг клас, който се нуждае от достъп до своите методи или свойства.
Ето защо трябва да използваме сингълтон за работа в мрежа:
Той е инициализиран статично, така че веднъж създаден, той ще има същите методи и свойства, достъпни за всеки клас, който се опитва да получи достъп до него. Няма шанс за странни проблеми със синхронизацията или изискване на данни от грешен екземпляр на клас.
Можете да ограничите извикванията си за API, за да останете под лимит на скоростта (например, когато трябва да поддържате заявките си за API под пет в секунда).
Статичните свойства като име на хост, номера на портове, крайни точки, версия на API, тип устройство, постоянни идентификатори, размер на екрана и др. Могат да бъдат разположени съвместно, така че една промяна засяга всички мрежови заявки.
Общите свойства могат да бъдат използвани повторно между много мрежови заявки.
Единичният обект не заема памет, докато не бъде инстанциран. Това може да бъде полезно за единични потребители с много специфични случаи на употреба, от които някои потребители може никога да не се нуждаят, като например обработка на прехвърляне на видео към Chromecast, ако нямат устройството.
Мрежовите заявки могат да бъдат напълно отделени от изгледите и контролерите, така че да могат да продължат дори след като изгледите и контролерите бъдат унищожени.
Мрежовото регистриране може да бъде централизирано и опростено.
Честите събития за отказ, като предупреждения, могат да бъдат използвани повторно за всички заявки.
Основната структура на такъв сингълтон може да бъде използвана повторно за множество проекти с прости промени на статични свойства на най-високо ниво.
Някои причини да не се използват единични:
Те могат да бъдат преувеличени, за да осигурят множество отговорности в един клас. Например методите за видеообработка могат да бъдат смесени с мрежови методи или методи на потребителско състояние. Това вероятно би било лоша практика на проектиране и би довело до трудно разбираем код. Вместо това трябва да се създадат множество единични със специфични отговорности.
Единичните точки не могат да бъдат подкласирани.
Единичните могат да скрият зависимости и по този начин да станат по-малко модулни. Например, ако сингълтон е премахнат и в клас липсва импорт, който единичният импорт е импортирал, това може да доведе до бъдещи проблеми (особено ако има външни библиотечни зависимости).
Клас може да модифицира споделени свойства в единични елементи по време на дълги операции, които са неочаквани в друг клас. Без да се помисли добре, резултатите могат да варират.
Изтичането на памет в единичен файл може да се превърне в значителен проблем, тъй като самият сингълън никога не се отменя.
Въпреки това, използвайки най-добрите практики за архитектура на приложения за iOS, тези негативи могат да бъдат облекчени. Няколко най-добри практики включват:
Всеки един човек трябва да носи една отговорност.
Не използвайте единични тонове за съхраняване на данни, които ще бъдат бързо променени от множество класове или нишки, ако имате нужда от висока точност.
Изградете единични елементи, за да активирате / деактивирате функции въз основа на наличните зависимости.
Не съхранявайте големи количества данни в единични свойства, тъй като те ще останат за цял живот на вашето приложение (освен ако не се управляват ръчно).
Първо, като предпоставка, добавете AFNetworking към вашия проект. Най-простият подход е чрез Cocoapods и в него се намират инструкции Страница на GitHub .
Докато сте готови, бих предложил да добавите UIAlertController+Blocks
и MBProgressHUD
(отново лесно се добавя с CocoaPods). Те очевидно не са задължителни, но това значително ще опрости напредъка и предупрежденията, ако искате да ги внедрите в единичния прозорец на прозореца AppDelegate.
llc s или c корпорация
Веднъж AFNetworking
се добавя, започнете със създаването на нов клас какао докосване, наречен NetworkManager
като подклас на NSObject
. Добавете метод на клас за достъп до мениджъра. Вашият NetworkManager.h
файлът трябва да изглежда като кода по-долу:
#import #import “AFNetworking.h” @interface NetworkManager : NSObject + (id)sharedManager; @end
След това внедрете основните методи за инициализация за сингълтона и импортирайте заглавката AFNetworking. Вашата реализация на класа трябва да изглежда по следния начин (ЗАБЕЛЕЖКА: Това предполага, че използвате автоматично преброяване на референции):
#import 'NetworkManager.h' @interface NetworkManager() @end @implementation NetworkManager #pragma mark - #pragma mark Constructors static NetworkManager *sharedManager = nil; + (NetworkManager*)sharedManager { static dispatch_once_t once; dispatch_once(&once, ^ { sharedManager = [[NetworkManager alloc] init]; }); return sharedManager; } - (id)init { if ((self = [super init])) { } return self; } @end
Страхотен! Сега готвим и сме готови да добавим свойства и методи. Като бърз тест, за да разберем как да осъществим достъп до сингълтон, нека добавим следното към NetworkManager.h
:
@property NSString *appID; - (void)test;
И следното към NetworkManager.m
:
#define HOST @”http://www.apitesting.dev/” static const in port = 80; … @implementation NetworkManager … //Set an initial property to init: - (id)init { if ((self = [super init])) { self.appID = @”1”; } return self; } - (void)test { NSLog(@”Testing out the networking singleton for appID: %@, HOST: %@, and PORT: %d”, self.appID, HOST, port); }
Тогава в нашия основен ViewController.m
файл (или каквото имате), импортирайте NetworkManager.h
и след това в viewDidLoad
добави:
[[NetworkManager sharedManager] test];
Стартирайте приложението и трябва да видите следното в резултата:
Testing our the networking singleton for appID: 1, HOST: http://www.apitesting.dev/, and PORT: 80
Добре, така че вероятно няма да смесвате #define
, статичен const и @property
наведнъж по този начин, но просто показвайки за яснота вашите възможности. „Static const“ е по-добра декларация за безопасност на типа, но #define
може да бъде полезен при изграждането на низове, тъй като позволява използването на макроси. За това, което си струва, използвам #define
за краткост в този сценарий. Освен ако не използвате указатели, няма голяма практическа разлика между тези подходи за деклариране.
Сега, когато разбирате #defines
, константи, свойства и методи, можем да ги премахнем и да преминем към по-подходящи примери.
Представете си приложение, в което потребителят трябва да е влязъл, за да има достъп до каквото и да било. При стартиране на приложението ще проверим дали сме запазили токен за удостоверяване и ако е така, изпълняваме GET заявка към нашия API, за да видим дали токенът е изтекъл или не.
В AppDelegate.m
, нека регистрираме по подразбиране за нашия токен:
+ (void)initialize { NSDictionary *defaults = [NSDictionary dictionaryWithObjectsAndKeys:@'', @'token', nil]; [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; }
Ще добавим проверка на маркера към NetworkManager и ще получим обратна връзка за проверката чрез блокове за завършване. Можете да проектирате тези блокове за завършване, както искате. В този пример използвам успех с данните на обекта за отговор и неуспех с низа за отговор на грешка и код на състоянието. Забележка: Неизправността може да бъде пропусната по избор, ако това няма значение за приемащата страна, като например увеличаване на стойност в анализа.
оптимизиране на sql заявките за производителност
NetworkManager.h
Отгоре @interface
:
typedef void (^NetworkManagerSuccess)(id responseObject); typedef void (^NetworkManagerFailure)(NSString *failureReason, NSInteger statusCode);
В @interface:
@property (неатомичен, силен) AFHTTPSessionManager * networkingManager;
- (void)tokenCheckWithSuccess:(NetworkManagerSuccess)success failure:(NetworkManagerFailure)failure;
NetworkManager.m:
Определете нашия BASE_URL:
#define ENABLE_SSL 1 #define HOST @'http://www.apitesting.dev/' #define PROTOCOL (ENABLE_SSL ? @'https://' : @'http://') #define PORT @'80' #define BASE_URL [NSString stringWithFormat:@'%@%@:%@', PROTOCOL, HOST, PORT]
Ще добавим няколко помощни метода за улесняване на удостоверените заявки, както и грешки при анализирането (този пример използва уеб токен JSON):
- (AFHTTPSessionManager*)getNetworkingManagerWithToken:(NSString*)token { if (self.networkingManager == nil) { self.networkingManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:BASE_URL]]; if (token != nil && [token length] > 0) { NSString *headerToken = [NSString stringWithFormat:@'%@ %@', @'JWT', token]; [self.networkingManager.requestSerializer setValue:headerToken forHTTPHeaderField:@'Authorization']; // Example - [networkingManager.requestSerializer setValue:@'application/json' forHTTPHeaderField:@'Content-Type']; } self.networkingManager.requestSerializer = [AFJSONRequestSerializer serializer]; self.networkingManager.responseSerializer.acceptableContentTypes = [self.networkingManager.responseSerializer.acceptableContentTypes setByAddingObjectsFromArray:@[@'text/html', @'application/json', @'text/json']]; self.networkingManager.securityPolicy = [self getSecurityPolicy]; } return self.networkingManager; } - (id)getSecurityPolicy { return [AFSecurityPolicy defaultPolicy]; /* Example - AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone]; [policy setAllowInvalidCertificates:YES]; [policy setValidatesDomainName:NO]; return policy; */ } - (NSString*)getError:(NSError*)error { if (error != nil) { NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey]; NSDictionary *responseObject = [NSJSONSerialization JSONObjectWithData: errorData options:kNilOptions error:nil]; if (responseObject != nil && [responseObject isKindOfClass:[NSDictionary class]] && [responseObject objectForKey:@'message'] != nil && [[responseObject objectForKey:@'message'] length] > 0) { return [responseObject objectForKey:@'message']; } } return @'Server Error. Please try again later'; }
Ако сте добавили MBProgressHUD, той може да се използва тук:
#import 'MBProgressHUD.h' @interface NetworkManager() @property (nonatomic, strong) MBProgressHUD *progressHUD; @end … - (void)showProgressHUD { [self hideProgressHUD]; self.progressHUD = [MBProgressHUD showHUDAddedTo:[[UIApplication sharedApplication] delegate].window animated:YES]; [self.progressHUD removeFromSuperViewOnHide]; self.progressHUD.bezelView.color = [UIColor colorWithWhite:0.0 alpha:1.0]; self.progressHUD.contentColor = [UIColor whiteColor]; } - (void)hideProgressHUD { if (self.progressHUD != nil) { [self.progressHUD hideAnimated:YES]; [self.progressHUD removeFromSuperview]; self.progressHUD = nil; } }
И нашата заявка за проверка на токени:
- (void)tokenCheckWithSuccess:(NetworkManagerSuccess)success failure:(NetworkManagerFailure)failure { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString *token = [defaults objectForKey:@'token']; if (token == nil || [token length] == 0) { if (failure != nil) { failure(@'Invalid Token', -1); } return; } [self showProgressHUD]; NSMutableDictionary *params = [NSMutableDictionary dictionary]; [[self getNetworkingManagerWithToken:token] GET:@'/checktoken' parameters:params progress:nil success:^(NSURLSessionTask *task, id responseObject) { [self hideProgressHUD]; if (success != nil) { success(responseObject); } } failure:^(NSURLSessionTask *operation, NSError *error) { [self hideProgressHUD]; NSString *errorMessage = [self getError:error]; if (failure != nil) { failure(errorMessage, ((NSHTTPURLResponse*)operation.response).statusCode); } }]; }
Сега, в метода ViewController.m viewWillAppear, ще извикаме този единичен метод. Забележете простотата на заявката и малката реализация от страна на View Controller.
[[NetworkManager sharedManager] tokenCheckWithSuccess:^(id responseObject) { // Allow User Access and load content //[self loadContent]; } failure:^(NSString *failureReason, NSInteger statusCode) { // Logout user if logged in and deny access and show login view //[self showLoginView]; }];
Това е! Забележете как този фрагмент може да се използва на практика във всяко приложение, което трябва да провери удостоверяването при стартиране.
По същия начин можем да обработваме POST заявка за вход: NetworkManager.h:
- (void)authenticateWithEmail:(NSString*)email password:(NSString*)password success:(NetworkManagerSuccess)success failure:(NetworkManagerFailure)failure;
NetworkManager.m:
- (void)authenticateWithEmail:(NSString*)email password:(NSString*)password success:(NetworkManagerSuccess)success failure:(NetworkManagerFailure)failure { if (email != nil && [email length] > 0 && password != nil && [password length] > 0) { [self showProgressHUD]; NSMutableDictionary *params = [NSMutableDictionary dictionary]; [params setObject:email forKey:@'email']; [params setObject:password forKey:@'password']; [[self getNetworkingManagerWithToken:nil] POST:@'/authenticate' parameters:params progress:nil success:^(NSURLSessionTask *task, id responseObject) { [self hideProgressHUD]; if (success != nil) { success(responseObject); } } failure:^(NSURLSessionTask *operation, NSError *error) { [self hideProgressHUD]; NSString *errorMessage = [self getError:error]; if (failure != nil) { failure(errorMessage, ((NSHTTPURLResponse*)operation.response).statusCode); } }]; } else { if (failure != nil) { failure(@'Email and Password Required', -1); } } }
Тук можем да получим фантазия и да добавим сигнали с AlertController + Blocks в прозореца AppDelegate или просто да изпратим обекти на повреда обратно към контролера на изгледа. Освен това можем да запазим потребителските идентификационни данни тук или вместо това да позволим на контролера на изгледа да се справи с това. Обикновено внедрявам отделен сингтон UserManager, който обработва идентификационни данни и разрешения, които могат да комуникират директно с NetworkManager (лични предпочитания).
Още веднъж, страната на контролера на изгледа е супер проста:
- (void)loginUser { NSString *email = @' [email protected] '; NSString *password = @'SomeSillyEasyPassword555'; [[NetworkManager sharedManager] authenticateWithEmail:email password:password success:^(id responseObject) { // Save User Credentials and show content } failure:^(NSString *failureReason, NSInteger statusCode) { // Explain to user why authentication failed }]; }
Ами сега! Забравихме да актуализираме API и да изпратим типа устройство. Освен това актуализирахме крайната точка от „/ checktoken“ на „/ token“. Тъй като ние централизирахме нашата мрежа, това е супер лесно за актуализиране. Не е нужно да копаем в нашия код. Тъй като ще използваме тези параметри за всички заявки, ще създадем помощник.
#define API_VERSION @'1.0' #define DEVICE_TYPE UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? @'tablet' : @'phone' - (NSMutableDictionary*)getBaseParams { NSMutableDictionary *baseParams = [NSMutableDictionary dictionary]; [baseParams setObject:@'version' forKey:API_VERSION]; [baseParams setObject:@'device_type' forKey:DEVICE_TYPE]; return baseParams; }
Няколко често срещани параметри могат лесно да бъдат добавени към това в бъдеще. След това можем да актуализираме нашите методи за проверка и удостоверяване по следния начин:
… NSMutableDictionary *params = [self getBaseParams]; [[self getNetworkingManagerWithToken:token] GET:@'/checktoken' parameters:params progress:nil success:^(NSURLSessionTask *task, id responseObject) { … … NSMutableDictionary *params = [self getBaseParams]; [params setObject:email forKey:@'email']; [params setObject:password forKey:@'password']; [[self getNetworkingManagerWithToken:nil] POST:@'/authenticate' parameters:params progress:nil success:^(NSURLSessionTask *task, id responseObject) {
Ще спрем до тук, но, както можете да видите, ние сме централизирали общи мрежови параметри и методи в един мениджър, което значително опрости внедряванията на нашия изглед на контролер. Бъдещите актуализации ще бъдат лесни и бързи и най-важното ще отделят нашата мрежа от потребителския опит. Следващият път, когато дизайнерският екип поиска ремонт на UI / UX, ще знаем, че нашата работа вече е свършена от страна на мрежата!
В тази статия се фокусирахме върху мрежов сингълтън, но същите тези принципи могат да бъдат приложени към много други централизирани функции като:
Ние също се фокусирахме върху архитектура на приложение за iOS, но това може също толкова лесно да бъде разширено до Android и дори JavaScript. Като бонус, като създава силно дефиниран и функционално ориентиран код, той прави пренасянето на приложения към нови платформи много по-бърза задача.
В обобщение, като отделите малко допълнително време в ранното планиране на проекти за създаване на ключови единични методи, като например мрежовия пример по-горе, вашият бъдещ код може да бъде по-чист, по-опростен и по-поддържаем.
AFNetworking е мрежова библиотека с отворен код за iOS и macOS, която опростява задачите на разработчика с RESTful мрежов API и създава модулни модели на заявки / отговори с блокове за успех, напредък и неуспех. Той има много активна общност на разработчици и се използва в някои от най-добрите приложения.
Единичен обект е клас, който бихте имали само един екземпляр в приложение, което съществува за целия живот на приложението. Освен това, тъй като знаем, че има само един екземпляр, той е лесно достъпен от всеки друг клас, който се нуждае от достъп до своите методи или свойства.