iOS
reflection
Swift

Swift reflectionについて

More than 1 year has passed since last update.

Swiftでリフレクションで少し遊んだら楽しかったので、

メモがき程度にまとめたいと思います。

リフレクションにはMirror構造体を使います。


リフレクション(Reflection)とは?

そもそもリフレクションってなに?っていう状態でした。

そこでカスである私はググりました。

下記リンクでなんとなく理解できたので概要の部分を引用させていただきます。


リフレクションは,プログラムの中でそのプログラムに含まれる型や変数/メソッドの情報を参照/操作できるようにする仕組みです。

-- 引用元: http://itpro.nikkeibp.co.jp/article/Keyword/20070207/261164/

リフレクション (reflection) とは,プログラムの実行過程でプログラム自身の構造を読み取ったり書き換えたりする技術のことである。

-- 引用元: http://promamo.com/?p=3079


実際にプログラムを書いて実行してみましょう。


enum MyEnum {
case One
case Two
case Three
}

class MyClass {

}

struct MyStruct {

}

class Object {
var string: String?
var number: Int = 0
var b: Bool {
return false
}
private let dic: [String: AnyObject] = ["key": "value", "a": "b"]
var arr: [Int] = [1,2,3,4,5]
var any: AnyObject?
var e: MyEnum = .One
var c: MyClass = MyClass()
private(set) var s: MyStruct = MyStruct()
let tuple: (label: String, value: Int) = (label: "AGE", value: 23)
var closure: Void -> Void = {}
lazy var lString: String = {
return "lazy"
}()
lazy var lNumber: Int = 1
func method() {
print("hogehoge")
}
class func method() -> String {
return "fugafuga"
}
}

Object型のClassを定義しました。

適当にアクセスコントロールを変えたりしています。

Objectクラスの情報を見ていきたいと思います。


let object = Object()
let mirror = Mirror(reflecting: object)

Mirror構造体にリフレクションに用いるプログラムを渡します。

この場合Object型の変数objectを引数に渡します。

そして、Mirrorで定義されているプロパティから情報を取得してprintで表示します。


print("mirror: \(mirror)")
print("children: \(mirror.children)")
print("subjectType: \(mirror.subjectType)")
print("displayStyle: \(mirror.displayStyle)")
print("description: \(mirror.description)")
print("")
mirror.children.map { print("\($0.label!) => \($0.value)") }

以下が出力結果です。


mirror: Mirror for Object
children: AnyForwardCollection<(Optional<String>, protocol<>)>(_box: Swift._CollectionBox<Swift.Mirror.LegacyChildren>)
subjectType: Object
displayStyle: Optional(Class)
description: Mirror for Object

string => nil
number => 0
dic => ["key": value, "a": b]
arr => [1, 2, 3, 4, 5]
any => nil
e => One
c => MyClass
s => MyStruct()
tuple => ("AGE", 23)
closure => (Function)
lString.storage => nil
lNumber.storage => nil

mirror、description、childrenでは、型・値について表示されています。

subjectTypeでobjectのクラス名"Object"がOptionalに包まれて取得できます。

displayStyleは渡されたインスタンスがClass型ということを表しています。

mirror.children.mapではプロパティの各要素が取得できます。

labelでプロパティ名、valueでプロパティの値がとれるみたいです。

labelはOptional型でそのままだとprintが見にくかったので、

forced unwrappingしてます。

関数名とコンピューテッドプロパティはprintされてないみたいです。

lazyがつくと値がnilで取れるみたいです。

closureのvalueが(Function)となっているのが少し意外です。

(() -> ()) 左みたいな感じで出力されると思っていました。

ここまででMirrorにインスタンスを渡たすことにより、

各プロパティ、クラスの情報が取得できることがわかりました。

これだけでちょっとハックなことをした気分になりワクワクしました。w

ですが、

- 関数とコンピューテッドプロパティの情報の取得

- 参照だけでなく操作


を実現する術が見つかりませんでした。

もしかしたら調査不足だったり、

そもそもSwiftでは提供していないかもしれません。

で、上記のことが分かった上で

実際にどういう風に使えるか、を考えました。


その1 各Enum・Struct・Class名の取得

下記のようなProtocolを一つ用意します。


protocol MyInstanceType {
static var myName: String { get }
var myName: String { get }
}

extension MyInstanceType {
static var myName: String {
return "\(Mirror(reflecting: self).subjectType)".componentsSeparatedByString(".")[0]
}
var myName: String {
return "\(Mirror(reflecting: self).subjectType)"
}
}

このプロトコルが定義されていれば、

Enum・Struct・Classでそれぞれの名前がmyNameプロパティで取得できます。

extension MyEnum: MyInstanceType {

}

extension MyClass: MyInstanceType {

}

extension MyStruct: MyInstanceType {

}

print(MyClass().myName)
print(MyClass.myName)
print(MyStruct.myName)
print(MyStruct().myName)
print(MyEnum.myName)
print(MyEnum.One.myName)

/* 出力
MyClass
MyClass
MyStruct
MyStruct
MyEnum
MyEnum
*/

文字列で何かをしているする場合、

必要に応じて使い所があるかもしれません。


その2 displayStyleから条件分岐

MyInstanceTypeプロトコルをもう一度拡張します。

共通のプロトコルでdisplayStyleから条件分岐することが出来ます。


extension MyInstanceType {
var myDisplayStyle: String {
return "\(Mirror(reflecting: self).displayStyle!)"
}

func call() {
switch self.myDisplayStyle {
case "Class":
print("クラス")
case "Struct":
print("構造体")
case "Enum":
print("列挙型")
default:
print("unknown")
}
}
}

print(MyClass().myDisplayStyle)
print(MyStruct().myDisplayStyle)
print(MyEnum.One.myDisplayStyle)

print("")

MyClass().call()
MyStruct().call()
MyEnum.One.call()

/* 出力
Class
Struct
Enum

クラス
構造体
列挙型
*/

Enumで管理すると分かりやすいかもです。

正直使い所は思いつきません。


まとめ

普段使用しない機能で発見も多くて楽しかったです。

ただ、リフレクションを多様するのは少々危険かもしれません。

現在は参照のみで操作の方法がわかっていませんが、

例えばクラス自体の情報を書き換えた場合に

コードの見通しが悪くなったり、バグを生み出す可能性があります。

Appleのドキュメントにも


Mirrors are used by playgrounds and the debugger.

って書いてありますし。


こういう設計を破壊できるような機能の多様はよくありません。

よくないですけど、楽しいのでもっといろんなことができたらいいなと思っていますw

何か他の用途だったり、記事に対する質問・誤り・指摘があればどしどし言ってください。

\(^o^)/