頭の体操
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つの機能を提供する方法を考えてみましょう。当方の考えた答は何種類か用意しましたが、同様にいくつか考えられるはずです。是非、このまま読み続ける前に自分で一度コーディングしてみてください。頭の体操です。
- StrongToWeak -> NSMapTable.strongToWeakObjectsMapTable()
- WeakToStrong -> NSMapTable.weakToStrongObjectsMapTable()
- WeakToWeak -> NSMapTable.weakToWeakObjectsMapTable()
- 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...
サブクラス化版
https://gist.github.com/codelynx/5e24a436ae3c4d91df33Protocol Extension 版
https://gist.github.com/codelynx/f304568a505da0e6f45eパラメータ版
https://gist.github.com/codelynx/cadf72f1bbba5f453dd9
EDIT: Swift のバージョンは以下の通りです。
wift --version
Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)