LoginSignup
3
2

More than 5 years have passed since last update.

NSMapTable の挙動について調べてみた

Posted at

NSMapTableKey の挙動に不信感を抱いたので少し調べてみました。ObjC時代にも調べたような気がしますが、記憶が定かではないので、自分へのメモ。


keyNSString の場合。"string" as NSString とか書いている時点で、異なるオブジェクトをキーにしているような気もするのですが、なぜか一致します。まぁ、NSStringの場合はそうでなければ不便なのでいいとします。

var key: NSString
key = "east"
mapTable.setObject(1024 as NSNumber, forKey: key as NSString)
key = "west"
mapTable.setObject(512 as NSNumber, forKey: key as NSString)

mapTable.object(forKey: "east" as NSString) // "1024" as expected
mapTable.object(forKey: "west" as NSString) // "512" as expected

keyNSMutableString の場合で setString() でその値を変えてみました。なんか不思議な挙動ですね。table内のキーは取り出せなくなってしまったのでしょうか。

let mapTable = NSMapTable<NSString, NSNumber>()
var key: NSMutableString
key = NSMutableString(string: "north")
mapTable.setObject(256 as NSNumber, forKey: key)
key.setString("south")

mapTable.object(forKey: "north" as NSString) // nil
mapTable.object(forKey: "south" as NSString) // nil
mapTable.object(forKey: key) // nil -- why??
mapTable.keyEnumerator().allObjects // ["south"] wow!!

key が同じ値でも、別オブジェクトでアクセス。同一キーとして扱われています。

let mapTable = NSMapTable<NSString, NSNumber>()
var key1 = NSMutableString(string: "north")
var key2 = NSMutableString(string: "north")
mapTable.setObject(64 as NSNumber, forKey: key1)
mapTable.setObject(2048 as NSNumber, forKey: key2)

key1.setString("north")
mapTable.object(forKey: key1) // "2048" as expected
mapTable.keyEnumerator().allObjects // ["north"]

keyNSDictionary の場合。 keyvalueの定義する順番が異なっても、同じオブジェクトとして扱ってくれるようです。また、NSMutableDictionarysetDictionary() で同じKeyValueのセットを与えても、同じキーと判断してくれるようです。

let mapTable = NSMapTable<NSDictionary, NSString>()
let key1a = NSDictionary(dictionary: ["city": "Tokyo", "color": "blue"])
let key1b = NSDictionary(dictionary: ["color": "blue", "city": "Tokyo"])
let key1ax = NSMutableDictionary(dictionary: ["city": "Tokyo", "color": "blue"])
var key1c = NSMutableDictionary()

key1c.setDictionary(["city": "Tokyo", "color": "blue"])
mapTable.setObject("Japan" as NSString, forKey: key1a)
mapTable.object(forKey: key1b) // "Japan"
mapTable.object(forKey: key1ax) // "Japan"
mapTable.object(forKey: key1c) // "Japan"

keyNSMutableDictionary にして 値をセット、その後 key の値を変更。すると、テーブル内のキーも変更され、キーはオブジェクトのように扱われているようです。

let mapTable = NSMapTable<NSDictionary, NSString>()
var key1 = NSMutableDictionary(dictionary: ["city": "Tokyo", "color": "blue"])
mapTable.setObject("Apple" as NSString, forKey: key1)
key1.setDictionary(["color": "red", "city": "Tokyo"]) // "red" !?
mapTable.object(forKey: key1) // "Apple"
mapTable.object(forKey: ["city": "Tokyo", "color": "blue"]) // nil
mapTable.keyEnumerator().allObjects // [["color": "red", "city": "Tokyo"]]

自分のクラスの場合です。HashableEquatable)を実装しても、実質的等価なオブジェクトも同じキーとは扱えないようで、これはこれで期待通りの挙動だったりします。

class MyObject: Hashable {
    var name: String
    init(name: String) {
        self.name = name
    }
    var hashValue: Int { return name.hashValue }
}

func == (lhs: MyObject, rhs: MyObject) -> Bool {
    return lhs.name == rhs.name
}

let mapTable = NSMapTable<MyObject, NSString>()
let obj1a = MyObject(name: "red")
let obj1b = MyObject(name: "red")
let obj2 = MyObject(name: "blue")
obj1a == obj1b // true
mapTable.setObject("Orange", forKey: obj1a)
mapTable.object(forKey: obj1a) // "Orange"
mapTable.object(forKey: obj1b) // nil
mapTable.object(forKey: obj2) // nil
3
2
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
3
2