核心数据可转换属性不适用于 NSPredicate

发布于 2024-11-08 22:50:29 字数 428 浏览 0 评论 0原文

我经常将 Transformable 用于 Core Data 属性,因此我可以稍后更改它们。

但是,似乎如果我想使用 NSPredicate 查找 NSManagedObject,请使用 "uniqueKey == %@"“uniqueKey MATCHES[cd] %@”,它没有按预期工作。

它总是会错过匹配的对象,直到我将匹配对象的 uniqueKey 的属性更改为具有特定的类,例如 NSStringNSNumber

有人可以解释使用带有 Transformable 属性的 NSPredicate 的限制吗?

I often use Transformable for Core Data attributes, so I can change them later.

However, it seems like, if I want to use NSPredicate to find a NSManagedObject, using "uniqueKey == %@", or "uniqueKey MATCHES[cd] %@", it's not working as it should.

It always misses matching objects, until I change the attributes of the uniqueKey of the matching object to have specific class like NSString, or NSNumber.

Can someone explain the limitation of using NSPredicate with Transformable attributes?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

情深已缘浅 2024-11-15 22:50:29

注意:我不确定自 5/2011 以来何时/是否发生了变化(来自 Scott Ahten 接受的答案),但您绝对可以使用 NSPredicate 对可转换属性进行搜索。 Scott 正确地解释了为什么你的假设被打破,但是如果你的问题是有人能解释使用带有 Transformable 属性的 NSPredicate 的限制吗?,他暗示这是不可能的,这是不正确的。

由于这是“核心数据可转换值搜索nspredicate”(我搜索试图寻找灵感的内容)的第一个谷歌搜索,我想添加我的工作答案。

如何使用具有可转换属性的 NSPredicate

简短而令人兴奋的答案:您需要对数据转换器保持聪明。您需要将值转换为 NSData,其中包含我所说的“原始识别信息”,即可用于重建对象的最小、最具识别性的字节集。长答案,...

最重要的是,考虑:

  • 您是否真的打算使用可转换属性?如果任何受支持的数据类型(甚至是二进制数据)就足够了,请使用它。
  • 你知道什么是可变形属性吗?他们如何打包和解包来自商店的数据?查看非标准持久属性苹果的文档。
    • 阅读完上述内容后,请问:隐藏受支持类型“支持属性”的自定义代码对您有用吗?可能会使用该技术。

现在,除了这些考虑之外,可变形属性相当灵活。坦率地说,为 Foo 实例编写一个 NSValueTransformer“FooToData” 到 NSData 似乎比编写大量临时自定义代码更干净。我还没有发现 Core Data 不知道它需要使用注册的 NSValueTransformer 来转换数据的情况。

继续简单地解决这些问题:

  • 您是否告诉 Core Data 使用什么变压器?在表视图中打开核心数据模型,单击实体,单击属性,加载数据模型检查器窗格。在“属性类型:可变形”下,将“名称”设置为您的变压器。
  • 使用默认的转换器(同样,请参阅之前的 Apple 文档)或编写自己的转换器—​​—transformedValue:必须返回 NSData。
    • NSKeyedUnarchiveFromDataTransformerName 是默认转换器,可能还不够,或者可能会绘制一些短暂的实例数据,这些数据可能使两个相似的对象在相等时有所不同。
  • 转换后的值应该只包含——我称之为——“原始识别信息”。存储将比较字节,因此每个字节都很重要。
  • 您还可以在全球范围内注册您的变压器。我必须这样做,因为我实际上在应用程序的其他地方重用它们 - 例如 NSString *name = @"FooTrans"; [NSValueTransformer setValueTransformer:[NSClassFromString(name) new] forName:name];

您可能不想使用大量查询数据操作的转换 - 例如,主键信息使用变压器的大型导入 - 哎呀!

最后,我只是用它来测试具有 NSPredicates 的模型上高级对象属性的相等性——例如“%K == %@”——并且它工作得很好。我还没有尝试过一些不同的匹配术语,但如果它们有时有效而有时无效,我不会感到惊讶。

