Всеки разработчик на iOS е запознат с Core Data, обектна графика и устойчивост на Apple. Освен постоянни данни локално, рамката се предлага с множество разширени функции, като проследяване на промяна на обекта и отмяна. Тези функции, макар и полезни в много случаи, не се предлагат безплатно. Той изисква много примерни кодове, а рамката като цяло има стръмна крива на обучение.
През 2014 г. Царство , мобилна база данни, беше пусната и завладя света на разработките. Ако всичко, от което се нуждаем, е да съхраняваме данни локално, Realm е добра алтернатива. В крайна сметка не всички случаи на употреба изискват разширените функции на Core Data. Realm е изключително лесен за използване и за разлика от Core Data, изисква много малко шаблонни кодове. Той също така е безопасен за нишки и се казва, че е по-бърз от рамката за постоянство от Apple.
В повечето съвременни мобилни приложения постоянните данни решават половината от проблема. Често трябва да извличаме данни от отдалечена услуга, обикновено чрез RESTful API. Това е където Мантия влиза в игра. Това е модел с отворен код за Cocoa и Cocoa Touch. Mantle значително опростява писането на модели данни за взаимодействие с API, които използват JSON като техния формат за обмен на данни.
В тази статия ще изградим приложение за iOS, което извлича списък със статии заедно с връзки към тях от API за търсене на статии на New York Times v2. Списъкът ще бъде извлечен с помощта на стандартна HTTP GET заявка, с модели на заявки и отговори, създадени с помощта на Mantle. Ще видим колко лесно е с Mantle да обработваме трансформации на стойности (напр. От NSDate към низ). След като данните бъдат извлечени, ние ще ги запазим локално, използвайки Realm. Всичко това с минимален код.
кога да използвате web api
Нека започнем със създаването на нов проект за Xcode „Master-Detail Application“ за iOS, наречен „RealmMantleTutorial“. Ще добавим рамки към него, използвайки CocoaPods. Подфайлът трябва да прилича на следното:
pod 'Mantle' pod 'Realm' pod 'AFNetworking'
След като се инсталират шушулките, можем да отворим новосъздадената MantleRealmTutorial работно пространство. Както забелязахте, известната рамка AFNetworking също е инсталирана. Ще го използваме за изпълнение на заявки към API.
Както бе споменато във въведението, New York Times предоставя отличен API за търсене на статии. За да го използвате, трябва да се регистрирате, за да получите ключ за достъп до API. Това може да стане на http://developer.nytimes.com . С API ключа в ръка сме готови да започнем с кодирането.
Преди да се задълбочим в създаването на модели на данни на Mantle, трябва да подготвим и изпълним нашия мрежов слой. Нека създадем нова група в Xcode и я наречем Мрежа. В тази група ще създадем два класа. Нека наречем първата SessionManager и се уверете, че е получено от AFHTTPSessionManager който е клас на мениджър на сесии от AFNetworking , възхитителната мрежова рамка. Нашите SessionManager class ще бъде единичен обект, който ще използваме за изпълнение на заявки за получаване на API. След като класът е създаден, моля, копирайте кода по-долу в интерфейс и файлове за изпълнение.
#import 'AFHTTPSessionManager.h' @interface SessionManager : AFHTTPSessionManager + (id)sharedManager; @end
#import 'SessionManager.h' static NSString *const kBaseURL = @'http://api.nytimes.com'; @implementation SessionManager - (id)init { self = [super initWithBaseURL:[NSURL URLWithString:kBaseURL]]; if(!self) return nil; self.responseSerializer = [AFJSONResponseSerializer serializer]; self.requestSerializer = [AFJSONRequestSerializer serializer]; return self; } + (id)sharedManager { static SessionManager *_sessionManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sessionManager = [[self alloc] init]; }); return _sessionManager; } @end
Мениджърът на сесии се инициализира с основния URL, дефиниран в статиката kBaseURL променлива. Той също така ще използва JSON сериализатори на заявки и отговори.
Сега вторият клас, който ще създадем в Мрежа ще бъде извикана група APIManager . Той ще бъде извлечен от новосъздадения ни SessionManager клас. След като бъдат създадени необходимите модели данни, ние ще добавим метод към ApiManager който ще се използва за заявяване на списък със статии от API.
Официалната документация за този отличен API е достъпна на http://developer.nytimes.com/…/article_search_api_v2 . Това, което ще направим, е да използваме следната крайна точка:
http://api.nytimes.com/svc/search/v2/articlesearch
... за извличане на статии, намерени с помощта на термин за търсене по наш избор, ограничен от период от време. Например, това, което бихме могли да направим, е да поискаме от API да върне списък на всички статии, появили се в New York Times, които имат нещо общо с баскетбола през първите седем дни на юли 2015 г. Според API документация , за да направим това, трябва да зададем следните параметри в заявката за получаване до тази крайна точка:
Параметър | Стойност |
Какво | 'баскетбол' |
начална_дата | „20150701“ |
крайна дата | „20150707“ |
Отговорът от API е доста сложен. По-долу е отговорът на заявка с горните параметри, ограничени само до една статия (един елемент в масива на документи) с много полета, пропуснати за яснота.
{ 'response': { 'docs': [ { 'web_url': 'http://www.nytimes.com/2015/07/04/sports/basketball/robin-lopez-and-knicks-are-close-to-a-deal.html', 'lead_paragraph': 'Lopez, a 7-foot center, joined Arron Afflalo, a 6-foot-5 guard, as the Knicks’ key acquisitions in free agency. He is expected to solidify the Knicks’ interior defense.', 'abstract': null, 'print_page': '1', 'source': 'The New York Times', 'pub_date': '2015-07-04T00:00:00Z', 'document_type': 'article', 'news_desk': 'Sports', 'section_name': 'Sports', 'subsection_name': 'Pro Basketball', 'type_of_material': 'News', '_id': '5596e7ac38f0d84c0655cb28', 'word_count': '879' } ] }, 'status': 'OK', 'copyright': 'Copyright (c) 2013 The New York Times Company. All Rights Reserved.' }
Това, което основно получаваме в отговор, са три полета. Първият се обади отговор съдържа масива документи , който от своя страна съдържа елементи, представляващи статии. Другите две полета са статус и Авторско право . Сега, когато знаем как работи API, е време да създадем модели на данни с помощта на Mantle.
Както бе споменато по-рано, Mantle е рамка с отворен код, която значително опростява писането на модели на данни. Нека започнем, като създадем модел на заявка за списък със статии. Нека наречем този клас ArticleListRequestModel и се уверете, че е получено от MTL Модел , което е клас, от който трябва да бъдат получени всички модели на Mantle. Освен това нека го приведем в съответствие с MTLJSONСериализиране протокол. Нашият модел за заявка трябва да има три свойства от подходящи типове: заявка, статии от дата , и articlesToDate . Само за да сме сигурни, че нашият проект е добре организиран, предлагам този клас да бъде поставен в Модели група.
Mantle опростява писането на модели с данни, намалява кода на шаблона. TweetЕто как интерфейсният файл на ArticleListRequestModel трябва да изглежда:
#import 'MTLModel.h' #import 'Mantle.h' @interface ArticleListRequestModel : MTLModel @property (nonatomic, copy) NSString *query; @property (nonatomic, copy) NSDate *articlesFromDate; @property (nonatomic, copy) NSDate *articlesToDate; @end
Сега, ако потърсим документите за нашата крайна точка за търсене на статии или погледнем таблицата с параметри на заявката по-горе, ще забележим, че имената на променливите в заявката за API се различават от тези в нашия модел на заявка. Mantle се справя ефективно с това, използвайки метода:
коя е добра практика за проектиране на приложения?
+ (NSDictionary *)JSONKeyPathsByPropertyKey.
Ето как трябва да се приложи този метод при изпълнението на нашия модел на заявка:
#import 'ArticleListRequestModel.h' @implementation ArticleListRequestModel #pragma mark - Mantle JSONKeyPathsByPropertyKey + (NSDictionary *)JSONKeyPathsByPropertyKey { return @{ @'query': @'q', @'articlesFromDate': @'begin_date', @'articlesToDate': @'end_date' }; } @end
Внедряването на този метод указва как свойствата на модела се картографират в неговите JSON представления. След като методът JSONKeyPathsByPropertyKey е реализиран, можем да получим представяне на речника на JSON на модела с метода на класа +[MTLJSONAdapter JSONArrayForModels:]
Едно нещо, което все още остава, както знаем от списъка с параметри, е, че и двата параметъра за дата трябва да бъдат във формата „ГГГГММДД“. Тук Mantle става много полезен. Можем да добавим персонализирана трансформация на стойност за всяко свойство чрез внедряване на незадължителен метод +JSONTransformer
Прилагайки го, ние казваме на Mantle как стойността на конкретно JSON поле трябва да се трансформира по време на десериализацията на JSON. Можем да внедрим и обратим трансформатор, който ще се използва при създаване на JSON от модела. Тъй като трябва да трансформираме NSDate обект в низ, ние също ще използваме NSDataFormatter клас. Ето пълното изпълнение на ArticleListRequestModel клас:
#import 'ArticleListRequestModel.h' @implementation ArticleListRequestModel + (NSDateFormatter *)dateFormatter { NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateFormat = @'yyyyMMdd'; return dateFormatter; } #pragma mark - Mantle JSONKeyPathsByPropertyKey + (NSDictionary *)JSONKeyPathsByPropertyKey { return @{ @'query': @'q', @'articlesFromDate': @'begin_date', @'articlesToDate': @'end_date' }; } #pragma mark - JSON Transformers + (NSValueTransformer *)articlesToDateJSONTransformer { return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *dateString, BOOL *success, NSError *__autoreleasing *error) { return [self.dateFormatter dateFromString:dateString]; } reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) { return [self.dateFormatter stringFromDate:date]; }]; } + (NSValueTransformer *)articlesFromDateJSONTransformer { return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *dateString, BOOL *success, NSError *__autoreleasing *error) { return [self.dateFormatter dateFromString:dateString]; } reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) { return [self.dateFormatter stringFromDate:date]; }]; } @end
Друга чудесна характеристика на Mantle е, че всички тези модели отговарят на NSC кодиране протокол, както и изпълнение е равно и хеш методи.
Както вече видяхме, полученият JSON от извикването на API съдържа масив от обекти, които представляват статии. Ако искаме да моделираме този отговор с помощта на Mantle, ще трябва да създадем два отделни модела на данни. Човек би моделирал обекти, представляващи статии ( документи array elements), а другият би моделирал целия JSON отговор, с изключение на елементите на масива docs. Сега не трябва да картографираме всяко свойство от входящия JSON в нашите модели на данни. Да предположим, че се интересуваме само от две полета на обекти на статии и това би било оловен_параграф и web_url . The ArticleModel class е доста лесно за изпълнение, както можем да видим по-долу.
#import 'MTLModel.h' #import @interface ArticleModel : MTLModel @property (nonatomic, copy) NSString *leadParagraph; @property (nonatomic, copy) NSString *url; @end
#import 'ArticleModel.h' @implementation ArticleModel #pragma mark - Mantle JSONKeyPathsByPropertyKey + (NSDictionary *)JSONKeyPathsByPropertyKey { return @{ @'leadParagraph': @'lead_paragraph', @'url': @'web_url' }; } @end
Сега, когато моделът на статията е дефиниран, можем да завършим дефиницията на модел на отговор, като създадем модел за списъка със статии. Ето как ще изглежда моделът за отговор на класа ArticleList.
#import 'MTLModel.h' #import #import 'ArticleModel.h' @interface ArticleListResponseModel : MTLModel @property (nonatomic, copy) NSArray *articles; @property (nonatomic, copy) NSString *status; @end
#import 'ArticleListResponseModel.h' @class ArticleModel; @implementation ArticleListResponseModel #pragma mark - Mantle JSONKeyPathsByPropertyKey + (NSDictionary *)JSONKeyPathsByPropertyKey { return @{ @'articles' : @'response.docs', @'status' : @'status' }; } #pragma mark - JSON Transformer + (NSValueTransformer *)articlesJSONTransformer { return [MTLJSONAdapter arrayTransformerWithModelClass:ArticleModel.class]; } @end
Този клас има само две свойства: статус и статии . Ако го сравним с отговора от крайната точка, ще видим, че третият JSON атрибут авторско право няма да бъде картографиран в модела на отговор. Ако разгледаме статииJSONTransformer метод, ще видим, че връща трансформатор на стойност за масив, съдържащ обекти от клас ArticleModel .
Също така си струва да се отбележи, че в метода JSONKeyPathsByPropertyKey , свойството на модела статии съответстват на масивите docs, които са вложени в атрибута JSON отговор .
Досега трябва да имаме внедрени три моделни класа: ArticleListRequestModel, ArticleModel и ArticleListResponseModel.
създаване на частен капиталов фонд
Сега, когато внедрихме всички модели на данни, е време да се върнем към класа APIManager за внедряване на метода, който ще използваме за изпълнение на GET заявки към API. Методът:
- (NSURLSessionDataTask *) getArticlesWithRequestModel:(ArticleListRequestModel *)requestModel success:(void (^)(ArticleListResponseModel *responseModel))success failure:(void (^)(NSError *error))failure
отнема ArticleListRequestModel заявка модел като параметър и връща ArticleListResponseModel в случай на успех или NSError в противен случай. Прилагането на този метод използва AFNetworking да изпълни GET заявка към API. Моля, обърнете внимание, че за да направим успешна заявка за API, трябва да предоставим ключ, който може да бъде получен, както беше споменато по-рано, като се регистрирате на http://developer.nytimes.com .
#import 'SessionManager.h' #import 'ArticleListRequestModel.h' #import 'ArticleListResponseModel.h' @interface APIManager : SessionManager - (NSURLSessionDataTask *)getArticlesWithRequestModel:(ArticleListRequestModel *)requestModel success:(void (^)(ArticleListResponseModel *responseModel))success failure:(void (^)(NSError *error))failure; @end
#import 'APIManager.h' #import 'Mantle.h' static NSString *const kArticlesListPath = @'/svc/search/v2/articlesearch.json'; static NSString *const kApiKey = @'replace this with your own key'; @implementation APIManager - (NSURLSessionDataTask *)getArticlesWithRequestModel:(ArticleListRequestModel *)requestModel success:(void (^)(ArticleListResponseModel *responseModel))success failure:(void (^)(NSError *error))failure{ NSDictionary *parameters = [MTLJSONAdapter JSONDictionaryFromModel:requestModel error:nil]; NSMutableDictionary *parametersWithKey = [[NSMutableDictionary alloc] initWithDictionary:parameters]; [parametersWithKey setObject:kApiKey forKey:@'api-key']; return [self GET:kArticlesListPath parameters:parametersWithKey success:^(NSURLSessionDataTask *task, id responseObject) { NSDictionary *responseDictionary = (NSDictionary *)responseObject; NSError *error; ArticleListResponseModel *list = [MTLJSONAdapter modelOfClass:ArticleListResponseModel.class fromJSONDictionary:responseDictionary error:&error]; success(list); } failure:^(NSURLSessionDataTask *task, NSError *error) { failure(error); }]; }
При прилагането на този метод се случват две много важни неща. Първо нека да разгледаме този ред:
NSDictionary *parameters = [MTLJSONAdapter JSONDictionaryFromModel:requestModel error:nil];
Това, което се случва тук е, че използвайки метода, предоставен от MTLJSONAdapter клас получаваме a NSD Dictionary представяне на нашия модел данни. Това представяне отразява JSON, който ще бъде изпратен до API. Тук се крие красотата на Mantle. След като се внедри JSONKeyPathsByPropertyKey и +JSONTransformer
методи в класа ArticleListRequestModel, можем за нула време да получим правилното JSON представяне на нашия модел данни само с един ред код.
Mantle също ни позволява да извършваме трансформации и в другата посока. И точно това се случва с данните, получени от API. Полученият от нас NSD Dictionary се преобразува в обект на класа ArticleListResponseModel, използвайки следния метод на класа:
ArticleListResponseModel *list = [MTLJSONAdapter modelOfClass:ArticleListResponseModel.class fromJSONDictionary:responseDictionary error:&error];
Сега, когато сме в състояние да извлечем данни от отдалечен API, е време да ги продължим. Както бе споменато във въведението, ще го направим с помощта на Realm. Realm е мобилна база данни и заместител на Core Data и SQLite. Както ще видим по-долу, той е изключително лесен за използване.
Realm, най-добрата мобилна база данни, е идеалният заместител на Core Data и SQLite. TweetЗа да запазим част от данните в Realm, първо трябва да капсулираме обект, който е получен от клас RLMObject. Това, което трябва да направим сега, е да създадем клас на модел, който да съхранява данни за отделни статии. Ето колко лесно е да създадете такъв клас.
#import 'RLMObject.h' @interface ArticleRealm : RLMObject @property NSString *leadParagraph; @property NSString *url; @end
И това може да бъде основно това, изпълнението на този клас може да остане празно. Моля, обърнете внимание, че свойствата в класа на модела нямат атрибути като неатомно, силно или копиране. Realm се грижи за тях и не е нужно да се притесняваме за тях.
Тъй като статиите, които можем да получим, са моделирани с модела Mante Член би било удобно да се инициализира ArticleRealm обекти с обекти от клас Член . За целта ще добавим initWithMantleModel метод към нашия Realm модел. Ето пълното изпълнение на ArticleRealm клас.
#import 'RLMObject.h' #import 'ArticleModel.h' @interface ArticleRealm : RLMObject @property NSString *leadParagraph; @property NSString *url; - (id)initWithMantleModel:(ArticleModel *)articleModel; @end
#import 'ArticleRealm.h' @implementation ArticleRealm - (id)initWithMantleModel:(ArticleModel *)articleModel{ self = [super init]; if(!self) return nil; self.leadParagraph = articleModel.leadParagraph; self.url = articleModel.url; return self; } @end
Взаимодействаме с базата данни, използвайки обекти от клас RLM Реално . Лесно можем да получим RLM Реално обект чрез извикване на метода “[RLMRealm defaultRealm]”. Важно е да запомните, че такъв обект е валиден само в нишката, в която е създаден и не може да бъде споделен между нишките. Записването на данни в Realm е доста лесно. Едно записване или поредица от тях трябва да се извърши в рамките на транзакция за запис. Ето пример за запис в базата данни:
RLMRealm *realm = [RLMRealm defaultRealm]; ArticleRealm *articleRealm = [ArticleRealm new]; articleRealm.leadParagraph = @'abc'; articleRealm.url = @'sampleUrl'; [realm beginWriteTransaction]; [realm addObject:articleRealm]; [realm commitWriteTransaction];
Това, което се случва тук, е следното. Първо създаваме a RLM Реално обект за взаимодействие с базата данни. Тогава an ArticleRealm създава се обект на модел (моля, имайте предвид, че е получен от RLM Реално клас). И накрая, за да го запишете, започва транзакция за запис, обектът се добавя към базата данни и след като бъде запазен, транзакцията за запис е ангажирана. Както виждаме, транзакциите за запис блокират нишката, на която са извикани. Докато Realm се казва, че е много бърз, ако трябва да добавим множество обекти към базата данни в рамките на една транзакция на основната нишка, това може да доведе до това потребителският интерфейс да не реагира, докато транзакцията приключи. Естествено решение за това е да се извърши такава транзакция за запис на фонова нишка.
Това е цялата информация, от която се нуждаем, за да продължим статиите, използващи Realm. Нека се опитаме да изпълним заявка за API, използвайки метода
- (NSURLSessionDataTask *) getArticlesWithRequestModel:(ArticleListRequestModel *)requestModel success:(void (^)(ArticleListResponseModel *responseModel))success failure:(void (^)(NSError *error))failure
и модели за искане и отговор на Mantle, за да се получат статии от Ню Йорк Таймс, които имат нещо общо (както в предишния пример) с баскетбола и са публикувани през първите седем дни на юни 2015 г. След като списъкът с такива статии е наличен, ще го упорства в царството. По-долу е кодът, който прави това. Поставя се в viewDidLoad метод на контролера за табличен изглед в нашето приложение.
стратегии за управление на валутния риск
ArticleListRequestModel *requestModel = [ArticleListRequestModel new]; // (1) requestModel.query = @'Basketball'; requestModel.articlesToDate = [[ArticleListRequestModel dateFormatter] dateFromString:@'20150706']; requestModel.articlesFromDate = [[ArticleListRequestModel dateFormatter] dateFromString:@'20150701']; [[APIManager sharedManager] getArticlesWithRequestModel:requestModel // (2) success:^(ArticleListResponseModel *responseModel){ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // (3) @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm deleteAllObjects]; [realm commitWriteTransaction]; [realm beginWriteTransaction]; for(ArticleModel *article in responseModel.articles){ ArticleRealm *articleRealm = [[ArticleRealm alloc] initWithMantleModel:article]; // (4) [realm addObject:articleRealm]; } [realm commitWriteTransaction]; dispatch_async(dispatch_get_main_queue(), ^{ // (5) RLMRealm *realmMainThread = [RLMRealm defaultRealm]; // (6) RLMResults *articles = [ArticleRealm allObjectsInRealm:realmMainThread]; self.articles = articles; // (7) [self.tableView reloadData]; }); } }); } failure:^(NSError *error) { self.articles = [ArticleRealm allObjects]; [self.tableView reloadData]; }];
Първо се извършва API извикване (2) с модел на заявка (1), който връща модел на отговор, който съдържа списък със статии. За да се запазят тези статии, използващи Realm, трябва да създадем обекти на Realm модел, което се извършва в цикъла for (4). Също така е важно да се отбележи, че тъй като множество обекти се запазват в рамките на една транзакция за запис, тази транзакция за запис се извършва на фонова нишка (3). След като всички статии са запазени в Realm, ние ги присвояваме на свойството на класа самостоятелни статии (7). Тъй като те ще бъдат достъпни по-късно в основната нишка в методите на източника на данни TableView, е безопасно да ги изтеглите и от базата данни Realm в основната нишка (5). Отново, за достъп до базата данни от нова нишка, трябва да се създаде нов обект RLMRealm (6) на тази нишка.
Ако получаването на нови статии от API не успее по някаква причина, съществуващите се извличат от локалното хранилище в блока за повреда.
В този урок научихме как да конфигурираме Mantle, модел на рамка за Cocoa и Cocoa Touch, за да взаимодействаме с дистанционно ПОЖАР . Също така научихме как да запазим локално данните, извлечени под формата на обекти на модел на Mantle, използвайки мобилна база данни Realm.
В случай, че искате да изпробвате това приложение, можете да извлечете изходния код от неговото хранилище на GitHub . Ще трябва да генерирате и предоставите свой собствен API ключ, преди да стартирате приложението.