Значит ли XCode "минимальное значение" и "максимальное значение" для базовых атрибутов ключевых данных?
Фон
Я, как и множество программистов передо мной, работаю над приложением, которое занимается деньгами. Я относительно новичок в программировании Cocoa, но после прочтения руководств я решил, что попытаюсь использовать Core Data, потому что он предоставляет ряд функций, которые я хочу, и должен спасти меня от повторного создания колеса. Во всяком случае, мой вопрос не имеет никакого отношения к тому, следует ли мне использовать Core Data: он имеет отношение к поведению Core Data и XCode.
ОБНОВЛЕНИЕ: Я отправил отчет об ошибке в Apple и был проинформирован о том, что это дубликат идентификатора проблемы 9405079. Они знают об этой проблеме, но я понятия не имею, когда или если они идут для его исправления.
Проблема
По какой-то причине я не могу понять, XCode устанавливает ограничения Минимальное значение и Максимальное значение при редактировании свойства Decimal в моей модели управляемых объектов. (Я использую десятичные свойства для причин, описанных здесь.)
Предположим, что у меня есть объект Core Data с атрибутом Decimal с именем value
(это просто для иллюстрации, я также использовал другие имена атрибутов). Я хочу, чтобы он имел значение больше 0, но поскольку XCode разрешит мне указывать минимальное значение (включительно), я устанавливаю Min Value равным 0.01
. К моему большому удивлению, это приводит к предикату проверки SELF >= 0
! Я получаю тот же результат, когда меняю минимальное значение: все дробные значения усекаются (минимальное значение перекрывается). Максимальное значение имеет такое же поведение.
В качестве иллюстрации свойство value
на следующем скриншоте приведет к предикатам проверки SELF >= 0
и SELF <= 1
.
![value configured in XCode]()
Как ни странно, если я изменил тип этого свойства на Двойной или Float, предикаты проверки будут изменены на SELF >= 0.5
и SELF <= 1.2
, как и ожидалось. Еще страннее, если я создаю свою собственную модель данных, следуя Учебное пособие по основным данным, предикаты проверки корректно задаются даже для десятичных свойств.
Исходное обходное решение
Поскольку я не могу найти какой-либо способ исправить эту проблему в редакторе объектной модели, управляемой XCode, я добавил следующий код, обозначенный комментариями begin workaround
и end workaround
, для моего метода делегирования приложения managedObjectModel
(например, это тот же делегат приложения, который XCode предоставляет по умолчанию при создании нового проекта, который использует Core Data). Обратите внимание, что я добавляю ограничение, чтобы свойство Transaction
entity 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;
}
Вопросы
- Это действительно ошибка в том, как XCode генерирует предикаты проверки десятичных свойств в моделях управляемых объектов для Core Data?
- Если да, есть ли лучший способ обойти его, чем те, которые я описал здесь?
Код Repro
Вы должны иметь возможность воспроизвести эту проблему со следующим примером кода для класса DebugController
, который печатает ограничения на каждое свойство в модели управляемого объекта на ярлык. Этот код делает следующие предположения.
- У вас есть делегат приложения с именем
DecimalTest_AppDelegate
- Ваш делегат приложения имеет метод
managedObjectContext
- Ваша управляемая объектная модель называется "Кошелек"
Для использования этого кода выполните следующие действия.
- Создайте экземпляр
DebugController
в построителе интерфейсов.
- Подключите контроллер
appDelegate
к делегату приложения.
- Добавьте в свой пользовательский интерфейс метку упаковки (
NSTextField
) и подключите к ней выход контроллера debugLabel
.
- Добавьте кнопку в свой пользовательский интерфейс и подключите ее селектор к действию контроллера
updateLabel
.
- Запустите приложение и нажмите кнопку, подключенную к действию
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
Спасибо всем.
Ответы
Ответ 1
Я сделал еще один тест, и я подозреваю, что он связан с методом compare:
NSNumber
и NSDecimalNumber
.
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<key>:(id *)ioValue error:(NSError **)outError
, вы можете реализовать поведение, близкое к поведению по умолчанию (как описано ).
Например (взято из органа вопроса, написанного О. П. Крисом):
-(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;
}
Ответ 2
Это очень длинный выстрел, но вы попробовали 1,2 вместо 1.2?
Ответ 3
Это важно: http://citeseer.ist.psu.edu/viewdoc/download;jsessionid=86013D0FEFFA6CD1A626176C5D4EF9E2?doi=10.1.1.102.244&rep=rep1&type=pdf
Это рассказ о вашей проблеме - я думаю,
И помните, что десятичная дробь не является дробной - где бы вы поместили десятичную точку?
В плавающих точках это внутри называется мантисса...