MMRecord 是一个基于块的 无缝 Web 服务集成库,用于 iOS 和 Mac OS X。它利用 Core Data 模型配置来自动从 API 响应创建和填充完整的对象图。它与任何网络库一起工作,易于设置,并包含许多流行的功能,使与 Web 服务一起工作更加容易。以下是请求 App.net Post 记录的请求方式
NSManagedObjectContext *context = [[MMDataManager sharedDataManager] managedObjectContext];
[Post
startPagedRequestWithURN:@"stream/0/posts/stream/global"
data:nil
context:context
domain:self
resultBlock:^(NSArray *posts, ADNPageManager *pageManager, BOOL *requestNextPage) {
NSLog(@"Posts: %@", posts);
}
failureBlock:^(NSError *error) {
NSLog(@"%@", error);
}];
继续阅读以了解更多关于如何在您的项目中开始使用 MMRecord 的信息!
您可以通过使用 CocoaPods 在您的项目中安装 MMRecord。
pod 'MMRecord', '~> 1.4.0'
MMRecord 的设计旨在使从新的 Web 服务请求中获取本地对象尽可能简单和快速。它在后台处理所有获取、创建和填充 NSManagedObject 的过程,所以当您进行请求时,您将得到您可以立即使用的本地对象。无需解析。
该库的设计使其尽可能简单和轻量。以下是 MMRecord 核心类的分析。
核心 | |
---|---|
MMRecord | 是NSManagedObject的子类MMRecord定义了接口并启动了对象图的填充过程。
|
MMServer | 是一个定义了由MMRecord.
|
支持任何网络框架,包括本地文件和服务器 | |
---|---|
MMRecordResponse | 填充MMRecord处理将响应转换为本地对象的过程的类。 |
MMRecordProtoRecord | 用于在填充过程中作为对象图占位符的容器类。 |
MMRecordRepresentation | 一个定义字典与Core Data之间的映射关系的类NSEntityDescription. |
MMRecordMarshaler | 负责根据MMRecord填充实例MMRecordRepresentation. |
分页 | |
MMServerPageManager | 一个定义处理分页接口的抽象类 |
缓存 | |
MMRecordCache | 一个将NSManagedObjectObjectIDs映射到NSCachedURLResponse. |
的类 | |
调试 | MMRecordDebugger一个管理NSError堆栈的类,提供调试反馈。 |
子模块 | |
---|---|
AFServer | 一个示例子类,实现了MMServer基于AFNetworking的 1.0. |
SessionManagerServer | 一个示例子类,实现了MMServer基于AFNetworking的 2.0. |
一个子类,可以读取本地的JSON文件。 | 一个示例子类,实现了MMServerDynamicModel |
一个自定义 | 和子类对,将原始对象字典作为可转换属性存储。MMRecordRepresentation和MMRecordMarshaler。 |
ResponseSerializer | 和子类对,将原始对象字典作为可转换属性存储。AFHTTPResponseSerializer它创建并返回MMRecord在2.0成功块中的实例。AFNetworking的。 tweaks |
一个子模块,实现了对Facebook Tweaks的支持,以调整 | 响应处理行为。MMRecord。MMRecord集成指南 |
MMRecord在使用前需要进行一些基本设置。本指南将向您介绍该配置过程的所有步骤。
MMRecord需要注册服务器类来发出请求。服务器类应该知道如何对您要集成的API发出请求。服务器实现的要求仅是返回一个响应对象(数组或字典),其中包含您请求的对象。服务器可以使用AFNetworking对一个特定的API执行GET请求。或者它可以加载并返回本地的JSON文件。有两个子模块提供了预构建的服务器,分别使用AFNetworking和本地JSON文件。一般来说,您最好实现自己的服务器来与您使用的API通信。
定义了您的服务器类后,您必须将其注册到MMRecord
[Post registerServerClass:[ADNServer class]];
请注意,您可以在不同的MMRecord子类上注册不同的服务器类。
[Tweet registerServerClass:[TWSocialServer class]];
[User registerServerClass:[MMJSONServer class]];
当您正在处理的一个端点是完整的,但另一个不是,或者位于另一个API上时,这非常有帮助。
虽然您被鼓励创建自己的特定服务器子类进行集成,但MMRecord确实提供了针对AFNetworking 1.0和AFNetworking 2.0的基类服务器实现作为子模块示例。您可以为AFNetworking 1.0查看AFServer子模块,或为AFNetworking 2.0查看AFMMRecordSessionManagerServer子模块。您可以在Foursquare示例应用中查看新的AFNetworking 2.0服务器。
此外,我们特别提供了AFMMRecordResponseSerializer子模块,用于AFNetworking 2.0。此响应序列化器可用于AFNetworking 2.0,以在AFNetworking成功块中为您提供解析和填充的MMRecord实例。有关更多信息,请参阅这篇博客文章或查看下面的示例。
您需要在MMRecord子类中覆盖一个方法,以告诉解析系统您希望解析的对象(或对象组)的位置。此方法返回一个关键路径,指定相对于响应对象根部的位置。如果您的响应对象是数组,您可以直接返回nil。
在App.net请求中,所有返回的对象都位于一个名为“data”的对象中,所以我们的子类将如下所示MMRecord。
@interface ADNRecord : MMRecord
@end
static NSDateFormatter *ADNRecordDateFormatter;
@implementation ADNRecord
+ (NSString *)keyPathForResponseObject {
return @"data";
}
+ (NSDateFormatter *)dateFormatter {
if (!ADNRecordDateFormatter) {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZ"]; // "2012-11-21T03:57:39Z"
ADNRecordDateFormatter = dateFormatter;
}
return ADNRecordDateFormatter;
}
@end
还有一些可选方法您可以实现在MMRecord上。其中一种方法返回一个用于填充属性类型的日期格式化器。您可以覆盖此方法以使用格式化的日期字符串填充日期属性。默认支持Unix数字时间戳日期。
注意,这些方法是在一个名为ADNRecord的类上实现的,它是一个MMRecord的子类ADNRecord。其他实体是
模型配置。Core Data的
NSManagedObjectModelNSEntityDescription非常有用。从高层次来看,模型由一系列实体组成。同样,API通常由一系列端点组成。MMRecord利用这一惯例将实体表示映射到端点表示,从而创建从API响应中创建原生对象。
MMRecord在存在唯一标识给定实体类型记录的方法时表现最好。这样,它可以获取现有记录(如果存在)并更新它,而不是创建一个重复的记录。要指定实体的主键,我们利用实体的用户信息字典。将主键属性名称指定为值,将MMRecordEntityPrimaryAttributeKey指定为键。
请注意,主键可以是任何属性,包括关系。如果使用关系作为主键,MMRecord将尝试获取父对象并在关系中搜索关联对象。
如果您知道要填充的记录的键不在用作填充记录的API响应字典中,也可以在填充时注入主键。以下是一个使用此功能示例的例子:[以下](https://github.com/mutualmobile/MMRecord/#mmrecordoptions-and-primary-key-injection)。此选项不打算取代适当的模型配置,但可用于额外的灵活性。您可以将其用于创建给定记录自己的唯一标识符,并通过解析字典中的内容来实现。
有时,你可能需要为你的实体中的一个属性定义一个别名。这可能有多种原因。比如,你可能不喜欢你的Core Data属性名中包含下划线?或者API响应已更改,你不希望改变你的NSManagedObject属性名。或者,属性值实际上在子对象中,你需要将其提升到根级别。这正是MMRecordAttributeAlternateNameKey的作用所在。你可以在任何属性或关系用户信息字典上定义这个键。这个键的值可以是别名,也可以是用于定位该属性的备选键Path。
为了参考,以下是App.net用户对象的截断版本,以说明这些配置值是如何确定的
{
"id": "1", // note this is a string
"username": "johnappleseed",
"name": "John Appleseed",
"avatar_image": {
"height": 512,
"width": 512,
"url": "https://example.com/avatar_image.jpg",
"is_default": false
},
"cover_image": {
"width": 320,
"height": 118,
"url": "https://example.com/cover_image.jpg",
"is_default": false
},
"counts": {
"following": 100,
"followers": 200,
"posts": 24,
"stars": 76
}
}
这里有一些使用MMRecord的请求类型的示例。请注意,AFMMRecordResponseSerializer是MMRecord的一个子规范。
+ (void)favoriteTweetsWithContext:(NSManagedObjectContext *)context
domain:(id)domain
resultBlock:(void (^)(NSArray *tweets))resultBlock
failureBlock:(void (^)(NSError *))failureBlock {
[Tweet startRequestWithURN:@"favorites/list.json"
data:nil
context:context
domain:self
resultBlock:resultBlock
failureBlock:failureBlock];
}
@interface Post : ADNRecord
+ (void)getStreamPostsWithContext:(NSManagedObjectContext *)context
domain:(id)domain
resultBlock:(void (^)(NSArray *posts, ADNPageManager *pageManager, BOOL *requestNextPage))resultBlock
failureBlock:(void (^)(NSError *error))failureBlock;
@end
@implementation Post
+ (void)getStreamPostsWithContext:(NSManagedObjectContext *)context
domain:(id)domain
resultBlock:(void (^)(NSArray *posts, ADNPageManager *pageManager, BOOL *requestNextPage))resultBlock
failureBlock:(void (^)(NSError *error))failureBlock {
[self startPagedRequestWithURN:@"stream/0/posts/stream/global"
data:nil
context:context
domain:self
resultBlock:resultBlock
failureBlock:failureBlock];
}
@end
[Tweet startBatchedRequestsInExecutionBlock:^{
[Tweet
timelineTweetsWithContext:context
domain:self
resultBlock:^(NSArray *tweets, MMServerPageManager *pageManager, BOOL *requestNextPage) {
NSLog(@"Timeline Request Complete");
}
failureBlock:^(NSError *error) {
NSLog(@"%@", error);
}];
[Tweet
favoriteTweetsWithContext:context
domain:self
resultBlock:^(NSArray *tweets, MMServerPageManager *pageManager, BOOL *requestNextPage) {
NSLog(@"Favorites Request Complete");
}
failureBlock:^(NSError *error) {
NSLog(@"%@", error);
}];
} withCompletionBlock:^{
NSLog(@"Request Complete");
}];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"User"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.name contains[c] %@", name];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
fetchRequest.predicate = predicate;
fetchRequest.sortDescriptors = @[sortDescriptor];
[self
startRequestWithURN:[NSString stringWithFormat:@"stream/0/users/%@", name]
data:nil
context:context
domain:domain
fetchRequest:fetchRequest
customResponseBlock:nil resultBlock:^(NSArray *records, id customResponseObject, BOOL requestComplete) {
if (resultBlock != nil) {
resultBlock(records, requestComplete);
}
}
failureBlock:failureBlock];
MMFoursquareSessionManager *sessionManager = [MMFoursquareSessionManager sharedClient];
NSManagedObjectContext *context = [[MMDataManager sharedDataManager] managedObjectContext];
AFHTTPResponseSerializer *HTTPResponseSerializer = [AFJSONResponseSerializer serializer];
AFMMRecordResponseSerializationMapper *mapper = [[AFMMRecordResponseSerializationMapper alloc] init];
[mapper registerEntityName:@"Venue" forEndpointPathComponent:@"venues/search?"];
AFMMRecordResponseSerializer *serializer =
[AFMMRecordResponseSerializer serializerWithManagedObjectContext:context
responseObjectSerializer:HTTPResponseSerializer
entityMapper:mapper];
sessionManager.responseSerializer = serializer;
[[MMFoursquareSessionManager sharedClient]
GET:@"venues/search?ll=30.25,-97.75"
parameters:requestParameters
success:^(NSURLSessionDataTask *task, id responseObject) {
NSArray *venues = responseObject;
self.venues = venues;
[self.tableView reloadData];
}
failure:failureBlock];
MMRecordOptions
是一种定制请求行为的方式。你可以使用它的方法之一是指定一系列选项后应用到以下请求的块。这允许你执行插入新主键等操作或指定孤立删除行为。
MMRecordOptions *options = [Post defaultOptions];
options.entityPrimaryKeyInjectionBlock = ^id(NSEntityDescription *entity,
NSDictionary *dictionary,
MMRecordProtoRecord *parentProtoRecord) {
if ([[entity name] isEqualToString:@"CoverImage"]) {
if ([[parentProtoRecord.entity name] isEqualToString:@"User"]) {
if (parentProtoRecord.primaryKeyValue != nil) {
return parentProtoRecord.primaryKeyValue;
}
}
}
return nil;
};
[Post setOptions:options];
[Post
getStreamPostsWithContext:context
domain:self
resultBlock:^(NSArray *posts, ADNPageManager *pageManager, BOOL *requestNextPage) {
[self populatePostsTableWithPosts:posts];
}
failureBlock:failureBlock];
MMRecordOptions *options = [Tweet defaultOptions];
options.deleteOrphanedRecordBlock = ^(MMRecord *orphan,
NSArray *populatedRecords,
id responseObject,
BOOL *stop) {
Tweet *tweet = (Tweet *)orphan;
if ([tweet isFavorite]) {
return NO;
}
return YES;
};
[Tweet setOptions:options];
[Tweet
timelineTweetsWithContext:context
domain:self
resultBlock:^(NSArray *tweets, MMServerPageManager *pageManager, BOOL *requestNextPage) {
self.tweets = tweets;
[self.tableView reloadData];
}
failureBlock:failureBlock];
虽然MMRecord是用Objective-C实现的,但你也可以使用库从Swift中构建你的模型。当你用Swift构建模型时,你应该注意的主要事情是,实体管理对象类名需要是全命名空间的。以下是一个例子。
请注意,在Swift中,使用MMRecordAtlassian作为Issue类的命名空间。这是因为默认命名空间是您项目的程序名称。请注意,使用特殊字符或空格作为您的产品名称可能会导致问题。通常这些字符会替换您的命名空间中的下划线,但为了最佳效果,请使用单个单词作为您的产品名称以避免问题。
你还应该记得在你的Objective-C桥梁头文件中导入MMRecord.h及其任何您使用的子规范。然后,你就可以准备好构建你的MMRecord模型了!
这里有一些在Swift中使用MMRecord的示例。
import CoreData
class Plan: ATLRecord {
@NSManaged var name: NSString
@NSManaged var id: NSString
override class func keyPathForResponseObject() -> String {
return "plans.plan"
}
}
Plan.startRequestWithURN("/plans",
data: nil,
context: managedObjectContext,
domain: self,
resultBlock: {records in
var results: Plan[] = records as Plan[]
self.plans = results
self.tableView.reloadData()
},
failureBlock: { error in
})
var options = Issue.defaultOptions()
options.entityPrimaryKeyInjectionBlock = {(entity, dictionary, parentProtoRecord) -> NSCopying in
let dict = dictionary as Dictionary
let key: AnyObject? = dict["id"]
let returnKey = key as String
return returnKey
}
options.recordPrePopulationBlock = { protoRecord in
let proto: MMRecordProtoRecord = protoRecord
let entity: NSEntityDescription = protoRecord.entity
var dictionary: AnyObject! = proto.dictionary.mutableCopy()
var mutableDictionary: NSMutableDictionary = dictionary as NSMutableDictionary
var primaryKey: AnyObject! = ""
if (entity.name == "OutwardLink") {
primaryKey = mutableDictionary.valueForKeyPath("outwardIssue.key")
}
if (entity.name == "InwardLink") {
primaryKey = mutableDictionary.valueForKeyPath("inwardIssue.key")
}
mutableDictionary.setValue(primaryKey, forKey: "PrimaryKey")
proto.dictionary = mutableDictionary
}
Issue.setOptions(options)
Issue.startRequestWithURN("/issue",
data: nil,
context: managedObjectContext,
domain: self,
resultBlock: { records in
var results: Issue[] = records as Issue[]
self.results = results
self.tableView.reloadData()
},
failureBlock: { error in
})
MMRecord 还提供了 TweakModel 子规范,以实现对 Facebook 微调的支持。您可以使用微调来修改 MMRecord 解析和填充参数的大部分内容。如果您正在开发一个 API 正在变动且仍在积极开发中的应用程序,这将非常有用。微调的用户界面将向您展示数据模型中 MMRecord 实体的列表、每个实体的主键、用于填充各种属性的所有键以及指向数据模型中该实体实例的关键路径。这是您如何使用它的。
#define FBMMRecordTweakModelDefine
[FBMMRecordTweakModel loadTweaksForManagedObjectModel:
[MMDataManager sharedDataManager].managedObjectModel];
这就是您需要的内容来启用您的 MMRecord 项目的微调。作为最佳实践,您应该只在调试模式下使用 #define。
设置完成后,以下是含 MMRecord 的微调界面。
MMRecordDebugger
是一个类,由 MMRecord
使用来向您提供有关模型配置和 MMRecord 如何处理来自您的服务器类的响应的调试信息。您可以使用 MMRecordDebugger 帮助解决模型配置中可能存在的问题,或识别与响应格式的偏差。
MMRecord 被设计得尽可能快和简单,以便从 Web 服务中序列化托管对象。该库的一个目标是提供有意义的自定义方式来支持所有类型的响应格式,同时仍保持一个易于使用的顶级界面,无需大量配置和设置。在大多数情况下,MMRecord 用户所需的配置和定制数量将取决于您的 Web 服务的响应格式的复杂程度。
在处理请求时,如果 MMRecord 遇到错误,它可能会根据错误的严重程度采取一些措施。
NSError
并将其与 MMRecordDebugger
相关联。但是,如果错误不够严重,则请求不会失败。NSError
。这些错误与调试器关联,并将传递回失败块,指示请求失败的原因。如果您遇到请求问题,第一步应该是启用 MMRecord 日志记录,使用以下命令。
[MMRecord setLoggingLevel:MMRecordLoggingLevelAll];
您可以将日志级别逐步降低,以接收更细粒度的日志信息,但是从最高级别开始以便更全面地了解情况是一个好主意。
如果您的请求失败,您可以使用传递到失败块的 NSError
对象来审查有关失败的各种数据。失败块中的错误参数实际上将包括 MMRecordDebugger
实例,其中包含在处理请求时遇到的全部错误以及与关键错误相关的各种状态。
调试器已被附加到 userInfo 字典中的 NSError
。以下是如何使用它的一个示例。
failureBlock:^(NSError *error) {
NSDictionary *recordDictionary = [[error userInfo] valueForKey:MMRecordDebuggerParameterRecordDictionary];
MMRecordDebugger *debugger = [[error userInfo] valueForKey:MMRecordDebuggerKey];
NSArray *allErrors = [debugger errorsEncounteredWhileHandlingResponse];
id responseObject = [debugger responseObject];
NSString *entityName = [[debugger initialEntity] name];
}];
如果您遇到希望跟踪的错误,或有关于某些错误严重性的建议,请创建一个问题或发起一项拉取请求。
MMRecord 1.4.0 及更高版本需要 iOS 6.0 或更高版本,或者 Mac OS 10.8 及更高版本(64位并使用现代 Cocoa 运行时)。请参考以下链接获取更多详细信息:iOS 6.0 及以上版本 和 Mac OS 10.8 及以上版本(需要使用现代 Cocoa 运行时的 64 位系统)。
MMRecord 使用 ARC。
如果您的项目未使用 ARC,则在所有 MMRecord 源文件中需要设置 -fobjc-arc
编译器标志。
MMRecord 由 Conrad Stoll 在 Mutual Mobile 创建。
MMRecord 遵循 MIT 许可协议。更多信息请参阅 LICENSE 文件。