取消归档返回零和格式不正确的编码数据

时间:2018-09-21 12:20:50

标签: ios nscoding nskeyedarchiver nskeyedunarchiver

我在NSUserDefaults中添加了类别方法来存储和检索编码对象(在本例中为NSArray)。我在检索编码数据时遇到问题。这是我的代码:

- (void)encodeObject:(id<NSCoding>)object forKey:(NSString *)key {
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object requiringSecureCoding:NO error:nil];
    [self setObject:data forKey:key];
    [self synchronize];
}

- (id)decodeObjectForKey:(NSString *)key class:(Class)aClass {
    NSData *data = [self objectForKey:key];
    NSError *error = nil;
    id object = [NSKeyedUnarchiver unarchivedObjectOfClass:aClass fromData:data error:&error];
    NSLog(@"E: %@ %@", error.localizedDescription, object);
    return object;
}

调用[[NSUserDefaults standardDefaults] encodeObject:object forKey:key]应该对传递的对象进行编码并将其存储为默认值,然后调用[[NSUserDefaults standardDefaults] decodeObjectForKey:key class:aClass应该返回已编码的对象。

问题是[NSKeyedUnarchiver unarchivedObjectOfClass:fromData:error:]返回nil,并且错误文本记录为The data couldn’t be read because it isn’t in the correct format.。使用[self objectForKey:]检索的数据为__NSCFData类型。我不知道这是否相关,因为AFAIK __NSCFData是免费电话桥接到NSData。

[NSKeyedUnarchiver unarchivedObjectOfClass:fromData:error:]代替[NSKeyedUnarchiver unarchiveObjectWithData:]解决了这个问题。数据已正确存储和检索。但是现在不赞成使用此方法,因此我需要转到更现代的方法,但无法确定为什么它不起作用。

4 个答案:

答案 0 :(得分:2)

我有一个类似的问题。我发现我必须传递NSArray中对象的类。

NSSet *set = [NSSet setWithArray:@[
                      [NSArray class],
                      [STUFF_IN_ARRAY class]
                      ]];

NSArray *results = [NSKeyedUnarchiver unarchivedObjectOfClasses:set fromData: rawData error: &error];

答案 1 :(得分:2)

对于自定义类,其他答案都没有足够涵盖(或者说得足够清楚),因此希望我可以通过添加答案来帮助某些人!

如果数据代表具有各种类属性的自定义类的存档数组,则仅提供数组类和自定义类是不够的,您需要提供自定义类用于的所有类,例如:

NSData* data;
NSError* error;
[NSKeyedUnarchiver unarchivedObjectOfClasses:
                        [NSSet setWithArray: @[
                             [NSMutableArray class],
                             [CustomClass class],
                             // CustomClass has properties using the following classes:
                             [NSDate class],
                             [NSString class],
                             [NSNumber class],
                             [NSIndexSet class]
                         ]]
                         fromData: data
                         error: &error];

答案 2 :(得分:1)

为了获得灵感,我正在解决通过[NSKeyedUnarchiver unarchivedObjectOfClasses:fromData:]从文件取消存档数据的方法。 改进很小:您不需要在方法调用中列出所有类,因为最常用的类会自动添加到方法主体中。

// MyFileManager.m
- (id)getDataFromFile:(NSString *)file inSubfolder:(NSString *)subfolder dataClasses:(NSArray<Class> *)classes {
    NSString *filePath = [self pathForFile:file subfolder:subfolder];

    NSData *data = [[NSFileManager defaultManager] contentsAtPath:filePath];
    NSArray *extendedClasses = [classes arrayByAddingObjectsFromArray:@[[NSArray class], [NSMutableArray class], [NSDictionary class], [NSMutableDictionary class], [NSDate class], [NSNumber class]]];

    NSError *unarchivingError;
    id unarchived = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithArray:extendedClasses] fromData:data error:&unarchivingError];

    if (unarchivingError) {
        return nil;
    } else {
        return unarchived;
    }
}

这是该方法的用法 //预期的未归档数据在字典中

NSDictionary *dictionary = [FILE_MANAGER getDataFromFile:@"testfile" inSubfolder:nil dataClasses:@[[MyClass class]]];

还有一个小建议。您的“ MyClass”必须符合“ NSSecureCoding”协议。并在您的“ MyClass”中实现3种方法...

@interface MyClass : NSObject <NSSecureCoding> // MyClass.h

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic) NSInteger count;

@end

@implementation MyClass // MyClass.m

- (id)initWithCoder:(NSCoder *)decoder {
    if (self = [super init]) {
        self.name = [decoder decodeObjectOfClass:[NSString class] forKey:@"name"];
        self.date = [decoder decodeObjectOfClass:[NSDate class] forKey:@"date"];
        self.count = [decoder decodeIntegerForKey:@"count"];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeObject:self.name forKey:@"name"];
    [encoder encodeObject:self.date forKey:@"date"];
    [encoder encodeInteger:self.count forKey:@"count"];
}

+ (BOOL)supportsSecureCoding{
    return YES;
}

对于那些希望查看帮助方法“ pathForFile”的人-我将其用作主要的“库/应用程序支持”(因为本文表1-3 iOS File System Basics

// MyFileManager.m

- (NSString *)pathForFile:(NSString *)file subfolder:(NSString *)subfolder {

    NSString *folderPath = [self storageFolderPathWithSubfolder:subfolder];
    NSString *filePath = [folderPath stringByAppendingPathComponent:file];

    return filePath;
}

- (NSString *)storageFolderPathWithSubfolder:(NSString *)subfolder {

    NSString *storageFolderPath = [self storageFolderPath];

    if (!subfolder.length) {
        return storageFolderPath;
    }

    NSString *subfolderPath = [storageFolderPath stringByAppendingPathComponent:subfolder];

    if (![[NSFileManager defaultManager] fileExistsAtPath:subfolderPath]) { //Does directory already exist?
        [[NSFileManager defaultManager] createDirectoryAtPath:subfolderPath withIntermediateDirectories:NO attributes:@{ NSFileProtectionKey: NSFileProtectionNone } error:nil];
    }

    return subfolderPath;
}

- (NSString *)storageFolderPath {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
    NSString *storageFolderPath = [paths firstObject];

    // create folder if it doesn't exist
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL folderExists = [fileManager fileExistsAtPath:storageFolderPath];

    if (!folderExists) {
        [fileManager createDirectoryAtPath:storageFolderPath withIntermediateDirectories:NO attributes:@{ NSFileProtectionKey: NSFileProtectionNone } error:nil];
    }

    return storageFolderPath;
}

答案 3 :(得分:-1)

此行应为您工作:

[NSKeyedUnarchiver unarchivedObjectOfClass:NSArray.class fromData:data error:&err];