符合协议和类的Swift属性

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

我希望在Swift中实现类似于这个Objective-C代码中的属性。这是我尝试过的代码:

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

这个可以编译。当我从storyboard添加属性时出现问题。 @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终于有了解决这个问题的方法。请查看我的更新答案。


我几个月前曾试图实现这个,当时大家的共识是不可能的。但如果那个结论是错误的,我会非常高兴。 - AdamPro13
4个回答

19

Swift 4更新

Swift 4新增了支持将类型表示为符合协议的类。语法为Class & 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],那么代码将编译通过,但你不能传递任何UISlider。如果你传递[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!
}

这对我来说似乎有些hackish。如果任何委托方法是必需的,那么我将不得不在ConformingClass中实现这些方法(我不想这样做),并在子类中覆盖它们。

我发表了这篇答案,以防其他人遇到此问题并且我的解决方案有所帮助,但我对解决方案并不满意。如果有人发布更好的解决方案,我将接受他们的答案。


我一定做错了什么; [Myclass&Myprotocol]类的子类被残酷地拒绝了。 - Jonny
2
我将其用作属性的类型。我不确定,也许语法不正确。现在查看WWDC视频,大约在6:44处,属性的语法是var client: (Someclass&Protocol) - Jonny
我在使用[Myclass&Myprotocol]时出现了类型错误,但是(Myclass&Myprotocol)适用于我。 - auspicious99

7

这不是最理想的解决方案,但您可以使用通用函数而不是通用类,例如:

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(),而不必在调用时进行强制类型转换。这样说清楚了吗? - keithbhunter
所以这可以用于函数,但不能用于属性?很烦人。 - pkamb

5
我遇到了同样的问题,并尝试了通用方法。最终,通用方法破坏了整个设计。
在重新思考这个问题后,我发现一个仅包含协议(换句话说,必须附带额外的类型信息,如类类型)的协议不太可能是完整的。此外,虽然 Objc 风格的声明 ClassType<ProtocolType> 很方便,但它忽略了协议提供的抽象优势,因为这种协议并没有真正提高抽象级别。此外,如果多个地方出现这样的声明,它必须被重复。更糟糕的是,如果多个这种类型的声明相互关联(可能会传递单个对象),程序会变得脆弱且难以维护,因为稍后如果一个地方的声明需要更改,则所有相关的声明也必须更改。
解决方案
如果属性的使用情况涉及协议(例如 ProtocolX)和类的某些方面(例如 ClassX),可以考虑采用以下方法:
  1. Declare an additional protocol that inherits from ProtocolX with the added method/property requirements which ClassX automatically satisfy. Like the example below, a method and a property are the additional requirements, both of which UIViewController automatically satisfy.

    protocol CustomTableViewDelegate: UITableViewDelegate {
        var navigationController: UINavigationController? { get }
        func performSegueWithIdentifier(identifier: String, sender: AnyObject?)
    }
    
  2. Declare an additional protocol that inherits from ProtocolX with an additional read-only property of the type ClassX. Not only does this approach allow the use of ClassX in its entirety, but also exhibits the flexibility of not requiring an implementation to subclass ClassX. For example:

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

这是我在这个主题上看到的迄今为止最好的答案。我认为你关于失去抽象化优势的想法表述得很好。越想,我尝试用Swift语法编写Objective-C代码,这才是问题的真正根源。感谢你深入的回答! - keithbhunter
1
虽然我完全同意你的原则性评论,但当我们使用我们无法控制的Cocoa类时,这并没有帮助。如果UIKit方法要求参数为UIViewController类型,并且我是一个良好的POP程序员并需要协议来添加功能,则无论如何都无法避免。我需要一个满足协议和超类要求的对象。 - Sebastien Martin
1
谢谢您发布这篇文章。对我的问题非常有用。我添加的一个补充是:extension CustomTableViewDelegate where Self: UIViewController { var viewController: UIViewController { return self } }这样可以避免在每个地方都实现变量 :) - Shaps
1
感谢@Shaps的补充。这个答案是在Swift 1.x时代发布的。我会将您的信息添加到其中。 - Fujia
关于“适用于Swift 2+的编辑”,是否应该将属性viewController声明为weak以避免保留循环? - valfer

0

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

weak var delegate : UITableViewDelegate?

它可以与混合(Objective-C和Swift)项目一起使用。Delegate应该是可选的和弱的,因为其可用性不能保证,而弱不会创建保留循环。

您之所以会遇到该错误,是因为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
    }
}

但是如果我只将其声明为 UITableViewDelegate,那么我就失去了作为 UIViewController 的其他要求。我想声明该属性是一个符合 UITableViewDelegateUIViewController - keithbhunter
你可以通过为委托定义自定义setter方法来实现。当委托不是视图控制器时,你可以抛出异常[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo]; - kmithi1
我会错过使用UIViewController调用的方法。如果它只是UITableViewDelegate类型,那么我不能调用self.delegate.dismissViewControllerAnimated(),即使它实际上是一个UIViewController - keithbhunter
添加了代码以强制进行类型检查。如果它解决了您的问题,请接受并点赞此答案。 - kmithi1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接