这是 NSURL 到 NSData 转换器的示例。为什么不只存储字符串呢?是的,这很好——这是自定义代码屏蔽存储属性的一个很好的例子。这个例子说明了一个额外的字节被添加到字符串化的 URL 中来记录它是否是一个文件 URL——让我们知道在解包对象时要使用什么构造函数。

// URLToDataTransformer.h - interface
extern NSString *const kURLToDataTransformerName;
@interface URLToDataTransformer : NSValueTransformer
@end

...

// URLToDataTransformer.m - implementation
#import "URLToDataTransformer.h"
NSString *const kURLToDataTransformerName = @"URLToDataTransformer";

@implementation URLToDataTransformer
+ (Class)transformedValueClass { return [NSData class]; }
+ (BOOL)allowsReverseTransformation { return YES; }

- (id)transformedValue:(id)value
{
    if (![value isKindOfClass:[NSURL class]])
    {
        // Log error ...
        return nil;
    }
    NSMutableData *data;
    char fileType = 0;
    if ([value isFileURL])
    {
        fileType = 1;
        data = [NSMutableData dataWithBytes:&fileType length:1];
        [data appendData:[[(NSURL *)value path] dataUsingEncoding:NSUTF8StringEncoding]];
    }
    else
    {
        fileType = -1;
        data = [NSMutableData dataWithBytes:&fileType length:1];
        [data appendData:[[(NSURL *)value absoluteString] dataUsingEncoding:NSUTF8StringEncoding]];
    }
    return data;
}

- (id)reverseTransformedValue:(id)value
{
    if (![value isKindOfClass:[NSData class]])
    {
        // Log error ...
        return nil;
    }

    NSURL *url = nil;
    NSData *data = (NSData *)value;
    char fileType = 0;
    NSRange range = NSMakeRange(1, [data length]-1);
    [data getBytes:&fileType length:1];
    if (1 == fileType)
    {
        NSData *actualData = [data subdataWithRange:range];
        NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
        url = [NSURL fileURLWithPath:str];
    }
    else if (-1 == fileType)
    {
        NSData *actualData = [data subdataWithRange:range];
        NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
        url = [NSURL URLWithString:str];
    }
    else
    {
        // Log error ...
        return nil;
    }

    return url;
}
@end

Note: I'm not sure when/if this has changed since 5/2011 (from Scott Ahten's accepted answer), but you can absolutely search with NSPredicate on transformable attributes. Scott correctly explained why your assumptions were broken, but if Can someone explain the limitation of using NSPredicate with Transformable attributes? was your question, he implied that it is not possible, and that is incorrect.

Since the is the first google hit for "Core Data transformable value search nspredicate" (what I searched for trying to find inspiration), I wanted to add my working answer.

How to use NSPredicate with transformable properties

Short, heady answer: you need to be smart about your data transformers. You need to transfrom the value to NSData that contains what I'll call "primitive identifying information", i.e. the smallest, most identifying set of bytes that can be used to reconstruct your object. Long answer, ...

Foremost, consider:

  • Did you actual mean to use a transformable attribute? If any supported data type -- even binary data -- will suffice, use it.
  • Do you understand what transformable attributes actually are? How they pack and unpack data to and from the store? Review Non-Standard Persistent Attributes in Apple's documentation.
    • After reading the above, ask: does custom code that hides a supported type "backing attribute" work for you? Possibly use that technique.

Now, past those considerations, transformable attributes are rather slick. Frankly, writing an NSValueTransformer "FooToData" for Foo instances to NSData seemed cleaner than writing a lot of adhoc custom code. I haven't found a case where Core Data doesn't know it needs to transform the data using the registered NSValueTransformer.

