[Swift] 親Viewを辿ったりする操作を抽象化する

  • 11
    いいね
  • 0
    コメント

Tl;Dr

以下のようなヘルパー関数を定義しておくと、便利かも。

ヘルパー関数
func chainIterator<T>(_ value: T, _ f: @escaping (T?) -> (T?)) -> AnyIterator<T> {
    var next: T? = value
    return AnyIterator {
        defer { next = f(next) }
        return next
    }
}
親Viewを辿る
let v1 = UIView(); v1.tag = 1;
let v2 = UIView(); v2.tag = 2; v1.addSubview(v2)
let v3 = UIView(); v3.tag = 3; v2.addSubview(v3)
let v4 = UIView(); v4.tag = 4; v3.addSubview(v4)

let superviews = chainIterator(v4) { $0?.superview }
superviews.forEach { print($0.tag) }
/*
 4
 3
 2
 1
 */

一般的な方法

例えば、Viewの階層が以下のようになっていたとします。
()内はtagプロパティ値)

v1 (1)
|
+- v2 (2)
   |
   +- v3 (3)
      |
      +- v4 (4)
View階層のセットアップ
let v1 = UIView(); v1.tag = 1;
let v2 = UIView(); v2.tag = 2; v1.addSubview(v2)
let v3 = UIView(); v3.tag = 3; v2.addSubview(v3)
let v4 = UIView(); v4.tag = 4; v3.addSubview(v4)

v4からsuperviewを辿る場合には、おそらくwhileを使うのが一般的かと思います。

whileを使って辿る
var v = v4
while let superview = v.superview {
    print(superview.tag)
    v = superview
}
/*
 3
 2
 1
 */

ワンショットの走査ならこれでも良いかもしれませんが、何回か登場する場合にはちょっと冗長になりがちです。
superview専用の関数を用意しても良いかもしれませんが、せっかくなのでこの手の操作を一般化したいところです。

走査するIteratorを返す関数を定義する

冒頭でも記載しましたが、以下のようなヘルパー関数を書くと一般化できそうです。

func chainIterator<T>(_ value: T, _ f: @escaping (T?) -> (T?)) -> AnyIterator<T> {
    var next: T? = value
    return AnyIterator {
        defer { next = f(next) }
        return next
    }
}

これは以下のように利用します。

let superviews = chainIterator(v4) { $0?.superview }
superviews.forEach { print($0.tag) }
/*
 4
 3
 2
 1
 */

whileで書くのに比べた利点は、共通化されていて再利用できるのはもちろんですが、意図が明白になるという点が挙げられるかと思います。

chainIterator(v4) { $0?.superview }

v4をベースとしてsuperviewを辿る”という意図が非常に読み取りやすいかと思います。

またIteratorであるため、リスト系の操作をそのまま利用することも出来る利点もあります。

let superviews = chainIterator(v4) { $0?.superview }
superviews
    .filter { $0.tag % 2 == 0 }
    .map { "tag: \($0.tag)" }
    .forEach { print($0) }
/*
 tag: 4
 tag: 2
 */

このような操作が必要になるかはさておき、Iteratorとして抽象化して扱えるのはメリットかと思います。

リフレクションで継承チェーンを辿る

リフレクションで継承チェーンを辿りたいときにも利用できます。

class A {}
class B : A {}
class C : B {}
class D : C {}

let mirrorD = Mirror(reflecting: D())
let superclasses = chainIterator(mirrorD) { $0?.superclassMirror }
superclasses.forEach { print($0) }
/*
 Mirror for D
 Mirror for C
 Mirror for B
 Mirror for A
 */

このように、自身と同じ型かnilを返す、プロパティまたは関数で利用することができます。

最後に

こういった処理が必要になるケースはそんなに多くないかもしれませんが、こういったIteratorを使った「繰り返し」としての抽象化テクニックを覚えておくと、他のケースでも応用が効くかもしれません。(と言えるほど、私も詳しくはないのですが・・・)

一応、Gistにソースを上げています。
https://gist.github.com/YusukeHosonuma/8798429da3d117bf3c4bc004292ecb82

P.S.
Twitter上でのアドホックな議論だったのですが、せっかくなので知識共有としてQiitaに記事として上げてみました。