[Swift] あると便利だったextension達 型名編
はじめに
モバイルアプリサービス部の中安です。
アプリを作ってきた際に、あるとなかなか便利だったextensionをボチボチとご紹介していければと思っています。 アプリの目的に合わせてやりかえる必要があったり、もっとよい実装があるかもしれませんが、何かの役に立てば光栄です。
今回は型名編です。
ある値の型が何であるかを把握するのは当たり前ですが大事です。
しかし、デバッグなどをしていると変数の中身が何かがわかりづらいときもあります。
デバッガを使えばいいという話なのですが、さらっとログに出したいときなどもあり、難なく型名を取得する機能を付け加えてみたら思いのほかに便利でした。
型名を取得する
今回はまず渡した値の型名を返してくれるクラスを定義するところから始めます。クラス名は Reflection
としました。
class Reflection { class func typeName(of target: Any?) -> String { guard let target = target else { return "nil" } return String(describing: type(of: target)) } }
type(of:)
は非オプショナルのみしか受け付けるないので、オプショナル対策をラッピングして増強してあげています。
これで引数を Any?
にできるので何を渡しても型名を取得することができます。
使い方例
Reflection.typeName(of: 1) // Int Reflection.typeName(of: "A") // String Reflection.typeName(of: nil) // nil
実験
実際に型名を取得するとどのように返ってくるのでしょうか。 いろいろなパターンから試してみたいと思います。
print(Reflection.typeName(of: 1)) // Int print(Reflection.typeName(of: "1")) // String print(Reflection.typeName(of: 0.1)) // Double print(Reflection.typeName(of: CGFloat(0.1))) // CGFloat
リテラルな値はこのように返ってきます。
print(Reflection.typeName(of: [1, 2, 3])) // Array<Int> print(Reflection.typeName(of: ["A": 1, "B": 2, "C": 3])) // Dictionary<String, Int> print(Reflection.typeName(of: (A: 1, B: 2))) // (A : Int, B : Int) print(Reflection.typeName(of: "Hello".range(of: "l"))) // Range<Index> print(Reflection.typeName(of: [0..<1])) // Array<CountableRange<Int>>
配列、辞書、タプル、レンジはこのように返ってきます。
print(Reflection.typeName(of: UITableViewController())) // UITableViewController print(Reflection.typeName(of: UITableViewController.self)) // UITableViewController.Type
クラスとそのオブジェクトはこのように返ってきます。しかし、注意点として下記のようなパターンが存在します。
print(Reflection.typeName(of: NSDate(timeIntervalSince1970: 200))) // __NSTaggedDate print(Reflection.typeName(of: NSData())) // _NSZeroData print(Reflection.typeName(of: UIColor.red.withAlphaComponent(0.2))) // UIDeviceRGBColor
旧来の objective-c から汎用的なクラスに多く見受けられますが、その型で初期化をしても実は返ってきている型は別の継承クラスであったりします。 これらは「クラスクラスタ」と呼ばれるものです。
クラスクラスタについての詳細はここでは割愛しますが、本実装時に型名比較でなにかをするというのは危ういものになってしまいます。 あくまで型名取得はデバッグ時などの限定的な使い方をしたほうがベターではあります。
print(Reflection.typeName(of: { return 3 })) // () -> Int
クロージャの場合はこのように返ってきます。
print(Reflection.typeName(of: UITableViewStyle.grouped)) // UITableViewStyle
列挙子の場合はケース名ではなく列挙子名になるようですね。
print(Reflection.typeName(of: CGSize(width: 40, height: 40))) // CGSize
構造体もクラスと同じように返ります。
class A { class a { } } print(Reflection.typeName(of: A.a())) // a print(Reflection.typeName(of: A.a.self)) // a.Type
クラスの中に定義されたクラスの場合は、そのクラス単体だけで返ってくるようです。
プロトコル化
ここまで書いたように Reflection.typeName(of:)
ですべて賄ってもいいのですが、オブジェクトのプロパティとして型名が取得できれば更に楽になります。
protocol Reflectable {} extension Reflectable { var typeName: String { return Reflection.typeName(of: self) } }
こいつを例えば NSObject
に準拠させてやります。
extension NSObject: Reflectable { }
すると
NSURLRequest(url: URL(string: "http://classmethod.jp")!).typeName // NSURLRequest
多くのオブジェクトに対して typeName
を使用することができます。
もちろんカスタムなクラスなどについても下記のようにしてやれば、変数の中身の型名がシンプルに取得できるようになります。
class Job: Reflectable {} class PoliceMan: Job {} class FireMan: Job {} let job: Job = FireMan() print(job.typeName) // FireMan
ここでは変数job
の型はJob
で宣言されていますが、実際には継承クラスのFireMan
オブジェクトが代入されています。
そんなときであってもtypeName
は"FireMan"
を返してくれます。
何が入ってるかわからないときにはこれで調べることが出来ます。
今回は以上です。