1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Swiftでdelegateの実装を禁止したい

Last updated at Posted at 2019-07-18

TL;DR

  • 基本的に禁止はできない
  • delegateのwillSetを拾って実行エラーを出せば開発者に伝えることができるかも

自身のDelegateの実装を守りたい

例えば特定のUIViewを継承し、オリジナルのUIViewを作ります。
UIViewの中にはdelegateを持つものがあります。今回はUITextViewを継承し、入力が終わった時に独自の処理を加えるViewを作りたいとします。そのView自身が自身のdelegateを実装する処理を加えてみます。

class ExtendedTextView: UITextView, UITextViewDelegate {
    override func layoutSubviews() {
        super.layoutSubviews()
        // もっといい場所があるかもしれない
        delegate = self
    }

    func textViewDidEndEditing(_ textView: UITextView) {
        print("オリジナル処理")
    }
}

これでExtendedTextViewの中でテキストの変更イベントに対して処理ができるようになります。
しかしこのExtendedTextViewに対して呼び出し側がdelegateを実装してしまった場合はどうなるのでしょうか?

@IBOutlet weak var textView: ExtendedTextView!

override func viewDidLoad() {
    super.viewDidLoad()
    textView.delegate = self
}

func textViewDidEndEditing(_ textView: UITextView) {
    print("いたって普通の処理")
}

この場合せっかくExtendedTextViewの中で実装したdelegateは呼ばれず、代わりに「いたって普通の処理」が出力されるようになってしまいます。
これを防ぎたい時はどうすれば良いでしょうか。

delegateの実装そのものを禁止することはできません。
スーパークラスでできることはサブクラスでもできるから継承なのです。
完全に防ぐにはUITextViewを継承せずに自力でUITextViewのようなものを作る必要があります。

それはかなりつらぽよなので、
今回はdelegateの実装をしようとした時にクラッシュを起こすことによって開発中に間違った使い方を示すというアプローチにしました。

class ExtendedUITextView : UITextView , UITextViewDelegate {
    override func layoutSubviews() {
        super.layoutSubviews()
        delegate = self
    }

    override var delegate: UITextViewDelegate? {
        willSet {
            try! check(willSetDelegate: newValue)
        }
    }

    private func check(willSetDelegate: UITextViewDelegate?) throws {
        if willSetDelegate != nil && !(willSetDelegate is ExtendedUITextView) {
            throw NSError(domain: "てきとーな委譲してんじゃねー!", code: 0, userInfo: nil)
        }
    }
    
    func textViewDidEndEditing(_ textView: UITextView) {
        print("大事な処理")
    }
}

ポイントはdelegateのwillSetでcheck関数を実行していることです。check関数の中では渡されたdelegateが自身のクラスかどうかを見ています。ExtendedUITextView以外の実装の場合はNSErrorをthrowすることによって実行時にアプリがクラッシュする仕組みになっています。

こうすることで他のクラスがdelegate実装するのを防ぐことができます。
ExtendedUITextViewで前処理した後に別のクラスで委譲したい場合は自前の拡張Delegateを新しく作ればOKです。

protocol ExtendedUITextViewDelegate: class {
    func textViewDidEndEditing(_ textView: ExtendedUITextView)
}

class ExtendedUITextView: UITextView, UITextViewDelegate {
    weak var extendedDelegate: ExtendedUITextViewDelegate?

    func textViewDidEndEditing(_ textView: UITextView) {
        print("前処理かーらーのー?")
        extendedDelegate?.textViewDidEndEditing(self)
    }
}
1
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?