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