XCode是否为核心数据十进制属性设置了"最小值"和"最大值"?

Chr*_*ick 29 xcode cocoa core-data objective-c

背景

我和我之前的许多程序员一样,正致力于处理金钱的应用程序.我对Cocoa编程比较陌生,但在阅读完手册之后,我决定尝试使用Core Data,因为它提供了许多我想要的功能,并且应该让我免于重新发明轮子.无论如何,我的问题与我是否应该使用核心数据没有任何关系:它与Core Data和XCode本身的行为有关.

更新:我向Apple提交了一份错误报告,并被告知它是问题ID 9405079的副本.他们知道这个问题,但我不知道他们何时或是否要修复它.

问题

由于某些我无法理解的原因,当我在托管对象模型中编辑Decimal属性时,XCode会覆盖最小值最大值约束.(我在这里描述的原因使用了Decimal属性.)

假设我有一个名为Decimal属性的Core Data实体value(这只是为了说明;我也使用了其他属性名称).我希望它的值大于0,但因为XCode只允许我指定最小值(包括),所以我将Min Value设置为等于0.01.令我惊讶的是,这导致了验证谓词SELF >= 0!当我更改最小值时,我得到相同的结果:所有小数值都被截断(最小值被覆盖).最大值具有相同的行为.

通过图示的方式,value在下面的截图属性将导致验证谓词SELF >= 0SELF <= 1.

在XCode中配置的值

但奇怪的是,如果我将此属性的类型更改为DoubleFloat,则验证谓词将更改为SELF >= 0.5SELF <= 1.2,如预期的那样.更奇怪的是,如果我按照Core Data Utility Tutorial创建自己的数据模型,即使对于十进制属性,验证谓词也会正确设置.

原始解决方法

由于我在XCode的托管对象模型编辑器中找不到任何解决此问题的方法,因此我在应用程序委托的方法中添加了以下代码(由begin workaroundend workaroundcomments 指示)managedObjectModel(这与XCode默认提供的应用程序委托相同)您创建一个使用Core Data的新项目.请注意,我添加了一个约束来保持Transaction实体的amount属性大于0.

- (NSManagedObjectModel *)managedObjectModel {

    if (managedObjectModel) return managedObjectModel;

    managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];

    // begin workaround
    NSEntityDescription *transactionEntity = [[managedObjectModel entitiesByName] objectForKey:@"Transaction"];
    NSAttributeDescription *amountAttribute = [[transactionEntity attributesByName] objectForKey:@"amount"];
    [amountAttribute setValidationPredicates:[NSArray arrayWithObject:[NSPredicate predicateWithFormat:@"SELF > 0"]]
                      withValidationWarnings:[NSArray arrayWithObject:@"amount is not greater than 0"]];
    // end workaround

    return managedObjectModel;
}
Run Code Online (Sandbox Code Playgroud)

问题

  1. 这真的是XCode如何为Core Data的托管对象模型中的十进制属性生成验证谓词的错误吗?
  2. 如果是这样,有没有比我在这里描述的更好的解决方法?

Repro Code

您应该能够使用以下DebugController类的示例代码重现此问题,该类将托管对象模型中的每个属性的约束打印到标签.此代码做出以下假设.

  • 您有一个名为的应用程序委托 DecimalTest_AppDelegate
  • 您的应用程序委托有一个managedObjectContext方法
  • 您的托管对象模型名为"Wallet"

请执行以下步骤以使用此代码.

  1. DebugController在Interface Builder中实例化.
  2. 将控制器的appDelegate插座连接到应用程序代理.
  3. 将包装标签(NSTextField)添加到用户界面并将控制器的debugLabel插座连接到它.
  4. 在用户界面中添加一个按钮,并将其选择器连接到控制器的updateLabel操作.
  5. 启动您的应用程序,然后按下与updateLabel操作相关的按钮.这会打印您的托管对象模型的约束,debugLabel并且应该说明我在此处描述的行为.

DebugController.h

