FXModelValidation 1.0.5

FXModelValidation 1.0.5

测试已测试
Lang语言 Obj-CObjective C
许可证 MIT
发布最新发布2015年9月

Andrey Gayvoronsky 维护。



  • 作者:
  • Andrey Gayvoronsky

目的

FXModelValidation 是一个 Objective-C 库,它允许您轻松地验证数据/模型/表单。适用于任何 NSObject。因此,它与 CoreData 或原生 NSObject 都能良好地工作。库透明地支持 FXForms 以排除自身属性。

要使其工作,您只需执行 3 个小型步骤

  • 定义验证规则
  • 附加 FXModel 功能
  • 验证

支持的 iOS/OSX 及 SDK 版本

  • 支持的构建目标 - iOS 8.1/OSX 10.10(Xcode 6.1,Apple LLVM 编译器 6.0)
  • 最早支持的部署目标 - iOS 5.0/OSX 10.7
  • 最早兼容的部署目标 - iOS 5.0/OSX 10.7

注意:“支持”表示库已与该版本进行过测试。“兼容”表示库应在该 iOS 版本上运行(即它不依赖于任何不可用的 SDK 特性),但不再进行兼容性测试,可能需要调整或修复错误才能正常运行。

ARC 兼容性

FXModelValidation 需要 ARC。

如何开始

  • 通过 CocoaPods 安装
pod 'FXModelValidation'

附加 FXModelValidation

为了使 FXModelValidation 与您的类一起工作,您必须 附加 它的功能。您可以为类做,或者仅对一个单独的实例做。

ContactForm* model = [ContactForm alloc] init];
[[ContactForm class] validationInit];

将 FXModelValidation 附加到类

ContactForm* model = [ContactForm alloc] init];
[model validationInit];

将 FXModelValidation 附加到 ContactForm 模型 的单个实例。

最可能的方式是重写类的初始化方法

@implementation ContactForm
-(instancetype *)init {
    if((self = [super init])) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            [[self class] validationInit];
        });
    }

    return self;
};
@end

现在,任何 ContactForm 的实例都将具有附加的 FXModelValidation 功能。

为了访问此功能,您还必须在其 接口声明 中添加 FXModelValidation 协议,例如

@interface ContactForm : NSObject <FXModelValidation>
@end

这就全部了。现在您可以使用类的 FXModelValidation 方法/属性。

但让我们从头开始...

模型

模型是MVC架构的一部分。MVC架构。它们是表示业务数据、规则和逻辑的对象。在这个库中,模型是实现了FXModelValidation协议的对象。

属性

模型以属性的形式表示业务数据。每个属性就如同模型的一个公开访问属性。方法attributeList指定一个模型类具有哪些属性。

你可以像访问普通对象的属性一样访问一个属性。

ContactForm* model = [ContactForm alloc] init];

// "name" is an attribute of ContactForm
model.name = @"example";
NSLog(@"%@", model.name);

你也可以通过使用NSKeyValueCoding来间接访问属性。

ContactForm* model = [ContactForm alloc] init];

// accessing attributes via NSKeyValueCoding 
[model setValue:@"example" forKey:@"name"];
NSLog(@"%@", [model valueForKey:@"name"]);

定义属性

默认情况下,attributeList返回所有公共属性。例如,下面的ContactForm模型类有四个属性:nameemailsubjectbody

@interface ContactForm : NSObject <FXModelValidation>
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *email;
@property (nonatomic, strong) NSString *subject;
@property (nonatomic, strong) NSString *body;
@end

你可以重写attributeList来以不同的方式定义属性。此方法应返回模型中的属性名称。请注意,访问你定义的属性必须兼容NSKeyValueCoding

场景

模型可以用于不同的场景。例如,一个User模型可以用来收集用户登录输入,但它也可以用于用户注册目的。在不同的场景下,模型可能使用不同的业务规则和逻辑。例如,在用户注册期间,可能需要email属性,但在用户登录期间则不需要。

模型使用scenario属性来跟踪它正在使用的场景。默认情况下,模型只支持一个名为default的场景。以下代码显示了如何设置模型的场景

// scenario is set as a property
User* model = [User alloc] init];
model.scenario = @"login";
// scenario is set as a setter
User* model = [User alloc] init];
[model setScenario: @"login"];

