【问题标题】:How can you enforce a subclass of an NSObject-based base class override a class-level variable?如何强制基于 NSObject 的基类的子类覆盖类级变量?
【发布时间】:2018-03-17 20:20:33
【问题描述】:

再次完全重写此问题,希望将重点放在我要解决的问题上。

我们有以下类,由于我们无法控制的外部框架,需要我们实现ObjCProtocolRequiringBla,就像这样......

class FooBase : NSObject, ObjCProtocolRequiringBla {

    class var possibleValues:[String] {
        // Note: Use preconditionFailure, not fatalError for things like this.
        // At runtime, they both halt execution, but precondition(Failure)
        // only logs the message for debug builds. Additionally,
        // in debug builds it pauses in a debuggable state rather than crash.
        preconditionFailure("You must override possibleValues")
    }

    // This satisfies the objective-c protocol requirement
    final func getBla() -> Bla {
        let subclassSpecificValues = type(of:self).possibleValues
        return Bla(withValues:subclassSpecificValues)
    }
}

class FooA : FooBase
    override class var possibleValues:[String] {
        return [
            "Value A1",
            "Value A2",
            "Value A3"
        ]
    }
}

class FooB : FooBase
    override class var possibleValues:[String] {
        return [
            "Value B1",
            "Value B2",
            "Value B3",
            "Value B4"
        ]
    }
}

如您所见,possibleValues 用于getBla() 的实现。此外,它必须是该特定子类的所有实例所共有的,因此是类变量而不是实例变量。这是因为在代码的另一个地方,我们必须检索所有子类中所有可能的值,就像这样......

static func getAllPossibleValues:[String] {
    return [
        FooA.possibleValues,
        FooB.possibleValues
    ].flatMap { $0 }
}

如果FooBase 的子类没有实现possibleValues,我想弄清楚如何让编译器抱怨。

换句话说,我们怎样才能让这个报告成为编译时错误:

class FooC : FooBase
    // Doesn't override class var possibleValues
}

目前我知道的唯一方法是上述方法,使用preconditionFailure 或类似名称在基类中定义它,但这是运行时检查,而不是编译时,因此它不是最佳解决方案。

