如何强制基于 NSObject 的基础 class 的子 class 覆盖 class 级变量?

How can you enforce a subclass of an NSObject-based base class override a class-level variable?

再次完全重写这个问题,希望将重点放在我试图解决的问题上。

我们有以下 class,由于我们无法控制的外部框架,需要我们实现 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"
        ]
    }
}

可以看到,在getBla()的实现中使用了possibleValues。此外,它必须对特定子 class 的所有实例都是通用的,因此是 class 变量而不是实例变量。这是因为在代码的另一个地方,我们必须检索所有子 class 中的所有可能值,就像这样...

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

如果 FooBase 的子 class 没有实现 possibleValues.

,我想弄清楚如何让编译器抱怨

换句话说,我们如何让这个报告编译时错误:

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

目前我知道的唯一方法是上面的方法,在基础 class 中用 preconditionFailure 或类似的方法定义它,但这是运行时检查,而不是编译时,所以它不是最佳解决方案。

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

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()

What I'm trying to figure out how to do is make the compiler complain if a subclass of FooBase does not implement possibleValues.

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

也许是这样的?我没有敲定每一个细节,但我认为这涵盖了你的很多编程问题。如果您想进一步讨论,让我们创建一个聊天室,而不是继续在这里 post 没完没了的评论。

// 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)
    }
}