48670


swift初心者です。

[code]

    class Animal {
    
        // 動物名を返すメソッド
        class func returnAnimalName() -> String {
            fatalError(message: "このメソッドはオーバーライドされていません")
        }
    
        requied init() {
        }
    
    }
    
    class Dog: Animal {
        override class func returnAnimalName() -> String {
            return "Dog"
        }
    }
    
    class Cat: Animal {
        override class func returnAnimalName() -> String {
            return "Cat"
        }
    }
    
[/code]

このようなコードで動物名の文字列("Dog"や"Cat"等)を使ってその動物名が指すAnimalのサブクラスのメタタイプを得たいです。

[code]

    let animalDict = ["Dog": Dog.self, "Cat": Cat.self]
    
    let dogType = animalDict["Dog"]
    let dog = dogType.init()
    
    let catType = animalDict["Cat"]
    let cat = catType.init()
    
[/code]

このように動物名を入れるとそれが指すメタタイプを返すDictionaryを作るという方法もあるのですが、このようにするとAnimalのサブクラスを追加した時にこのDictionaryにも追加する必要があるゆえ、追加し忘れた時のことを考えるとバグの元です。他に何かいい方法はないでしょうか。  
swift4、xcode9.2です。


CC BY-SA 4.0
2018-09-24T02:52:21.303
48666
2018-09-24T04:21:57.563
30216
question
swift ios swift4
swift あるクラスのサブクラスの一覧を出す方法について


"swift get all subclasses of a
class"なんてキーワードで検索したら、本家StackOverflowの記事に同趣旨のものがありました。

[List all subclasses of one
class](https://stackoverflow.com/q/42746981/6541007)

**現在のSwiftの実装では** (*)一部の[Objective-
C用実行時関数](https://developer.apple.com/documentation/objectivec/objective-
c_runtime)が動作すると言う事実に基づいています。

ちなみに...

  * あるクラスから、そのサブクラスの一覧を取るような関数はObjective-Cにも無いので、実行時のアプリに登録されているクラス(iOSのクラスも含む)を全部リストアップして、指定のクラスがスーパークラスになっているかどうかを調べています。リストには対象のクラス以外も膨大な数が含まれるので、壮絶(と言うほどでもないが…)に無駄な時間を食います。度々呼び出されるような場所やクリティカルな実行速度が要求される場所で使うのはやめた方が良いでしょう。

  * 直接のスーパークラスしか調べていません。よって、指定クラスの直接の子クラスだけが取得できます。孫クラスや子孫クラスも取れるようにするには、ちょっと工夫する必要があるでしょうが、ここでは置いておきます。

と言うわけで、上記リンク先の承認された記事では「こんな大層なもん別に要らんやん」て感じのユーティリティクラス(`ClassInfo`)がかなりの行数を取っていたのと、メモリ管理的に怪しそうだったので、エッセンスの部分だけ拝借して、メモリ管理面を改良した関数を作ってみました。

[code]

    func getAllSubclasses<T>(of theClass: T.Type) -> [T.Type] {
        var subclasses: [T.Type] = []
        autoreleasepool {
            var classCount: UInt32 = 0
            if let classes = objc_copyClassList(&classCount) {
                for i in 0..<Int(classCount) {
                    let aClass: AnyClass = classes[i]
                    if
                        let superclass = class_getSuperclass(aClass),
                        ObjectIdentifier(superclass) == ObjectIdentifier(theClass)
                    {
                        subclasses.append(aClass as! T.Type)
                        print(aClass)   //デバッグ用
                    }
                }
                free(UnsafeMutableRawPointer(classes))
            } else {
                print("objc_copyClassList retruned nil")
            }
        }
        return subclasses
    }
    
[/code]

現在Xcode 9.2は手元にないので、Xcode 9.4.1と10でしか試していませんが、Xcode 9.2でも動くはずです。(多分…。)

以下のような感じで使えます。

[code]

    let animalSubclasses = getAllSubclasses(of: Animal.self)
    let animalDict = Dictionary(uniqueKeysWithValues: animalSubclasses.map{(String(describing: $0), $0)})
    
    if let dogType = animalDict["Dog"] {
        print(dogType.returnAnimalName())
        let dog = dogType.init()
        print(dog)
    } else {
        print("NO type named Dog in subtypes of Animal")
    }
    
    if let catType = animalDict["Cat"] {
        print(catType.returnAnimalName())
        let cat = catType.init()
        print(cat)
    } else {
        print("NO type named Cat in subtypes of Animal")
    }
    
[/code]

* * *

(*)SwiftのクラスにどこまでObjective-Cの実行時関数が正常に働くかは明記されていません。今後「ABIの安定性」が宣言される予定のSwift
5までに大きな変更はないとは思いますが、なんせ「Objective-
C用の実行時間数」なんで、`objc_copyClassList()`なんかがいつまで期待通り動くかは未知数です。

もし使われるなら、「追加し忘れた時のことを考えるとバグの元」と「いつまで期待通り動くかは未知数」と言うのを天秤にかける必要があるでしょう。


CC BY-SA 4.0
2018-09-24T04:21:57.563
48670
2018-09-24T04:21:57.563
13972
48666
answer