#import <Cocoa/Cocoa.h>
// TODO: Replace 'DecimalTest_AppDelegate' with the name of your application delegate
#import "DecimalTest_AppDelegate.h"


@interface DebugController : NSObject {

    NSManagedObjectContext *context;

    // TODO: Replace 'DecimalTest_AppDelegate' with the name of your application delegate
    IBOutlet DecimalTest_AppDelegate *appDelegate;
    IBOutlet NSTextField *debugLabel;

}

@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;

- (IBAction)updateLabel:sender;

@end
Run Code Online (Sandbox Code Playgroud)

DebugController.m

#import "DebugController.h"

@implementation DebugController

- (NSManagedObjectContext *)managedObjectContext
{
    if (context == nil)
    {
        context = [[NSManagedObjectContext alloc] init];
        [context setPersistentStoreCoordinator:[[appDelegate managedObjectContext] persistentStoreCoordinator]];
    }
    return context;     
}

- (IBAction)updateLabel:sender
{
    NSString *debugString = @"";

    // TODO: Replace 'Wallet' with the name of your managed object model
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Wallet" inManagedObjectContext:[self managedObjectContext]];
    NSArray *properties = [entity properties];

    for (NSAttributeDescription *attribute in properties)
    {
        debugString = [debugString stringByAppendingFormat:@"\n%@: \n", [attribute name]];
        NSArray *validationPredicates = [attribute validationPredicates];
        for (NSPredicate *predicate in validationPredicates)
        {
            debugString = [debugString stringByAppendingFormat:@"%@\n", [predicate predicateFormat]];
        }
    }
    //  NSPredicate *validationPredicate = [validationPredicates objectAtIndex:1];
    [debugLabel setStringValue:debugString];
}

@end
Run Code Online (Sandbox Code Playgroud)

感谢大家.

Gra*_*yer 2

我又做了一次测试,我怀疑这与和 的compare:方法有关。NSNumberNSDecimalNumber

NSDecimalNumber * dn = [NSDecimalNumber decimalNumberWithString:@"1.2"];

if ([dn compare:[NSNumber numberWithFloat:1.2]]==NSOrderedSame) {
        NSLog(@"1.2==1.2");
    }else{
        NSLog(@"1.2!=1.2");
    }

    if ([[NSNumber numberWithFloat:1.2] compare:dn]==NSOrderedSame) {
        NSLog(@"1.2==1.2");
    }else{
        NSLog(@"1.2!=1.2");
    }
Run Code Online (Sandbox Code Playgroud)

输出是:

2011-06-08 14:39:27.835 decimalTest[3335:903] 1.2==1.2
2011-06-08 14:39:27.836 decimalTest[3335:903] 1.2!=1.2
Run Code Online (Sandbox Code Playgroud)

编辑:以下解决方法最初是我添加到问题中的评论,最终被改编到问题正文中。

使用-(BOOL)validate<key>:(id *)ioValue error:(NSError **)outError您可以实现接近默认行为的行为(如此处所述)。

例如(摘自问题正文,由 OP Chris 撰写):

-(BOOL)validateAmount:(id *)ioValue error:(NSError **)outError {

    // Assuming that this is a required property...
    if (*ioValue == nil)
    {
        return NO;
    }

    if ([*ioValue floatValue] <= 0.0)
    {
        if (outError != NULL)
        {
            NSString *errorString = NSLocalizedStringFromTable(
                @"Amount must greater than zero", @"Transaction",
                @"validation: zero amount error");

            NSDictionary *userInfoDict = [NSDictionary dictionaryWithObject:errorString
                forKey:NSLocalizedDescriptionKey];

            // Assume that we've already defined TRANSACTION_ERROR_DOMAIN and TRANSACTION_INVALID_AMOUNT_CODE
            NSError *error = [[[NSError alloc] initWithDomain:TRANSACTION_ERROR_DOMAIN
                code:TRANSACTION_INVALID_AMOUNT_CODE
                userInfo:userInfoDict] autorelease];
            *outError = error;
        }

        return NO;
    }



  return YES;
}
Run Code Online (Sandbox Code Playgroud)