【问题讨论】:

    标签: swift abstract-class swift4 swift-protocols


    【解决方案1】:

    如果 FooBase 的子类没有实现 possibleValues,我想弄清楚如何让编译器抱怨。

    你不能。显然,您可以将 possibleValues 之类的东西作为协议要求并尝试采用该协议,但如果 FooBase 本身实现了 possibleValues,则如果子类未能覆盖它,您将无法让 编译器 抱怨。这不是一个愚蠢的想法,可能有一些计算机语言可以为您提供一种方法,但 Swift 不是这样的语言。这就是为什么运行时解决方案是您所希望的最好的,这就是标准模式,如果您查看 UIPopoverBackgroundView 之类的东西,您就会看到。

    【讨论】:

    • 是的。这就是我害怕的。如果不是那个 Objective-C 协议,我可以使用面向协议的编程,定义协议,然后在扩展中实现 getBla,但是你不能在扩展中满足 Objective-C 协议。如果 Swift 决定采用抽象类,这也可以解决,但看起来也不会是这样。具有讽刺意味的是,这是我在 Swift 中遇到的唯一限制,它不允许我以我想要的方式解决它……在编译时。喜欢这种语言!
    • 这个答案很好地总结了情况:stackoverflow.com/a/49346872/341994
    • “但看起来也不会如此”我不会这么说。对未来 Swift 功能的讨论正在进行中并且是开放的,您可以做出贡献。跳进去!正如我所说,您的想法绝不是愚蠢的,并且可以想象类似@mustOverride 的属性可以一口气解决问题。
    • 是的,再说一次,我通常会使用 POP,而且我是一个超级粉丝。问题是 POP 由于 Objective-C 要求而在此处失败,因此您无法在扩展中实现默认行为,这是 POP 方式。它必须在继承NSObject 的基类中实现。
    • 参见例如github.com/apple/swift-evolution/blob/master/proposals/… — 很明显,您想要的东西在料斗中。但实施肯定还没有到来。
    【解决方案2】:

    您可以通过将 FooBase 类替换为扩展 NSObjectProtocol 的协议来解决此问题。这将强制任何实现您的协议的类必须是 NSObject 的子类,并且还必须实现您想要的属性和方法。

    protocol foo: NSObjectProtocol {
        var someProp: String {get set}
    
        func someFunc() -> Void
    }
    

    要从 Objective-C 中使用协议,可以使用 @objc 注释,这至少适用于简单的场景,例如发送选择器 #selector() 作为参数。

    @objc protocol foo: NSObjectProtocol {
      var someProp: String {get set}
    
      func someFunc() -> Void
    }
    

    以下代码运行良好

    class fooClass: NSObject, foo {
      var someProp: String
    
      init(someProp: String) {
        self.someProp = someProp
      }
    
      func someFunc() {
        print("In someFunc with property: \(someProp)")
      }
    
      func timeIt() {
        let timer = Timer.scheduledTimer(timeInterval: 0,  
        target: self, selector: #selector(someFunc), userInfo: nil, repeats: false)
        print("timer created")
        timer.fire()
      }
    }
    
    func main() {
      let f = fooClass(someProp: "test")
      f.timeIt()
    }
    
    main()
    

    【讨论】:

    • 如果我错了,请纠正我,但这是否意味着在定义子类时,我现在必须继承 NSObject 并且遵守协议?如果可行,我可以接受(我必须进行实验)。
    • 我认为继承 NSObject 是一个要求。如果不是,那么就跳过扩展 NSObjectProtocol。
    • 是的,但由FooBase处理。子类简单地继承了FooBase,因此免费获得了NSObject。你说要替换FooBase,这意味着我现在也必须这样做。顺便说一句,我刚刚用更多细节更新了这个问题。 FooBase 中的函数必须可以从 Objective-C 调用。你能否编写一个支持 Objective-C 的协议(就像你在这里所做的那样),然后在它的扩展中,实现一个可从 Objective-C 调用的方法?是不是像用@objc 标记一样简单?
    • 用更多相关细节重新处理了问题。 LMK 如果这改变了你的答案。
    • 我不确定 obj-c 部分,但我使用有效的计时器进行了快速测试。我会更新我的答案。
    【解决方案3】:

    也许是这样的?我没有敲定每一个细节,但我认为这涵盖了你的很多编程问题。如果您想进一步讨论它,让我们开始一个聊天室,而不是继续在这里发布无休止的 cmets。

    // Just to get it to compile
    class Bla { init(withValues: [String]) {} }
    protocol ObjCProtocolRequiringBla {}
    
    class FooContainer : ObjCProtocolRequiringBla {
        fileprivate var fooCore: FooProtocol
    
        fileprivate init(_ fooCore: FooProtocol) { self.fooCore = fooCore }
    
        // This satisfies the objective-c protocol requirement
        final func getBla() -> Bla { return fooCore.getBla() }
    }
    
    protocol FooProtocol {
        static var possibleValues:[String] { get }
    
        func getBla() -> Bla
    }
    
    class FooA : NSObject, FooProtocol {
        class var possibleValues:[String] {
            return [
                "Value A1",
                "Value A2",
                "Value A3"
            ]
        }
    
        func getBla() -> Bla {
            return Bla(withValues: FooA.possibleValues)
        }
    }
    
    class FooB : NSObject, FooProtocol {
        class var possibleValues:[String] {
            return [
                "Value B1",
                "Value B2",
                "Value B3",
                "Value B4"
            ]
        }
    
        func getBla() -> Bla {
            return Bla(withValues: FooB.possibleValues)
        }
    }
    

    【讨论】:

    • 不完全。有几个问题。首先,FooAFooB 不实现 ObjCProtocolRequiringBla,如图所示,这是一项要求。此外,他们似乎都必须手动实现getBla(),这应该由基类处理。此外,不确定FooContainer 如何适应它,因为FooAFooB 都不是从它继承的。就像@Matt 所说的那样,由于 Objective-C 的要求,Swift 似乎不支持将其作为编译时要求的能力。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-07-22
    • 2022-06-16
    • 1970-01-01
    • 1970-01-01
    • 2017-11-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多