LoginSignup
11
11

More than 5 years have passed since last update.

Swiftでほんの一部しか違わないクラスのバリエーションの実装方法の考察

Last updated at Posted at 2016-03-26

頭の体操

iOS馬場の勉強会で取り組んでいた課題が面白そうだったので、会場で時間切れになったネタを持ち帰ってみました。そこで、今回は頭の体操をしてみたいと思います。読者の方も単に読みとばすのではなくて、自分ならどうすると考えながら読んでいただけると幸いです。

まず前回(直前)、「Swift で Key や Value に弱参照が使える Dictionary 的な方法を考える」の中で、最初に出てきたコードがこれです。

class ZStrongToWeakMapTable<K: AnyObject, V: AnyObject> {

    let mapTable: NSMapTable

    init() {
        self.mapTable = NSMapTable.strongToWeakObjectsMapTable() // <== Here !!
    }

    subscript(key: K) -> V? {
        get {
            return self.mapTable.objectForKey(key) as? V
        }
        set {
            self.mapTable.setObject(newValue, forKey: key)
        }
    }

    var description: String {
        return mapTable.description
    }
}

さて、これには、全部の4つのバリエーションがあります。なるべく、重複したコードを少なくしたコードでこの4つの機能を提供する方法を考えてみましょう。当方の考えた答は何種類か用意しましたが、同様にいくつか考えられるはずです。是非、このまま読み続ける前に自分で一度コーディングしてみてください。頭の体操です。

  1. StrongToWeak -> NSMapTable.strongToWeakObjectsMapTable()
  2. WeakToStrong -> NSMapTable.weakToStrongObjectsMapTable()
  3. WeakToWeak -> NSMapTable.weakToWeakObjectsMapTable()
  4. StrongToStrong -> NSMapTable.strongToStrongObjectsMapTable()

サブクラス化作戦

最初私は、スーパクラスとサブクラスの関係を作って、スーパークラス側に subscript()description を集約しようとしました。まずベースとなるコードはすんなりできました

class ZMapTable<K: AnyObject, V: AnyObject>: CustomStringConvertible {
    let mapTable: NSMapTable
    private init(mapTable: NSMapTable) {
        self.mapTable = mapTable
    }
    subscript(key: K) -> V? {
        get { return self.mapTable.objectForKey(key) as? V }
        set { self.mapTable.setObject(newValue, forKey: key) }
    }
    var description: String {
        return mapTable.description
    }
}

これのサブクラスを頑張りますが、なかなか Syntax Error がとれてくれません。

class ZStrongToWeakMapTable<K, V>: ZMapTable { // error
    init() {
        super.init(mapTable: NSMapTable.strongToWeakObjectsMapTable())
    }
}

試行錯誤に、すったもんだした結果、こんな風に書けばいい事がわかりました。こんなんおぼえられるかぁ!

class ZStrongToWeakMapTable<K: AnyObject, V: AnyObject>: ZMapTable<K, V> {
    init() {
        super.init(mapTable: NSMapTable.strongToWeakObjectsMapTable())
    }
}

残りもこんな風に書く事ができます。

class ZWeakToStrongMapTable<K: AnyObject, V: AnyObject>: ZMapTable<K, V> {
    init() {
        super.init(mapTable: NSMapTable.weakToStrongObjectsMapTable())
    }
}

class ZWeakToWeakMapTable<K: AnyObject, V: AnyObject>: ZMapTable<K, V> {
    init() {
        super.init(mapTable: NSMapTable.weakToWeakObjectsMapTable())
    }
}

class ZStrongToStrongMapTable<K: AnyObject, V: AnyObject>: ZMapTable<K, V> {
    init() {
        super.init(mapTable: NSMapTable.strongToStrongObjectsMapTable())
    }
}

Protocol Extension 作戦

iOS馬場であきおさんに、サブクラス化がうまくできない事を相談した時に、あきおさんから出た一言に驚きました「ベースクラスが事実上 abstract class なら、 protocol extension に処理を集約するのはどうだろうか?」といった趣旨の言葉です。なるほど、protocol extension で解決出来るユースケースをあまりにも体験していないので、なかなか自然にそういう発想が生まれないのですが、あきおさんと相談しながら試行錯誤してみました。

protocol ZMapTableProtocol {
    associatedtype K: AnyObject
    associatedtype V: AnyObject

    var mapTable: NSMapTable { get }
    subscript(key: K) -> V? { get set }
}

extension ZMapTableProtocol: CustomStringConvertible {
    subscript(key: K) -> V? {
        get { return self.mapTable.objectForKey(key) as? V }
        set { self.mapTable.setObject(newValue, forKey: key) }
    }
    var description: String { return self.mapTable.description }
}

とまぁ、ここまでいいのですが、具体的なクラスを実装するとまた Syntax Error です。

class ZStrongToWeakMapTable ZMapTableProtocol { // error !! does not conform to protocol ....
    let mapTable: NSMapTable = NSMapTable.strongToWeakObjectsMapTable()
}

会場で時間切れになったので、持ち帰りまたもや、試行錯誤とすったもんだを繰り返しした結果がこれです。こんなんわかるかぁ!

class ZStrongToWeakMapTable<T: AnyObject, U: AnyObject>: ZMapTableProtocol {
    typealias K = T
    typealias V = U
    let mapTable: NSMapTable = NSMapTable.strongToWeakObjectsMapTable()
}

class ZWeakToStrongMapTable<T: AnyObject, U: AnyObject>: ZMapTableProtocol {
    typealias K = T
    typealias V = U
    let mapTable: NSMapTable = NSMapTable.weakToStrongObjectsMapTable()
}

class ZWeakToWeakMapTable<T: AnyObject, U: AnyObject>: ZMapTableProtocol {
    typealias K = T
    typealias V = U
    let mapTable: NSMapTable = NSMapTable.weakToWeakObjectsMapTable()
}

class ZStrongToStrongMapTable<T: AnyObject, U: AnyObject>: ZMapTableProtocol {
    typealias K = T
    typealias V = U
    let mapTable: NSMapTable = NSMapTable.strongToStrongObjectsMapTable()
}

パラメータ作戦

Protocol Extension の文法とエラーと格闘しながらフト思いついてしまいました。いやぁ、あまりにも複雑な文法と格闘した後にこれでは、あまりにもシンプルすぎるのですが、そもそも4つのモデル別のクラスを作らなくても、イニシャライザのパラメータでモデルが決定すればいいじゃないかという話です。

enum ZMapTableOption {
    case StrongToWeak
    case WeakToStrong
    case WeakToWeak
    case StrongToStrong
}

class ZMapTable<K: AnyObject, V: AnyObject>: CustomStringConvertible {

    let mapTable: NSMapTable

    init(option: ZMapTableOption) {
        switch option {
        case .StrongToWeak: self.mapTable = NSMapTable.strongToWeakObjectsMapTable()
        case .WeakToStrong: self.mapTable = NSMapTable.weakToStrongObjectsMapTable()
        case .WeakToWeak: self.mapTable = NSMapTable.weakToWeakObjectsMapTable()
        case .StrongToStrong: self.mapTable = NSMapTable.strongToStrongObjectsMapTable()
        }
    }

    subscript(key: K) -> V? {
        get { return self.mapTable.objectForKey(key) as? V }
        set { self.mapTable.setObject(newValue, forKey: key) }
    }

    var description: String {
        return self.mapTable.description
    }

}

一番シンプルですよね。

まとめ

最終的なコードは以下の Gist からも拾ってくる事ができます。それではみなさま、Have a happy coding...


EDIT: Swift のバージョンは以下の通りです。

wift --version
Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)
11
11
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
11