11
10

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 で Key や Value に弱参照が使える Dictionary 的な方法を考える

Last updated at Posted at 2016-03-26

はじめに

前回、Weak Object を Collection として扱う方法を記事にしました。これは、弱参照で複数のオブジェクトを保持するクラスを紹介しました。すると、次は、Dictionary 形式で、Key や Value に弱参照を使いたくなるのは自然な流れです。今回は方法を紹介したいと思います。

そんなコードを一生懸命考えていたのは、iOS馬場の勉強会の会場内です。

設計方針

Foundation に NSMapTable があります。これは、Key または Value の片方または両方に弱参照、強参照のオブジェクトが扱えますが、型の意識は全くありません。そこで、Generic を使って、Key と Value を型で縛った、クラスを設計してみたいと思います。

まずは、素直に Swift の Generics で wrap しただけのクラスを書いてみました。とりあえず、「強参照の Key と弱参照の Value を紐付ける」ZStrongToWeakMapTable、と「弱参照の Key と強参照の Value を紐付ける」ZWeakToStrongMapTable を普通に書いてみたいと思います。

.swift
class ZStrongToWeakMapTable<K: AnyObject, V: AnyObject>: CustomStringConvertible {

	let mapTable: NSMapTable

	init() {
		self.mapTable = NSMapTable.strongToWeakObjectsMapTable()
	}

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

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

	let mapTable: NSMapTable

	init() {
		self.mapTable = NSMapTable.weakToStrongObjectsMapTable()
	}

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

Genericsで Key を K、Value を V と型に縛りを入れているので、クライアントコードは Swift らしく型を意識したプログラミングが可能になります。

ではテストしてみましょう。弱参照の Key から強参照の Value を紐付けるクラス ZWeakToStrongMapTable を使ってみます。Dictionary と同様に [key] でアクセスできます。今回は Key も Value も文字列にしてみますが、Swift の StringAnyObject ではないので、NSString を使います。

.swift

let mapTable = ZWeakToStrongMapTable<NSString, NSString>()

var alice: NSString? = "Alice"
var bob: NSString? = "Bob"
var cathline: NSString? = "Cathline"

var toronto: NSString? = "Toronto"
var newyork: NSString? = "New York"
var tokyo: NSString? = "Tokyo"

mapTable[alice!] = toronto
mapTable[bob!] = newyork
mapTable[cathline!] = tokyo
print(mapTable.description)

alice = nil
print(mapTable.description)

普通に実行すると以下の通りです。途中で alicenil にしていますが、その直後も aliceNSMapTable に残ってしまいます。

.console
NSMapTable {
[3] Bob -> New York
[8] Alice -> Toronto
[15] Cathline -> Tokyo
}

NSMapTable {
[3] Bob -> New York
[8] Alice -> Toronto
[15] Cathline -> Tokyo
}

ところが、以下のようにタイミングをちょっとだけ遅らせて参照すると、alice が消えている事が確認できます。どんなタイミングで NSMapTable から消えるかは確認できていません。Run Loopの終わりじゃないかと言う噂も…

.swift
dispatch_async(dispatch_get_main_queue()) {
	print(self.mapTable.description)
}
.console
NSMapTable {
[3] Bob -> New York
[15] Cathline -> Tokyo
}

とにかく、動作する事が確認できたのですが、この NSMapTable は、Key と Value に 弱参照と強参照の組み合わせで4つのモデルがあります。よって同様の実装を4種類実装する必要があります。具体的には先のコードで NSMapTable.weakToStrongObjectsMapTable() の部分にバリエーションがあるだけで後は全く同じです。仕方ないので、同じようなコードを4回書くのは、使用上は全く問題がありませんが、エンジニアとしては残念な感じがします。

• Weak -> Strong
• Strong -> Weak
• Weak -> Strong
• Strong -> Strong

そこで、どうやって、この重複したコードをすっきりさせるかが課題となります。方法はいくつかあるのですが、本題の趣旨から逸脱するので、別のトピックとしたいと思います。ここではそのやり方の一つを紹介したいと思います。

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

サブクラス化して、スーパークラスに共通のコードを押し込む

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

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

繰り返しますが、Key も Value も AnyObject という縛りがあるので、Intstruct は受け付けてくれませんので、ご注意ください。

それではみなさん、Have a happy coding!

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

.console
wift --version
Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)
11
10
0

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
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?