iOS / Programming / Swift

プロトコルとか準拠ってなに!?!? – iOS Swift

class AppDelegate : UIResponder, UIApplicationDelegate {
  // 略
}

このクラスから試しに「UIResponder」を消してみたら「Cannot declare conformance to ‘NSObjectProtocol’ in Swift; ‘AppDelegate’ should inherit ‘NSObject’ instead」エラーが出ました。

直訳したら、AppDelegateは「NSObjectProtocol」に準拠してないから「NSObject」を継承して!ということらしいです。
なにが、どうなって、そうなった??怒ってる?

プロトコルについてを先に書いていますが、ご存知でしたら「まとめ」まで飛んでください。

そもそもProtocol(プロトコル)とは??

protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol.

公式より

簡単に意訳すると「プロトコルは特定の関数・プロパティなどの骨組みを定義したもの。class・structure・enumがプロトコルを使用でき、骨組みに沿っている状態を『準拠している』と呼ぶ。」だそうです。

ブループリント – 骨組み??

ここで言うブループリントとは、骨組みだけで中身がなにもない困ったやつです。ただ便利な時もあります。

protocol DaikuHoneGumi {
    func GenkanTsukuru()
}

このDaikuHoneGumiプロトコルはGenkanTsukuru関数を内部で定義していますが、関数の中身がありません。classなどでこの書き方をするとエラーになりますが、プロトコルでは問題ありません。

これがなんの役に立つのかというと、このプロトコルに準拠しているclassなどにGenkanTsukuru関数の中身の定義を強制できます。

準拠の利点 – 強制力!!


class JapaneseDaiku : DaikuHoneGumi {
    func GenkanTsukuru() {
        print("玄関をつくります")
    }
}

class AmericanDaiku : DaikuHoneGumi {
    func GenkanTsukuru() {
        print("I build an entrance")
    }
}

class BadDaiku : DaikuHoneGumi { // エラー!!!!
    
}

protocol DaikuHoneGumi {
    func GenkanTsukuru()
}

クラスをDaikuHoneGumiに準拠させることにより、GenkanTsukuru関数の実装を強制させることができ、準拠する姿勢を見せているのに関数を実装していないBadDaikuはエラーを指摘されます。

準拠の利点 – 引数の型に指定できる!!


daikuYobidashi(daiku: JapaneseDaiku())

func daikuYobidashi(daiku: DaikuHoneGumi) {
    daiku.GenkanTsukuru()
}

daikuYobidashi関数を新たに作成し、引数で受け取れる型をDaikuHoneGumiにしました。
これにより関数内のdaikuはDaikuHoneGumi型として扱われ、DaikuHoneGumi型であれば確実にGenkanTsukuru関数を持っていることになります。よってGenkanTsukuru関数の呼び出しが可能になります。

ポリモーフィズム(多様性)と呼ばれますが、難しい言葉なので来世で覚えることにします。
上記のコードはこちらにあります。

本題に戻ります。

NSObjectProtocolに準拠すれば良い

忘れてましたが、「Cannot declare conformance to ‘NSObjectProtocol’ in Swift; ‘AppDelegate’ should inherit ‘NSObject’ instead」エラーを解消したいんでした。

このエラーが表示される理由を探るため、UIApplicationDelegateを見ました。

@MainActor public protocol UIApplicationDelegate : NSObjectProtocol {
  // 略
}

こいつがプロトコルであること、裏でNSObjectProtocolに準拠していることがわかりました。

プロトコルがプロトコルに準拠するというのは骨組みが複雑になっているのと同じ状況です。

class ClassAB : ProtocolA {
  func protocolFuncA() {
    // 何かしらの実装
  }

  func protocolFuncB() {
    // 何かしらの実装
  }
}

protocol ProtocolA : ProtocolB {
  func protocolFuncA()
}

protocol ProtocolB {
  func protocolFuncB()
}

protocolAはprotocolBに準拠していますが実際に中身を指定するのはClassABです。
これと同じ状況がUIApplicationDelegateにも起こっているため、AppDelegateはUIApplicationDelegateとNSObjectProtocolに準拠する必要があります。

そのためには直で準拠するか、すでに準拠しているクラスを継承するかの方法があります。

直で準拠するパターン

class AppDelegate : NSObjectProtocol, UIApplicationDelegate {
    func conforms(to aProtocol: Protocol) -> Bool {
        
    }
    
    func isEqual(_ object: Any?) -> Bool {
        
    }
    
    var hash: Int = 0
    
    var superclass: AnyClass?
    
    func `self`() -> Self {
        
    }
    
    func perform(_ aSelector: Selector!) -> Unmanaged<AnyObject>! {
        
    }
    
    func perform(_ aSelector: Selector!, with object: Any!) -> Unmanaged<AnyObject>! {
        
    }
    
    func perform(_ aSelector: Selector!, with object1: Any!, with object2: Any!) -> Unmanaged<AnyObject>! {
        
    }
    
    func isProxy() -> Bool {
        
    }
    
    func isKind(of aClass: AnyClass) -> Bool {
        
    }
    
    func isMember(of aClass: AnyClass) -> Bool {
        
    }
    
    func responds(to aSelector: Selector!) -> Bool {
        
    }
    
    var description: String = ""
}

とんでもなく実装する内容が多く、それぞれの関数がどのように使われ、どのタイミングでどの値を返すべきかを確かめる必要があります。
お勧めしません。というかNSObjectProtocolに関してはこの方法はやめて。

準拠しているクラスを継承するパターン

NSObjectProtocolに関してはこの方法を使用することをお勧めします!

open class NSObject : NSObjectProtocol {
  // 略
}

NSObjectクラスはNSObjectProtocolに準拠しているクラスかつopenキーワードがついています。
そのためNSObjectをAppDelegateで継承すればOKです。

※openについてはこちらの方を参考にしてください!
もしくはコメントで。

class AppDelegate : NSObject, UIApplicationDelegate {
  // 略
}

これでも良いですが、Swiftの慣習でAppDelegateではNSObjectの代わりにUIResponderを継承することが一般的です。

class AppDelegate : UIResponder, UIApplicationDelegate {
  // 略
}

UIResponderは内部でNSObjectを継承しているため、これを継承しているAppDelegateはNSObjectProtocolに準拠していることになります。

@MainActor open class UIResponder : NSObject, UIResponderStandardEditActions {
  // 略
}

まとめ

  • プロトコルに準拠しよう
  • AppDelegateではNSObjectではなくUIResponderに準拠しよう
class AppDelegate : UIResponder, UIApplicationDelegate {
  // 略
}

プロトコルって名前、かっこいい。ミドルネームにしたい。佐藤・プロトコル・太郎って感じ
佐藤・プロトコル・太郎に準拠する。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です