默认情况下,模型支持的场景是由模型中声明的验证规则决定的。然而,你可以通过实现scenarioList方法来自定义这种行为,如下所示

@implementation User
-(NSDictionary *)scenarioList {
    return @{
            @"login": @[@"username", @"password"],
            @"register": @[@"username", @"email", @"password"],
    };
};
@end

scenarioList方法返回一个字典,其键是场景名称,值是对应的激活属性。一个激活属性可以被大量分配并且受验证。在上面的例子中,usernamepassword属性在login场景中是激活的;而在register场景中,除了usernamepassword之外,email也是激活的。

scenarioList的默认实现将返回在验证规则声明方法rules中找到的所有场景。当重写scenarioList时,如果你想除了默认场景之外引入新的场景,你可以编写如下代码

@implementation User
-(NSDictionary *)scenarioList {
    NSMutableDictionary *scenarios = [NSMutableDictionary dictionaryWithDictionary:[(id<FXModel>)super scenarioList]];
    scenarios[@"login"] = @[@"username", @"password"];
    scenarios[@"register"] = @[@"username", @"email", @"password"];
    return scenarios;
};
@end

场景功能主要用于验证大量属性分配。然而,你仍然可以用它来实现其他目的。

验证规则

当从最终用户那里接收到模型的数据时,应该对其进行验证,以确保其满足某些规则(称为验证规则,也称为业务规则)。例如,给定一个ContactForm模型,你可能想确保所有属性都不为空,并且email属性包含有效的电子邮件地址。如果某些属性的值没有满足对应的企业规则,应该显示适当的错误消息来帮助用户修复错误。

你可以调用validate来验证接收到的数据。此方法将使用在rules中声明的验证规则来验证每个相关属性。如果没有发现错误,它将返回YES。否则,它将保持错误在errors属性中并返回NO。例如,

User* model = [User alloc] init];

// populate model attributes with user inputs
model.attributes = @{@"username": @"john"};

if ([model validate]) {
    // all inputs are valid
} else {
    // validation failed: errors is an array containing error messages
    errors = model.errors;
}

为了声明与模型关联的验证规则,通过返回模型属性应满足的规则来重写rules方法。以下示例展示了为ContactForm模型声明的验证规则。


-(NSArray *)rules {             
    return @[
        // the name, email, subject and body attributes are required
        @{
            FXModelValidatorAttributes : @[@"name", @"email", @"subject", @"body"],
            FXModelValidatorType : @"required",
        },
        // the email attribute should be a valid email address
        @{
            FXModelValidatorAttributes : @"email",
            FXModelValidatorType : @"email"
        }
    ];
}

一个规则可以用来验证一个或多个属性,一个属性可以由一个或多个规则验证。有关如何声明验证规则的更多详细信息,请参阅验证输入部分。

有时,你可能只想在某些场景下应用规则。为此,你可以指定规则的on属性,如下所示:

-(NSArray *)rules {
    return @[
            // username, email and password are all required in "register" scenario
            @{
                    FXModelValidatorAttributes : @[@"user", @"email", @"password"],
                    FXModelValidatorType : @"required",
                    FXModelValidatorOn: @[@"register"],
            },
            // username and password are required in "login" scenario
            @{
                    FXModelValidatorAttributes : @[@"user", @"password"],
                    FXModelValidatorType : @"required",
                    FXModelValidatorOn: @[@"login"],
            },
    ];
}

如果你没有指定on属性,则该规则会在所有场景下应用。如果一个规则可以在当前场景下应用,则被称为活跃规则。

只有当一个属性在scenarioList中声明为活跃属性,并且与在rules中声明的至少一个活跃规则相关联时,才会验证该属性。

验证

你可以通过两种方式验证模型:

User* model = [User alloc] init];

// populate model attributes with user inputs
model.attributes = @{@"username": @"john"};

if ([model validate]) {
    // all inputs are valid
} else {
    // validation failed: errors is an array containing error messages
    errors = model.errors;
}

即手动调用验证。

User* model = [User alloc] init];
//set observer to track updates and validate updated attributes
[model validateUpdates];

// populate model attributes with user inputs
model.attributes = @{@"username": @"john"};

if(model.hasErrors) {
    // validation failed: errors is an array containing error messages
    errors = model.errors;    
}

