本文概述
- RESTful API-入门
- 纽约时报文章搜索API概述
- Mantle简介
- 第一个API请求
- 使用领域持久化数据
- API请求和领域中的持久响应
- 本文总结
在2014年, 境界, 移动数据库, 发布和风靡世界的发展。如果我们需要的是在本地持久化数据, 境界是一个很好的选择。毕竟, 并非所有用例都需要Core Data的高级功能。境界是非常容易使用, 而不是核心数据, 只需要很少的样板代码。这也是线程安全的, 据说是比苹果的持久化框架更快。
在大多数现代移动应用程序中, 持久数据解决了一半的问题。通常, 我们通常需要通过RESTful API从远程服务中获取数据。这是Mantle发挥作用的地方。它是针对Cocoa和Cocoa Touch的开源模型框架。 Mantle极大地简化了与使用JSON作为数据交换格式的API进行交互的编写数据模型。
文章图片
在本文中, 我们将构建一个iOS应用程序, 该应用程序将从《纽约时报》文章搜索API v2中获取文章列表以及指向这些文章的链接。将使用标准HTTP GET请求以及使用Mantle创建的请求和响应模型来获取列表。我们将看到使用Mantle处理值转换(例如, 从NSDate到字符串)有多么容易。提取数据后, 我们将使用Realm在本地将其持久化。所有这些只需最少的样板代码即可。
RESTful API-入门 首先, 为iOS创建一个名为” RealmMantleTutorial” 的新的” 主从应用程序” Xcode项目。我们将使用CocoaPods向其添加框架。 podfile应该类似于以下内容:
pod 'Mantle'
pod 'Realm'
pod 'AFNetworking'
安装Pod后, 我们可以打开新创建的MantleRealmTutorial工作区。如你所见, 著名的AFNetworking框架也已安装。我们将使用它来执行对API的请求。
如引言中所述, 《纽约时报》提供了出色的文章搜索API。为了使用它, 需要注册以获得API的访问密钥。可以在http://developer.nytimes.com上完成。有了API密钥, 我们就可以开始进行编码了。
在深入研究创建Mantle数据模型之前, 我们需要启动并运行我们的网络层。让我们在Xcode中创建一个新组, 并将其命名为” 网络” 。在这个小组中, 我们将创建两个类。让我们调用第一个SessionManager并确保它是从AFHTTPSessionManager派生的, AFHTTPSessionManager是来自AFNetworking(令人愉快的网络框架)的会话管理器类。我们的SessionManager类将是一个单例对象, 我们将使用它来执行对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
使用静态kBaseURL变量中定义的基本URL初始化会话管理器。它还将使用JSON请求和响应序列化器。
现在, 我们将在” 网络” 组中创建的第二个类称为APIManager。它应从我们新创建的SessionManager类派生。一旦必要的数据模型的创建, 我们将增加ApiManager的方法将被用于请求从API文章的列表。
纽约时报文章搜索API概述 可以在http://developer.nytimes.com/…/article_search_api_v2上找到有关此出色API的官方文档。我们要做的是使用以下端点:
http://api.nytimes.com/svc/search/v2/articlesearch
…提取使用我们选择的以日期范围为界的搜索查询词找到的文章。例如, 我们可以做的就是要求API返回《纽约时报》上发表的所有与篮球相关的所有文章的清单, 这些文章与2015年7月的前7天有关。根据API文档, 可以这样做我们需要在对该端点的get请求中设置以下参数:
参数 | 值 |
q | “ 篮球” |
开始日期 | “ 20150701” |
结束日期 | “ 20150707” |
{
"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."
}
我们基本上得到的回应是三个领域。第一个称为响应的响应包含数组docs, 该数组又包含表示文章的项目。其他两个字段是状态和版权。现在我们知道了API的工作原理, 是时候使用Mantle创建数据模型了。
Mantle简介 如前所述, Mantle是一个开放源代码框架, 可大大简化数据模型的编写。首先创建一个文章列表请求模型。让我们将此类称为ArticleListRequestModel, 并确保它是从MTLModel派生的, MTLModel是所有Mantle模型都应派生的类。另外, 让它符合MTLJSONSerializing协议。我们的请求模型应该具有三个合适类型的属性:query, articlesFromDate和articlesToDate。为了确保我们的项目井井有条, 我建议将此课程放在” 模型” 组中。
外套简化了数据模型的编写, 减少了样板代码。
这是ArticleListRequestModel的界面文件的外观:
#import "MTLModel.h"
#import "Mantle.h"@interface ArticleListRequestModel : MTLModel <
MTLJSONSerializing>
@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, 我们就可以使用类方法+ [MTLJSONAdapter JSONArrayForModels:]获得模型的JSON字典表示形式。
正如我们从参数列表中知道的那样, 剩下的一件事是两个日期参数都必须采用” YYYYMMDD” 格式。这是Mantle派上用场的地方。通过实现可选方法+ < propertyName> 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的另一个重要功能是所有这些模型都符合NSCoding协议, 并实现了isEqual和hash方法。
正如我们已经看到的, API调用产生的JSON包含代表文章的对象数组。如果要使用Mantle对该响应进行建模, 则必须创建两个单独的数据模型。一种将对表示文章的对象(docs数组元素)进行建模, 另一种将对JSON响应(除了docs数组的元素)进行建模。现在, 我们不必将传入JSON中的每个属性都映射到我们的数据模型中。假设我们只对文章对象的两个字段感兴趣, 那就是lead_paragraph和web_url。如我们在下面看到的, ArticleModel类非常容易实现。
#import "MTLModel.h"
#import <
Mantle/Mantle.h>
@interface ArticleModel : MTLModel <
MTLJSONSerializing>
@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 <
Mantle/Mantle.h>
#import "ArticleModel.h"@interface ArticleListResponseModel : MTLModel <
MTLJSONSerializing>
@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属性版权不会映射到响应模型中。如果查看articlesJSONTransformer方法, 我们将看到它为包含ArticleModel类的对象的数组返回一个值转换器。
还值得注意的是, 在方法JSONKeyPathsByPropertyKey中, 模型属性文章对应于嵌套在JSON属性响应中的数组文档。
到目前为止, 我们应该已经实现了三个模型类:ArticleListRequestModel, ArticleModel和ArticleListResponseModel。
第一个API请求
文章图片
现在我们已经实现了所有数据模型, 是时候回到类APIManager来实现将用于执行对API的GET请求的方法了。方法:
- (NSURLSessionDataTask *) getArticlesWithRequestModel:(ArticleListRequestModel *)requestModel success:(void (^)(ArticleListResponseModel *responseModel))success failure:(void (^)(NSError *error))failure
将ArticleListRequestModel请求模型作为参数, 并在成功的情况下返回ArticleListResponseModel, 否则返回NSError。此方法的实现使用AFNetworking对API执行GET请求。请注意, 为了成功发送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类提供的方法, 我们获得了数据模型的NSDictionary表示形式。该表示反映了将要发送到API的JSON。这就是Mantle之美所在。在ArticleListRequestModel类中实现了JSONKeyPathsByPropertyKey和+ < propertyName> JSONTransformer方法之后, 我们只需一行代码就可以立即获得数据模型的正确JSON表示形式。
Mantle还允许我们在另一个方向上执行转换。而这正是从API接收到的数据所发生的一切。使用以下类方法将我们收到的NSDictionary映射到ArticleListResponseModel类的对象:
ArticleListResponseModel *list = [MTLJSONAdapter modelOfClass:ArticleListResponseModel.class fromJSONDictionary:responseDictionary error:&
error];
使用领域持久化数据 现在我们已经能够从远程API提取数据, 是时候将其持久化了。如引言中所述, 我们将使用Realm来实现。 Realm是一个移动数据库, 是Core Data和SQLite的替代品。正如我们将在下面看到的, 它非常易于使用。
Realm是最终的移动数据库, 是Core Data和SQLite的完美替代。
鸣叫
为了在Realm中保存一条数据, 我们首先需要封装一个从RLMObject类派生的对象。我们现在需要做的是创建一个模型类, 该模型类将存储单个文章的数据。创建此类的过程很简单。
#import "RLMObject.h"@interface ArticleRealm : RLMObject@property NSString *leadParagraph;
@property NSString *url;
@end
基本上就是这样, 此类的实现可以保持为空。请注意, 模型类中的属性没有非原子, 强或副本之类的属性。 Realm会照顾那些人, 我们不必担心它们。
由于我们可以使用Mante模型Article对模型进行建模, 因此使用Article类的对象初始化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
我们使用类RLMRealm的对象与数据库进行交互。通过调用方法” [RLMRealm defaultRealm]” , 我们可以轻松获得RLMRealm对象。重要的是要记住, 这样的对象仅在创建它的线程内有效, 并且不能在线程间共享。将数据写入Realm非常简单。一次写入或一系列写入都需要在写入事务中完成。这是写入数据库的示例:
RLMRealm *realm = [RLMRealm defaultRealm];
ArticleRealm *articleRealm = [ArticleRealm new];
articleRealm.leadParagraph = @"abc";
articleRealm.url = @"sampleUrl";
[realm beginWriteTransaction];
[realm addObject:articleRealm];
[realm commitWriteTransaction];
以下是发生的情况。首先, 我们创建一个RLMRealm对象以与数据库进行交互。然后创建一个ArticleRealm模型对象(请记住, 它是从RLMRealm类派生的)。最后要保存它, 一个写事务开始, 该对象被添加到数据库, 并且一旦保存, 就提交写事务。如我们所见, 写事务阻塞了调用它们的线程。虽然说Realm很快, 但是如果我们要在主线程上的单个事务中向数据库添加多个对象, 这可能导致UI变得无响应, 直到事务完成为止。一个自然的解决方案是在后台线程上执行这种写事务。
API请求和领域中的持久响应 这是我们使用Realm保留文章所需的全部信息。让我们尝试使用方法执行API请求
- (NSURLSessionDataTask *) getArticlesWithRequestModel:(ArticleListRequestModel *)requestModel success:(void (^)(ArticleListResponseModel *responseModel))success failure:(void (^)(NSError *error))failure
和Mantle请求和响应模型, 以获取与篮球有任何关系的纽约时报文章(如前面的示例), 并于2015年6月的前7天发布。一旦此类文章列表可用, 我们将其保留在Realm中。下面是执行此操作的代码。它放置在我们应用程序中的表格视图控制器的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];
}];
首先, 使用请求模型(1)进行API调用(2), 该模型返回包含文章列表的响应模型。为了使用Realm保留这些文章, 我们需要创建Realm模型对象, 该对象在for循环(4)中进行。还需要注意的是, 由于多个对象保留在单个写事务中, 因此该写事务是在后台线程(3)上执行的。现在, 将所有文章保存在Realm中之后, 我们将它们分配给类属性self.articles(7)。由于稍后将在TableView数据源方法中的主线程上对其进行访问, 因此也可以从主线程上的Realm数据库中检索它们(5)。同样, 要从新线程访问数据库, 需要在该线程上创建一个新的RLMRealm对象(6)。
文章图片
如果从API获取新文章由于某种原因而失败, 则将从失败块中的本地存储中检索现有文章。
本文总结 在本教程中, 我们学习了如何配置Mantle(可可和Cocoa Touch的模型框架), 以便与远程API进行交互。我们还学习了如何使用Realm移动数据库以Mantle模型对象的形式本地保留检索到的数据。
【通过Mantle和Realm简化iOS上的RESTful API使用和数据持久化】如果你想试用此应用程序, 可以从其GitHub存储库中检索源代码。在运行应用程序之前, 你将需要生成并提供自己的API密钥。
推荐阅读
- 正在为Android Auto和Apple Carplay之类的汽车信息娱乐系统开发下一件大事吗()
- 适用于开发人员的Apple Pay和Android Pay
- 缓慢采用Android Wear令人窒息
- 适用于开发人员的iOS 9 Beta和WatchOS 2
- 从应用程序启动android浏览器
- 在中查找最近标记的邮件邮件.app使用appscript
- AppleScript强制退出应用程序-sugar sync kill SugarSyncCMPlugInLoader
- 唯一标识android设备
- 启用Apple的内置VNC服务器