13

With introduction of open keyword in Swift 3.0 (What is the 'open' keyword in Swift?).

Note: Limited to extensions on NSObject derived classes or @objc attributed method/properties.

Code which declared and used public (class) methods/properties in extension across modules/frameworks broke, as public is no longer means 'overridable' outside of defining module.

Example:

public extension UIManagedDocument {

    public class func primaryDocumentName() -> String {
        return "Document"
    }

    public class func primaryStoreURL() -> URL {
        let documentsURL = FileManager.default.userDocumentsURL
        return URL(fileURLWithPath: self.primaryDocumentName(), isDirectory: false, relativeTo: documentsURL)
    }

    public class func primaryModelName() -> String? {
        return "Model"
    }

}
  • Original proposal (SE-0117) is focused on subclassing and doesn't mention extensions.
  • Currently extensions do not support open keyword (you can't write open extension NSObject as well as open func Method())

Question: Is there workaround to be able override extension provided methods/properties across modules/frameworks?

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Nocross
  • 251
  • 1
  • 4
  • 6
  • Is this really related to the new open vs public access modes? Unless I am mistaken, you cannot override methods declared in extensions anyway, neither in Swift 2 nor in Swift 3). – Martin R Aug 25 '16 at 11:31
  • You right for *pure* swift classes, but one can on NSObject derived classes as well as with `@objc` attributed methods/properties. ([Can you override between extensions in Swift or not?](http://stackoverflow.com/questions/27109006/can-you-override-between-extensions-in-swift-or-not-compiler-seems-confused#27109202)) – Nocross Aug 25 '16 at 11:37
  • I see, thanks. (Perhaps you can add that information to the question). – Martin R Aug 25 '16 at 11:38
  • And thanks for reminding me of my own answer :) – Martin R Aug 25 '16 at 11:40

2 Answers2

9

Unless I am mistaken, you can declare the extension methods as open in your framework if you just omit the public keyword in the extension declaration:

extension UIManagedDocument {

    open class func primaryDocumentName() -> String {
        return "Document"
    }
    // ...
}

And then (for NSObject subclasses or @objc members) you can override the method in your custom subclass in the main application (or in any module):

class MyManagedDocument: UIManagedDocument {

    override class func primaryDocumentName() -> String {
        return "MyDocument"
    }
    // ...
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • I can't omit `public` word, as extension is declared in another module (shared framework), and concrete subclass is implemented in the dependant target (app/extension). – Nocross Aug 25 '16 at 11:57
  • @Nocross: Actually I just tested that and it worked for me. `extension UIManagedDocument {}` is in a framework and `class MyManagedDocument: UIManagedDocument {}` in the main app. – Martin R Aug 25 '16 at 11:58
  • You are right it works. But what bothers me is that this way we are relying on swift 'inference' logic, which is at least unstable, as well as may be implementation specific for different IDEs – Nocross Aug 25 '16 at 12:06
  • @Nocross: I don't think this is IDE related. And note that you removed the public keyword in your solution as well. – Martin R Aug 25 '16 at 12:13
  • A nuance is a bit different. I omitted it, as protocol conformance extension also conforms to access modifiers from protocol. – Nocross Aug 25 '16 at 12:18
  • @Nocross: As I understand it, the default access level of an extension is the access level of the class itself (in this case: open). – Martin R Aug 25 '16 at 15:30
3
  • 'Protocol-oriented' - declare protocol with desired methods/properties then refactor your extension for protocol compliance.
  • 'Traditional' - implement intermediate (abstract) subclass with desired methods/properties.

Protocol example:

protocol PrimaryDocument {
    static func primaryDocumentName() -> String

    static func primaryStoreURL() -> URL

    static func primaryModelName() -> String?
}

extension UIManagedDocument : PrimaryDocument {

    open class func primaryDocumentName() -> String {
        return "Document"
    }

    open class func primaryStoreURL() -> URL {
        let documentsURL = FileManager.default.userDocumentsURL
        return URL(fileURLWithPath: self.primaryDocumentName(), isDirectory: false, relativeTo: documentsURL)
    }

    open class func primaryModelName() -> String? {
        return "Model"
    }

}
Nocross
  • 251
  • 1
  • 4
  • 6