model.password = @"password123";
if(!(model.hasErrors)) {
    //validation succeded now.
}

即自动验证任何更新的属性。你需要只调用一次validateUpdates,在初始设置之后。

通过KVO自动验证,使您可以更轻松地验证更改。

大量赋值

大量赋值是一种通过一行代码用用户输入填充模型的高效方式。它通过直接将输入数据分配给attributes属性来填充模型的属性。以下两段代码是等效的,都试图将最终用户提交的表单数据赋给ContactForm模型的属性。显然,使用大量赋值的代码更简洁,出错的可能性也更小。

User* model = [User alloc] init];

model.attributes = @{
    @"name": @"john", 
    @"email": @"[email protected]", 
    @"subject": @"Hello", 
    @"body": @"Hi, man!"
};
User* model = [User alloc] init]
NSDictionary *data = @{
    @"name": @"john", 
    @"email": @"[email protected]", 
    @"subject": @"Hello", 
    @"body": @"Hi, man!"
};

if(data[@"name"])
    model.name = data[@"name"];

if(data[@"email"])  
    model.email = data[@"email"];

if(data[@"subject"])
    model.subject = data[@"subject"];

if(data[@"body"])
    model.body = data[@"body"];

安全属性

大量赋值仅适用于所谓的安全属性,这些属性被列在模型的当前场景的scenarioLis中。例如,如果User模型具有以下场景声明,那么当当前场景是登录时,只能大量赋值用户名和密码。其他属性将保持不变。

-(NSDictionary *)scenarioList {
    return @{
            @"login": @[@"username", @"password"],
            @"register": @[@"username", @"email", @"password"],
    };
};

信息:大量赋值仅适用于安全属性的原因是因为你想控制哪些属性可以通过最终用户数据修改。

因为默认的sucaiList实现将返回在rules中找到的所有场景和属性,如果你没有重写这个方法,这意味着只要属性出现在一个活跃的验证规则中,它就是安全的。

因此,提供了一个名为safe的特殊验证器别名,使你可以声明一个属性为safe,实际上并不验证它。例如,以下规则声明titledescription都是安全属性。

-(NSArray *)rules {
    return @[
            @{
                    FXModelValidatorAttributes : @[@"title", @"description"],
                    FXModelValidatorType : @"safe",
            },
    ];
}

不安全属性

如上所述,sucaiList方法有两个用途:确定哪些属性应该被验证,以及确定哪些属性是安全的。在某些罕见的情况下,你可能想验证一个属性,但不想将其标记为安全。你可以在将属性声明在sucaiList中时将其前缀为一个感叹号!来做到这一点,就像以下例子中的secret属性。

-(NSDictionary *)scenarioList {
    return @{
            @"login": @[@"username", @"password", @"!secret"],
    };
};

当模型处于登录场景时,将验证所有三个属性。但是,只有用户名密码属性可以大量分配。要将输入值分配给密钥属性,您必须显式地这样做,如下所示:

model.secret = @(42);

最佳实践

模型是表示业务数据、规则和逻辑的中心位置。它们通常需要在不同地方重复使用。在设计良好的应用程序中,模型的体积通常比控制器大得多。

总结来说,模型

  • 可能包含属性以表示业务数据;
  • 可以包含验证规则以确保数据的有效性和完整性;
  • 可以包含实现业务逻辑的方法;
  • 避免在单个模型中拥有过多的场景。

当您开发大型复杂系统时,您可能会通常考虑上述最后一个建议。在这些系统中,模型可能非常庞大,因为它们被用于许多地方,并且可能包含许多规则和业务逻辑集。这通常会导致在维护模型代码时的噩梦,因为对代码的单次触摸可能会影响不同的几个地方。为了使模型代码更容易维护,您可能采取以下策略

  • 定义一组由应用程序不同部分共享的基础模型类。这些模型类应包含所有用法中共同的最小规则和逻辑集合。

  • 通过从相应的基模型类扩展来定义一个具体的模型类。具体模型类应包含特定于该应用程序部分的规定和逻辑。

API 文档

完整的 API 文档从源代码自动生成,可以通过CocoaDocs访问。

版权

这个库很大程度上基于 PHP 框架 Yii。与模型一起工作的方法已被时间和许多开发者证明和测试过。

必须将该库视为一个独立的项目,具有从地面创建的自己的代码库。