一个轻量级的不可变模型框架,通过友好的食物隐喻伪装。
要使用 ZCREasyBake,您的项目应具有至少 iOS 5.1+ 或 OSX 10.7+ 的最低部署目标,并运行 ARC。然而,此项目只在 iOS 7.0+ 和 OSX 10.9+ 上进行了单元测试。
您可以通过各种方式安装 ZCREasyBake,具体取决于您的偏好
Classes
文件夹中的文件拖放到您的项目中。pod "ZCREasyBake"
添加到您的 Podfile 以使用 CocoaPods。无论您如何将框架添加到项目中,您都可以根据需要导入主头文件。
#import <ZCREasyBake/ZCREasyBake.h> // Or #import "ZCREasyBake.h"
在整个 ZCREasyBake 项目中,您会遇到一些术语
readonly
属性。NSObject
和 NSCopying
协议的任意对象,可以唯一标识模型实例。NSDictionary
或 NSArray
。此类数据通常由外部服务(如 Web API)生成,并可在一些工作后处理成模型。ZCREasyRecipe
的烘焙成分的说明。食谱使我们能够解耦原料来源与其最终的模型表示。食谱通常是模型和原料来源相关的,但其他方面可重用。首先,将您的模型定义为 ZCREasyDough
的子类
@interface User : ZCREasyDough
@property (strong, readonly) NSString *name;
@property (assign, readonly) NSUInteger unreadMessages;
@property (strong, readonly) NSDate *updatedAt;
@end
注意属性被定义为 readonly
因此实例在创建后实际上是不可变的!
您的模型需要包含来自成分来源的成分。例如,我们上面定义的 User
模型可以由一个返回如下 JSON 格式的 Web 服务支持:
{
"server_id": "1209-3r47-4482-9rj4-93iu-324s",
"name": "Zach Radke",
"unread_messages": 10,
"updated_at": "2014-04-19T19:32:05Z"
}
无论成分来源如何,成分在可以处理之前必须是 NSDictionary
或 NSArray
。
要将原始成分处理成模型,使用一个 ZCREasyRecipe
。这些配方通常是模型和成分来源相关的。例如,我们可以为一个 User
模型和上面的 JSON 成分来源创建一个单一的配方。
所有配方都以一个 NSDictionary
映射开始,这是必需的。键是填充模型的属性键,值是对应的成分来源路径。
NSDictionary *mapping = @{@"name": @"name",
@"unreadMessages": @"unread_messages",
@"updatedAt": @"updated_at"};
成分路径以字符串表示,但可以使用点表示法来表示字典键遍历,或者使用 [<索引>]
形式表示数组索引遍历。例如:
// The mapping…
NSDictionary *mapping = @{@"key": @"values[0].key"};
// Which corresponds to this structure…
NSDictionary *ingredientSource = @{@"values": @[@{@"key": @"fooBar"}]};
// Would produce these processed ingredients…
NSDictionary *processedIngredients = @{@"key": @"fooBar"};
配方还可以提供一系列转换器,用于将原始成分处理成不同的对象。与成分映射类似,键是模型上应变换的属性键。值是 NSValueTransformer
实例或 NSString
。如果使用字符串,则必须将其注册为值转换器。
// NSValueTransformer+DefaultTransformers.h
// Assume we have created the DateTransformer class elsewhere...
DateTransformer *dateTransformer = [[DateTransformer alloc] initWithDateFormat:@"yyyy-MM-dd'T'HH-mm-ss'Z'"];
[NSValueTransformer setValueTransformer:dateTransformer forName:@"DateTransformer"];
// ...
NSDictionary *transformers = @{@"updatedAt": @"DateTransformer"};
最后,配方可以有名称。这对调试很有用,但也可以用于在 ZCREasyRecipeBox
实例中存储和重复使用配方。
ZCREasyRecipe *userJSONRecipe = [ZCREasyRecipe makeWith:^(id<ZCREasyRecipeMaker maker) {
[maker setIngredientMapping:mapping];
[maker setIngredientTransformers:transformers];
[maker setName:@"UserJSONRecipe"];
}];
[[ZCREasyRecipeBox defaultBox] addRecipe:userJSONRecipe error:NULL];
由于配方通常是模型相关的,您还可以在模型上提供面向方法的类,以实现更简单的配方访问。
// User.m
+ (ZCREasyRecipe *)JSONRecipe {
return [[ZCREasyRecipeBox defaultBox] recipeWithName:@"UserJSONRecipe"];
}
和
许多实用工具使生成和验证配方更加容易。检查它们的头文件以了解更多信息。
您的模型将继承自 ZCREasyDough
的指定初始化器。
- (instancetype)initWithIdentifier:(id<NSObject,NSCopying>)identifier
ingredients:(id)ingredients
recipe:(ZCREasyRecipe *)recipe
error:(NSError **)error;
+ (instancetype)makeWith:(void (^)(id<ZCREasyBaker> baker))constructionBlock;
要创建一个实例,您需要一个标识符,一些成分和一个配方。
NSDictionary *ingredients = @{@"server_id": @"1209-3r47-4482-9rj4-93iu-324s"
@"name": @"Zach Radke"
@"unread_messages": @10,
@"updated_at": @"2014-04-19T19:32:05Z"};
User *user = [User prepareWith:^(id<ZCREasyChef> chef) {
[chef setIdentifier:ingredients[@"server_id"]];
[chef setIngredients:ingredients];
[chef setRecipe:[User JSONRecipe]];
}];
当您要更新一个实例时,只需使用它的更新方法,传递新的成分和配方来生成一个新的实例。
NSDictionary *ingredients = @{@"name": @"Zachary Radke"};
User *updatedUser = [user updateWithIngredients:ingredients
recipe:[User JSONRecipe]
error:NULL];
更新后的实例将与其父级的唯一标识符相同,并会生成可以观察的通知。
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(userUpdated:)
name:[User updateNotificationName]
object:nil];
对于更通用的通知,可以观察 ZCREasyDoughUpdatedNotification
,该通知会在更新 子类时触发。
子类的相等性是通过初始化实例时使用的标识符确定的。这意味着对于
isEqual:
调用,一个模型和一个更新后的模型将返回 YES
。
[user isEqual:updatedUser]; // YES
(user == updatedUser); // NO
子类还可以报告它是否已使用给定的配方包含给定的成分。
NSDictionary *ingredients = @{@"name": @"Zachary Radke"};
[user isEqualToIngredients:ingredients withRecipe:[User JSONRecipe] error:NULL]; // NO
[updatedUser isEqualToIngredients:ingredients withRecipe:[User JSONRecipe] error:NULL]; // YES
从制作它的成分返回!可以使用给定配方分解一个模型。
NSDictionary *ingredients = [updatedUser decomposeWithRecipe:[User JSONRecipe] error:NULL];
结果将是一个 NSDictionary
或 NSArray
,具体取决于配方映射中建议的根对象类型。
如果配方提供了值转换器,将应用它们于模型值(如果转换器支持反向转换)。
[DateTransformer allowsReverseTransformation]; // YES
ingredients[@"updated_at"]; // @"2014-04-19T19:32:05Z"
在使用模型时遇到困难?也许以下提示可以有所帮助
setValue:forKey:
在接收模型中设置。// Invalid mapping since key1 indicates a dictionary and key2 indicates an array
NSDictionary *invalidMapping = @{@"key1": @"key_1",
@"key2": @"[0]"};
NSNull
值会转换为 nil
用于转换器。nil
,则在处理后的成分中会将其转换为 NSNull
。NSNull
成分值会转换为 nil
。readonly
属性不能通过 setValue:forKey:
设置。尝试这样做将引发一个 ZCREasyDoughExceptionAlreadyBaked
异常。updateWithIngredients:recipe:error
时,不会发布任何通知,并且将返回同一个对象而不是新实例。ZCREasyDough
类在运行时检查您的模型属性并缓存它们,所以避免在运行时在模型类上动态创建属性。