11
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-03-26

頭の体操

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

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

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

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

  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())
	}
}

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

.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馬場であきおさんに、サブクラス化がうまくできない事を相談した時に、あきおさんから出た一言に驚きました「ベースクラスが事実上 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()
}

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

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

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


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

.console
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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?