4

I'm having trouble creating classes that uses NSSecureCoding and its subclasses.

class ClassA: NSObject, NSSecureCoding {
    public static var supportsSecureCoding: Bool { return true }
}

class ClassB: ClassA {
    public static var supportsSecureCoding: Bool { return true } // "Cannot override static var"
}

I'm supposed to call this since the documentation in NSObject.h says,

This property must return YES on all classes that allow secure coding. Subclasses of classes that adopt NSSecureCoding and override initWithCoder: must also override this method and return YES. // The Secure Coding Guide should be consulted when writing methods that decode data.

Objective-C:

@property (class, readonly) BOOL supportsSecureCoding;

Swift:

public static var supportsSecureCoding: Bool { get }

I am using Xcode 10.0, tried on both Swift 4.0 and Swift 4.2. How are people getting around this? Any help is appreciated.

UPDATE: When using public class var supportsSecureCoding, it compiles but it crashes at runtime when Optimize for Speed is used.

Genki
  • 3,055
  • 2
  • 29
  • 42

4 Answers4

6

Seems the current optimizer of Swift suppresses to generate the overridden getter method when its definition is the same as its superclass. What a clever optimizer!?

This sort of hack would suppress such too strong optimization.

class ClassB: ClassA {

    //...

    static private var secureCoding = true
    override public class var supportsSecureCoding: Bool { return secureCoding }

}

static private let does not have the same effect. So, when the Swift optimizer being more clever, the code above may not work. Better send a bug report soon.


Seems the Swift optimizer is already clever enough and the workaround above may not work. (See Martin R's comment.)

You may need to remove private.

class ClassB: ClassA {

    //...

    static var secureCoding = true
    override public class var supportsSecureCoding: Bool { return secureCoding }

}
OOPer
  • 47,149
  • 6
  • 107
  • 142
  • I agree that it looks like a bug. But even with your workaround it still crashes if optimized. Here is my quick and dirty test code: https://gist.github.com/martinr448/03aa3f425a636d469769020dbe3953b5. – Martin R Sep 30 '18 at 19:23
  • @MartinR, Thanks, I tested my code in an iOS project, but it seems the optimizer is already clever enough for a small project. Maybe you need to remove `private`. – OOPer Sep 30 '18 at 19:29
  • Indeed, that makes it work (in a macOS command line project)! – Martin R Sep 30 '18 at 19:32
  • Fun variant: `override public class var supportsSecureCoding: Bool { return Int.random(in: 1...1) > 0 }` – Martin R Sep 30 '18 at 19:46
  • @OOPer @MartinR Using `static private var secureCoding = true` was still crashing but `static var secureCoding = true` made it work. Thank you! What a weird compiler bug :D – Genki Oct 06 '18 at 02:38
4

static in a class declaration is an alias for final class, i.e. a type method which cannot be overridden in a subclass. What you want is a class method

public class var supportsSecureCoding: Bool { return true }

which can be overridden in the subclass with

override public class var supportsSecureCoding: Bool { return true }
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 1
    You might also need to add `@objc` to expose the method to the Objective-C runtime. – Martin R Sep 27 '18 at 20:25
  • Thanks for the reply. I tried this but I get a crash at runtime (hence I started looking into this issue) ```*** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: 'Class 'MyModel' has a superclass that supports secure coding, but 'MyModel' overrides -initWithCoder: and does not override +supportsSecureCoding. The class must implement +supportsSecureCoding and return YES to verify that its implementation of -initWithCoder: is secure coding compliant.'`` In the subclass, I do have `@objc override public class var supportsSecureCoding: Bool { return true }` – Genki Sep 27 '18 at 20:38
  • @Gomfucius: It works in my simple test. Can you post a minimal self-contained example which leads to the crash? – Martin R Sep 27 '18 at 21:21
  • can you try setting the Swift optimization level to "Optimize for Speed" and trying again? I think that's causing the issue. – Genki Sep 27 '18 at 21:24
  • 1
    @Gomfucius: Yes, same here. – It works in Debug configuration, but crashes in the Release configuration. Unfortunately, I have no idea why that happens. – Martin R Sep 27 '18 at 21:34
1

I could not get any of the above answers to work. This is definitely a bug on Apple's side they will need to address so make sure you send it in the Feedback system. I even added the @objc without luck.

The Swift 5.3 compiler now gives a deprecation message for NSKeyedArchiver for initializations that don't specify secure coding.. but apparently the language doesn't allow for overriding this in subclasses.

When I don't override the variable I get: Error: Class 'Subclass' has a superclass that supports secure coding, but 'Subclass' overrides -initWithCoder: and does not override +supportsSecureCoding. The class must implement +supportsSecureCoding and return YES to verify that its implementation of -initWithCoder: is secure coding compliant. (NSInvalidUnarchiveOperationException)

When I do override the variable I get: "Cannot override a static variable"

CONDITIONAL WORKAROUND:

This workaround worked for me, as I have a super type that is sort of abstract in that it is never used without a subtype. If you have a similar setup, this should work for you:

public class Supertype: NSObject, NSCoding {
     ...

}

Subtype:

public class SubType: Supertype, NSSecureCoding {

     public static var supportsSecureCoding = true
     ...
}

This way the var is at least in all subtypes that I use.

If you instantiate and work with Supertype directly, this will not work for you.

TheJeff
  • 3,665
  • 34
  • 52
0

Fix that worked for me

private static var secureCodingWorkaround = true
@objc override public class var supportsSecureCoding: Bool { return secureCodingWorkaround }