如何使用NSSecureCoding保证集合类的内容?

时间:2014-03-13 16:07:03

标签: objective-c nscoding nssecurecoding

我有一个类SGBContainer的对象,它有一个名为objects的数组,其中包含类SGBObject的对象。目前,它们各自实现NSCoding但不实现NSSecureCoding。 -initWithCoder:的{​​{1}}如下所示:

SGBContainer

我想切换到使用NSSecureCoding,据我所知,这意味着将上述内容更改为:

- (id)initWithCoder:(NSCoder *)aCoder
{
    self = [self init];
    if (self)
    {
        _objects = [aCoder decodeObjectForKey:@"objects"];
    }
}

...这不是一个很大的改进,因为数组的内容将被实例化,无论他们的类。如何确保数组只包含类- (id)initWithCoder:(NSCoder *)aCoder { self = [self init]; if (self) { _objects = [aCoder decodeObjectOfClass:[NSArray class] forKey:@"objects"]; } } 的对象而不实例化它们?

5 个答案:

答案 0 :(得分:4)

虽然从文档中看不清楚(并且听起来像是一个奇怪的不合语法的方法名称),但-decodeObjectOfClasses:forKey:就是这样做的。您可以执行以下操作:

NSSet *classes = [NSSet setWithObjects:[NSArray class], [SGBObject class], nil];
_objects = [aCoder decodeObjectOfClasses:classes forKey:@"objects"];

(信用到期:见NSSecureCoding trouble with collections of custom class

答案 1 :(得分:1)

Sean D.的答案是正确的解锁代码,但有一点需要注意:基础不保证数组内部的类类型,如果它们是基础类。例如。以下代码演示了该问题:

@implementation Serializable
- (id)initWithCoder:(NSCoder *)aDecoder {
  return [super init];
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
}
+ (BOOL)supportsSecureCoding {
  return YES;
}
@end

int main(int argc, const char * argv[]) {

  NSArray *foo = @[ @1, @"Gotcha", [Serializable new] ];
  NSMutableData *archive = [NSMutableData data];
  NSKeyedArchiver *archiver =
      [[NSKeyedArchiver alloc] initForWritingWithMutableData:archive];
  archiver.requiresSecureCoding = YES;
  [archiver encodeObject:foo forKey:@"root"];
  [archiver finishEncoding];

  NSKeyedUnarchiver *unarchiver = 
     [[NSKeyedUnarchiver alloc] initForReadingWithData:archive];
  unarchiver.requiresSecureCoding = YES;
  NSSet *classes = [NSSet setWithArray:@[ [NSArray class], [Serializable class] ]];
  // Should throw, but it does not.
  NSArray *loaded = [unarchiver decodeObjectOfClasses:classes forKey:NSKeyedArchiveRootObjectKey];
  if (loaded.count > 2 && ![loaded[0] isKindOfClass:[Serializable class]]) {
    NSLog(@"Successfully performed object substitution attack.");
    NSLog(@"Class: %@", [loaded[0] class]);  // All 3 objects are
    return 1;
  }
  return 0;
}

答案 2 :(得分:0)

使用NSSecureCoding没有直接的方法,因为NSCoder不能识别集合。您必须手动清理数组,以确保它只包含SGBObject类型的对象(公平地说,这有点违背了{​​{1}}的目的。)

另一种方法是自己对数组进行编码和解码,而不是依靠NSSecureCoding这样做。

答案 3 :(得分:0)

尝试 NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:archivedData]; unarchiver.requiresSecureCoding = YES; MyClass *myClass = [unarchiver decodeObjectOfClass:[MyClass class] forKey:@"root"]; 如果您未设置unarchiver.requiresSecureCoding = YES,则不会应用任何检查。

答案 4 :(得分:0)

这是 Swift 4.2 版本:

假设您拥有自定义类 CustomClass 。 确保它符合 NSSecureCoding 协议。

class CustomClass: NSObject, NSCopying, NSSecureCoding {
    var longName: String
    var shortName: String

    init(longName: String, shortName: String) {
        self.longName = longName
        self.shortName = shortName
    }

    // MARK: - NSCoding Protocol

    required convenience init(coder aDecoder: NSCoder) {
        let longName = (aDecoder.decodeObject(forKey: "longName") as? String ?? "")
        let shortName = (aDecoder.decodeObject(forKey: "shortName") as? String ?? "")

        self.init(longName: longName, shortName: shortName)
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(self.longName, forKey: "longName")
        aCoder.encode(self.shortName, forKey: "shortName")
    }

    // MARK: - NSSecureCoding Protocol

    static var supportsSecureCoding: Bool = true

    // MARK: - NSCopying Protocol

    func copy(with zone: NSZone? = nil) -> Any {
        return CustomClass(longName: self.longName, shortName: self.shortName)
    }
}

首先,您需要将对象数组转换为二进制数据并将其存档:

let userDefaults = UserDefaults.standard
let customObjectsData = try? NSKeyedArchiver.archivedData(withRootObject: customObjectsArray, requiringSecureCoding: true)
userDefaults.set(customObjectsData, forKey: USER_DEFAULTS_DATA_KEY)
userDefaults.synchronize()

此后,您可以从 UserDefaults 中读取数据并取消存档:

if let customObjectsData = UserDefaults.standard.data(forKey: USER_DEFAULTS_DATA_KEY) {
    if let customObjects = (try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, CustomObject.self], from: customObjectsData)) as? [CustomObject] {
        // your code
    }
}