ZCREasyBake 0.3.0

ZCREasyBake 0.3.0

测试已测试
Lang语言 Obj-CObjective C
许可 MIT
发布最后发布2014年12月

Zach Radke 维护。



  • Zach Radke

master

develop

一个轻量级的不可变模型框架,通过友好的食物隐喻伪装。

设备

要使用 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 项目中,您会遇到一些术语

  • 模型
  • 属性:一个 Objective-C 属性,它控制对模型底层数据的访问。当暴露模型底层的应用数据时,ZCREasyBake 倾向于使用 readonly 属性。
  • 标识符:一个符合 NSObjectNSCopying 协议的任意对象,可以唯一标识模型实例。
  • 成分:构成模型的原生数据,表示为 NSDictionaryNSArray。此类数据通常由外部服务(如 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"
}

无论成分来源如何,成分在可以处理之前必须是 NSDictionaryNSArray

定义烹饪配方

要将原始成分处理成模型,使用一个 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];

结果将是一个 NSDictionaryNSArray,具体取决于配方映射中建议的根对象类型。

如果配方提供了值转换器,将应用它们于模型值(如果转换器支持反向转换)。

[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 类在运行时检查您的模型属性并缓存它们,所以避免在运行时在模型类上动态创建属性。