Swift 属性 符合协议并且 Class

Swift Property that conforms to a Protocol and Class

@property (strong, nonatomic) UIViewController<UITableViewDelegate> *thing;

我想像 Swift 中的 Objective-C 代码一样实现 属性。所以这是我尝试过的:

class AClass<T: UIViewController where T: UITableViewDelegate>: UIViewController {
    var thing: T!
}

编译通过。当我从情节提要中添加属性时,我的问题就来了。 @IBOutlet 标记生成编译器错误。

class AClass<T: UIViewController where T: UITableViewDelegate>: UIViewController {
    @IBOutlet weak var anotherThing: UILabel!  // error
    var thing: T!
}

错误:

Variable in a generic class cannot be represented in Objective-C

我执行得对吗?我该怎么做才能修复或绕过此错误?

编辑:

Swift4 这个问题终于有了解决办法。查看我更新的答案。

您可以像这样在 Swift 中声明委托:

weak var delegate : UITableViewDelegate?

它甚至可以与混合(Objective-c 和 swift)项目一起使用。委托应该是可选的和弱的,因为不能保证它的可用性并且弱不会创建保留周期。

您收到该错误是因为 Objective-C 中没有泛型,并且不允许您添加 @IBOutlet 属性。

编辑:1. 强制委托类型

要强制该委托始终是 UIViewController,您可以实现自定义 setter 并在它不是 UIViewController 时抛出异常。

weak var _delegate : UITableViewDelegate? //stored property
var delegate : UITableViewDelegate? {
    set {
        if newValue! is UIViewController {
            _delegate = newValue
        } else {
            NSException(name: "Inavlid delegate type", reason: "Delegate must be a UIViewController", userInfo: nil).raise()
        }
    }
    get {
        return _delegate
    }
}

更新 Swift 4

Swift 4 添加了对将类型表示为符合协议的 class 的支持。语法是 Class & Protocol。以下是使用 "What's New in Swift"(来自 WWDC 2017 的会话 402)中的此概念的一些示例代码:

protocol Shakeable {
    func shake()
}
extension UIButton: Shakeable { /* ... */ }
extension UISlider: Shakeable { /* ... */ }

// Example function to generically shake some control elements
func shakeEm(controls: [UIControl & Shakeable]) {
    for control in controls where control.isEnabled {
        control.shake()
    }
}

从 Swift 3 开始,此方法会导致问题,因为您无法传入正确的类型。如果您尝试传入 [UIControl],它没有 shake 方法。如果您尝试传入 [UIButton],则代码会编译,但您无法传入任何 UISlider。如果你传入 [Shakeable],那么你无法检查 control.state,因为 Shakeable 没有那个。 Swift4终于说正题了

旧答案

我暂时使用以下代码解决了这个问题:

// This class is used to replace the UIViewController<UITableViewDelegate> 
// declaration in Objective-C
class ConformingClass: UIViewController, UITableViewDelegate {}

class AClass: UIViewController {
    @IBOutlet weak var anotherThing: UILabel!
    var thing: ConformingClass!
}

这对我来说似乎很老套。如果需要任何委托方法,那么我将不得不在 ConformingClass 中实现这些方法(我不想这样做)并在 subclass.

中覆盖它们

我已经发布了这个答案,以防其他人遇到这个问题,我的解决方案可以帮助他们,但我对这个解决方案不满意。如果有人发布更好的解决方案,我会采纳他们的回答。

这不是理想的解决方案,但您可以使用通用函数代替通用函数 class,如下所示:

class AClass: UIViewController {
  @IBOutlet weak var anotherThing: UILabel!
  private var thing: UIViewController?

  func setThing<T: UIViewController where T: UITableViewDelegate>(delegate: T) {
    thing = delegate
  }
}

我遇到了同样的问题,也尝试了通用方法。最终,通用方法破坏了整个设计。

重新思考这个问题后,我发现一个协议不能完全指定一个类型(换句话说,必须带有额外的类型信息,例如 class 类型)的可能性不大成为一个完整的人。此外,虽然声明 ClassType<ProtocolType> 的 Objc 风格很方便,但它忽略了协议提供的抽象的好处,因为这种协议并没有真正提高抽象级别。此外,如果此类声明出现在多个地方,则必须重复。更糟糕的是,如果这种类型的多个声明是相互关联的(可能会在它们周围传递一个对象),程序会变得脆弱且难以维护,因为以后如果需要更改一个地方的声明,则所有相关的声明都必须更改也被改变了。

解决方案

如果 属性 的用例既涉及协议(例如 ProtocolX)又涉及 class 的某些方面(例如 ClassX),则采用以下方法可以考虑:

  1. 声明一个附加协议,该协议继承自 ProtocolX,并添加了 ClassX 自动满足的 method/property 要求。像下面的例子,一个方法和一个属性是额外的要求,UIViewController自动满足。

    protocol CustomTableViewDelegate: UITableViewDelegate {
        var navigationController: UINavigationController? { get }
        func performSegueWithIdentifier(identifier: String, sender: AnyObject?)
    }
    
  2. 声明一个从 ProtocolX 继承的附加协议,带有 ClassX 类型的附加只读 属性。这种方法不仅允许完整地使用 ClassX,而且还展示了不需要实现 subclass ClassX 的灵​​活性。例如:

    protocol CustomTableViewDelegate: UITableViewDelegate {
        var viewController: UIViewController { get }
    }
    
    // Implementation A
    class CustomViewController: UIViewController, UITableViewDelegate {
    
        var viewController: UIViewController { return self }
    
        ... // Other important implementation
    }
    
    // Implementation B
    class CustomClass: UITableViewDelegate {
    
        private var _aViewControllerRef: UIViewController // Could come from anywhere e.g. initializer
        var viewController: UIViewController { return _aViewControllerRef } 
    
        ... // UITableViewDelegate methods implementation
    }
    

PS. 以上代码片段仅供演示,不建议将 UIViewControllerUITableViewDelegate 混合使用。

编辑 Swift 2+: 感谢@Shaps 的评论,可以添加以下内容以节省必须在所有地方实现所需的 属性。

extension CustomTableViewDelegate where Self: UIViewController { 
    var viewController: UIViewController { return self } 
}