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

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

まず前回（直前）、「[Swift で Key や Value に弱参照が使える Dictionary 的な方法を考える](http://qiita.com/codelynx/items/27b8dd8c3ac49f6316ef)」の中で、最初に出てきたコードがこれです。

```.swift
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
    }
}
```

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

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

#サブクラス化作戦

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

```.swift
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 がとれてくれません。

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

試行錯誤に、すったもんだした結果、こんな風に書けばいい事がわかりました。<font color="silver">こんなんおぼえられるかぁ！</font>

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

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

```.swift
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馬場で[あきおさん](http://qiita.com/akio0911)に、サブクラス化がうまくできない事を相談した時に、あきおさんから出た一言に驚きました「ベースクラスが事実上 abstract class なら、 protocol extension に処理を集約するのはどうだろうか？」といった趣旨の言葉です。なるほど、protocol extension で解決出来るユースケースをあまりにも体験していないので、なかなか自然にそういう発想が生まれないのですが、あきおさんと相談しながら試行錯誤してみました。

```.swift
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 です。

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

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

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

```.swift
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...

* サブクラス化版
https://gist.github.com/codelynx/5e24a436ae3c4d91df33

* Protocol Extension 版
https://gist.github.com/codelynx/f304568a505da0e6f45e

* パラメータ版
https://gist.github.com/codelynx/cadf72f1bbba5f453dd9

---

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

```.console
wift --version
Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)
```