【问题标题】:Does XCode Floor "Min Value" and "Max Value" for Core Data Decimal Attributes?XCode 是否为核心数据十进制属性设置“最小值”和“最大值”?
【发布时间】:2011-08-27 09:32:02
【问题描述】:

背景

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

更新:我向 Apple 提交了错误报告,并被告知它与问题 ID 9405079 重复。他们知道这个问题,但我不知道他们何时或是否会去修复它。

问题

由于某种我无法理解的原因,当我在托管对象模型中编辑 Decimal 属性时,XCode 会限制 Min ValueMax Value 约束。(我正在为the reasons described here 使用十进制属性。)

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

作为说明,以下屏幕截图中的value 属性将产生SELF >= 0SELF <= 1 的验证谓词。

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

原始解决方法

由于我在 XCode 的托管对象模型编辑器中找不到解决此问题的任何方法,我已将以下代码(由 begin workaroundend workaround cmets 表示)添加到我的应用程序委托的 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;
}

问题

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

复制代码

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

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

按照以下步骤使用此代码。

  1. 在 Interface Builder 中实例化 DebugController
  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

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

谢谢大家。

【问题讨论】:

  • 是的,我验证了行为,但我找不到任何表明它被错误地保存在 .xcdatamodeld/entities 文件中的东西,所以我怀疑它不是真正的 xcode正在做。可能是运行时环境,也许您应该提交错误报告。您需要使用 Decimal 类型是否有特殊原因?
  • @Grady 谢谢,我很高兴我不是唯一遇到此问题的人。为了回答您的问题,我使用 Decimal 数字来表示它们提供的精度(以 10 为底的数学而不是浮点精度),如前所述 on Stack Overflow and elsewhere
  • 我猜你只需要实现你自己的 -(BOOL)validate:(id *)ioValue error:(NSError **)outError,将最接近默认行为。跨度>
  • @Grady 感谢您的建议!我刚刚更新了我的问题以包含您的解决方法。我实际上曾考虑过提交错误报告,但我不确定在哪里和/或如何提交。你能指出我正确的方向吗?

标签: objective-c xcode cocoa core-data


【解决方案1】:

我又做了一个测试,我怀疑它与NSNumberNSDecimalNumbercompare:方法有关。

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");
    }

输出是:

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

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

使用-(BOOL)validate&lt;key&gt;:(id *)ioValue error:(NSError **)outError,您可以实现接近默认行为的行为(如described here)。

例如(取自问题正文,由 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;
}

【讨论】:

  • 这很有趣,我得到了同样的行为。你会认为compare 是可交换的,但事实并非如此。我想知道在第一个条件语句中NSNumber 是否被转换为NSDecimalNumber,而在第二个条件语句中NSDecimalNumber 是否被转换为NSNumber——这比更容易出现浮点错误NSDecimalNumber 并因此未能通过相等性测试...
  • 是的,我不知道。似乎是问题的根源,也许如果我们提交错误报告,我们会得到更好的答案
  • 1.2 应该没有浮动问题,反正我不认为
  • 您能否更新您的答案以包括您对使用-(BOOL)validate&lt;key&gt;:(id *)ioValue error:(NSError **)outError 的建议?这就是我选择使用的解决方法,我想感谢您。如果/当我从 Apple 获得有关此错误的更新时,我将编辑我的问题以包含该信息。
  • 谢谢!我稍后会更新我的问题以消除冗余。
【解决方案2】:

这是一个很长的镜头,但您尝试过 1,2 而不是 1.2?

【讨论】:

  • 哦,那不是很尴尬...我只是试了一下,Max Value 的文本字段自动将我的输入更正为 1
【解决方案3】:

这很重要:http://citeseer.ist.psu.edu/viewdoc/download;jsessionid=86013D0FEFFA6CD1A626176C5D4EF9E2?doi=10.1.1.102.244&rep=rep1&type=pdf

这是一个关于你的问题的故事 - 我认为

记住小数不是小数 - 你会把小数点放在哪里?

Floatingpoints 里面有这个叫做尾数 ...

【讨论】:

  • 来自Apple documentation on decimal numbers(粗体格式是我的):“NSDecimalNumberNSNumber 的一个不可变子类,它为执行base-10 算术提供了一个面向对象的包装器。An instance 可以表示任何可以表示为 mantissa x 10 exponent 的数字,其中 mantissa 是最多 38 位长度的十进制整数,exponent 是介于 -128 和 127 之间的整数。”
  • 完全正确 - 没有分数 - 只是十进制世界中的很多数字
  • 我不确定我是否遵循您的意思。您能否提供一些示例代码来说明您的回答?
  • 这似乎是逗号/点问题。我最初认为它可能涉及浮点公差 - 我错过了它......
猜你喜欢
  • 1970-01-01
  • 2015-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-28
相关资源
最近更新 更多