web-dev-qa-db-ja.com

Swiftプロトコルおよびクラスに準拠するプロパティ

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

このObjective-CコードのようなプロパティをSwiftに実装したいと思います。だからここに私が試したものがあります:

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には、この問題に対する解決策がついにあります。私の更新された答えを見てください。

22
keithbhunter

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]を渡そうとすると、コードはコンパイルされますが、UISlidersを渡すことができません。[Shakeable]を渡すと、 Shakeableにはcontrol.stateがないため、チェックしないでください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に実装し(私はしたくない)、サブクラスでそれらをオーバーライドする必要があります。

他の誰かがこの問題に遭遇し、私の解決策が彼らを助ける場合に備えて、私はこの回答を投稿しましたが、解決策に満足していません。誰かがより良い解決策を投稿したら、私は彼らの答えを受け入れます。

18
keithbhunter

これは理想的なソリューションではありませんが、次のように、ジェネリッククラスの代わりにジェネリック関数を使用できます。

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

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

私は同じ問題に遭遇し、一般的なアプローチも試しました。結局、一般的なアプローチは設計全体を壊しました。

この問題について再考した結果、タイプを完全に指定するために使用できないプロトコル(つまり、クラスタイプなどの追加のタイプ情報が必要になるプロトコル)は完全なプロトコルではない可能性があることがわかりました。さらに、宣言するObjcスタイルがClassType<ProtocolType>は便利です。そのようなプロトコルは実際には抽象化レベルを上げないため、プロトコルによって提供される抽象化の利点を無視します。さらに、そのような宣言が複数の場所に現れる場合は、それを複製する必要があります。さらに悪いことに、そのようなタイプの複数の宣言が相互に関連している場合(おそらく単一のオブジェクトがそれらの周りに渡される)、プログラムは壊れやすくなり、後で1か所の宣言を変更する必要がある場合、関連するすべての宣言が同様に変更されます。

ソリューション

プロパティの使用例にプロトコル(たとえば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。上記のスニペットはデモンストレーションのみを目的としており、UIViewControllerUITableViewDelegateを混在させることはお勧めしません。

Swift 2 +:の編集@Shapsのコメントに感謝します。次のコードを追加すると、必要なプロパティをどこにでも実装する必要がなくなります。

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

次のように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
    }
}
0
kmithi