Core Data Transformable attributes NOT working with NSPredicate

☆樱花仙子☆ 提交于 2019-12-12 07:57:02

问题


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?


回答1:


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



回答2:


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.




回答3:


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];


来源:https://stackoverflow.com/questions/6104566/core-data-transformable-attributes-not-working-with-nspredicate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!