To proceed simply address these concerns:

  • Did you tell Core Data what transformer to use? Open the Core Data model in table view, click the entity, click the attribute, load the Data Model Inspector pane. Under "Attribute Type: Transformable", set "Name" to your transformer.
  • Use a default transformer (again, see the previous Apple docs) or write your own transformer -- transformedValue: must return NSData.
    • NSKeyedUnarchiveFromDataTransformerName is the default transformer and may not suffice, or may draw in somewhat-transient instance data that can make two similar objects be different when they are equal.
  • The transformed value should contain only -- what I'll call -- "primitive identifying information". The store is going to be comparing bytes, so every byte counts.
  • You may also register your transformer globally. I have to do this since I actually reuse them elsewhere in the app -- e.g. NSString *name = @"FooTrans"; [NSValueTransformer setValueTransformer:[NSClassFromString(name) new] forName:name];

You probably don't want to use transforms heavily queried data operations - e.g. a large import where the primary key information uses transformers - yikes!

And then in the end, I simply use this to test for equality for high-level object attributes on models with NSPredicates -- e.g. "%K == %@" -- and it works fine. I haven't tried some of the various matching terms, but I wouldn't be surprised if they worked sometimes, and others not.

Here's an example of an NSURL to NSData transformer. Why not just store the string? Yeah, that's fine -- that's a good example of custom code masking the stored attribute. This example illustrates that an extra byte is added to the stringified URL to record if it was a file URL or not -- allowing us to know what constructors to use when the object is unpacked.

// URLToDataTransformer.h - interface
extern NSString *const kURLToDataTransformerName;
@interface URLToDataTransformer : NSValueTransformer
@end

...

// URLToDataTransformer.m - implementation
#import "URLToDataTransformer.h"
NSString *const kURLToDataTransformerName = @"URLToDataTransformer";

@implementation URLToDataTransformer
+ (Class)transformedValueClass { return [NSData class]; }
+ (BOOL)allowsReverseTransformation { return YES; }

- (id)transformedValue:(id)value
{
    if (![value isKindOfClass:[NSURL class]])
    {
        // Log error ...
        return nil;
    }
    NSMutableData *data;
    char fileType = 0;
    if ([value isFileURL])
    {
        fileType = 1;
        data = [NSMutableData dataWithBytes:&fileType length:1];
        [data appendData:[[(NSURL *)value path] dataUsingEncoding:NSUTF8StringEncoding]];
    }
    else
    {
        fileType = -1;
        data = [NSMutableData dataWithBytes:&fileType length:1];
        [data appendData:[[(NSURL *)value absoluteString] dataUsingEncoding:NSUTF8StringEncoding]];
    }
    return data;
}

- (id)reverseTransformedValue:(id)value
{
    if (![value isKindOfClass:[NSData class]])
    {
        // Log error ...
        return nil;
    }

    NSURL *url = nil;
    NSData *data = (NSData *)value;
    char fileType = 0;
    NSRange range = NSMakeRange(1, [data length]-1);
    [data getBytes:&fileType length:1];
    if (1 == fileType)
    {
        NSData *actualData = [data subdataWithRange:range];
        NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
        url = [NSURL fileURLWithPath:str];
    }
    else if (-1 == fileType)
    {
        NSData *actualData = [data subdataWithRange:range];
        NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
        url = [NSURL URLWithString:str];
    }
    else
    {
        // Log error ...
        return nil;
    }

    return url;
}
@end
最美的太阳 2024-11-15 22:50:29

可转换属性通常作为存档的二进制数据保存。因此,您尝试将 NSData 的实例与 NSString 或 NSNumber 的实例进行比较。

由于这些类以不同的方式解释相同的数据,因此它们不被视为匹配。

Transformable attributes are usually persisted as archived binary data. As such, you are attempting to compare an instance of NSData with an instance of NSString or NSNumber.

Since these classes interpret the same data in different ways, they are not considered a match.

如梦初醒的夏天 2024-11-15 22:50:29

你可以试试这个方法

NSExpression *exprPath = [NSExpression expressionForKeyPath:@"transformable_field"];
NSExpression *exprKeyword = [NSExpression expressionForConstantValue:nsdataValue];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:exprPath rightExpression:exprKeyword modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0];

you can try this way

NSExpression *exprPath = [NSExpression expressionForKeyPath:@"transformable_field"];
NSExpression *exprKeyword = [NSExpression expressionForConstantValue:nsdataValue];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:exprPath rightExpression:exprKeyword modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0];
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文