【问题标题】:Swift Property that conforms to a Protocol and Class符合协议和类的 Swift 属性
【发布时间】:2015-05-28 01:38:22
【问题描述】:
@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

我执行的对吗?我可以做些什么来修复或绕过这个错误?

编辑:

Swift 4 终于有了解决这个问题的办法。请参阅我的更新答案。

【问题讨论】:

  • 几个月前我打算这样做,当时的共识是这是不可能的。如果那是不正确的,我会喜欢它。

标签: ios swift generics properties protocols


【解决方案1】:

Swift 4 更新

Swift 4 添加了对将类型表示为符合协议的类的支持。语法为Class &amp; Protocol。以下是使用“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],那么代码会编译,但你不能传入任何UISliders。如果你传入[Shakeable],那么你不能检查control.state,因为Shakeable没有那个。 Swift 4 终于解决了这个话题。

旧答案

我暂时用下面的代码解决了这个问题:

// 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 中实现这些方法(我不想这样做)并在子类中覆盖它们。

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

【讨论】:

  • 我一定是做错了什么; [Myclass &amp; Myprotocol] 中的类的子类被粗暴地拒绝。
  • 我用它作为属性的类型。我不确定,也许语法不正确。现在在 6:44 左右观看 WWDC 视频,属性的语法是 var client: (Someclass &amp; Protocol)
  • 我在使用 [Myclass &amp; Myprotocol] 时遇到类型错误,但 (Myclass &amp; Myprotocol) 对我有用
【解决方案2】:

这不是理想的解决方案,但您可以使用泛型函数而不是泛型类,如下所示:

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

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

【讨论】:

  • 我想要的最大的事情是能够从属性调用类或委托方法之一。例如,如果thing 是实现UITableViewDelegateUIViewController,我希望能够访问thing.navigationControllerthing.didSelectRowAtIndexPath(),而不必在拨打电话时强制转换thing。这有意义吗?
  • 所以这可以用于函数,但不能用于属性?烦人。
【解决方案3】:

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

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

解决方案

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

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

    protocol CustomTableViewDelegate: UITableViewDelegate {
        var navigationController: UINavigationController? { get }
        func performSegueWithIdentifier(identifier: String, sender: AnyObject?)
    }
    
  2. 声明一个从ProtocolX 继承的附加协议,并带有一个ClassX 类型的附加只读属性。这种方法不仅允许完整地使用ClassX,而且还展示了不需要实现子类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.上面的sn-p仅供演示,不建议将UIViewControllerUITableViewDelegate混用。

为 Swift 2+ 编辑:感谢@Shaps 的评论,可以添加以下内容以省去在任何地方实现所需属性的麻烦。

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

【讨论】:

  • 这是迄今为止我看到的关于该主题的最佳答案。我认为您对失去抽象的好处的想法很好。我越想越想用 Swift 语法编写 Objective-C 代码,这才是问题的真正根源。感谢您的深入回答!
  • 虽然我原则上 100% 同意您的 cmets,但当我们使用我们无法控制的 Cocoa 类时,它并没有帮助。如果一个 UIKit 方法需要一个类型为 UIViewController 的参数,而我是一个优秀的 POP 程序员并且需要一个协议来添加我的功能,那么就没有办法解决它。我需要一个满足协议和超类要求的对象。
  • 感谢您发布此信息。非常适合我的问题。我添加的一个补充是extension CustomTableViewDelegate where Self: UIViewController { var viewController: UIViewController { return self } } 节省了必须在任何地方实现 var :)
  • 感谢@Shaps 的加入。这个答案发布在 swift 1.x 时代。我会将您的信息添加到其中。
  • 关于“Edit for Swift 2+”,是否应该将属性 viewController 声明为weak以避免保留循环?
【解决方案4】:

你可以像这样在 Swift 中声明一个委托:

weak var delegate : UITableViewDelegate?

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

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

编辑:1. 强制委托类型

要强制该委托始终是 UIViewController,您可以实现自定义设置器并在它不是 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
    }
}

【讨论】:

  • 但如果我只将其声明为UITableViewDelegate,那么我就失去了成为UIViewController 的另一个要求。我想声明该属性是符合UITableViewDelegateUIViewController
  • 您可以通过为委托定义自定义设置方法来做到这一点。当委托不是视图控制器时,您可以抛出异常[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];
  • 我会错过使用UIViewController 进行的方法调用。如果它只是UITableViewDelegate 类型,那么我不能调用self.delegate.dismissViewControllerAnimated(),即使它是UIViewController
  • 添加了强制类型检查的代码。如果它解决了您的问题,请接受并投票赞成这个答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多