will and way

ただの自分用メモを人に伝える形式で書くことでわかりやすくまとめてるはずのブログ

objc_getAssociatedObject で必ずnil返ってくる件(解決済み)

blog.matsuokah.jp

こちらのエントリーでExtensionPropertyをExtensionとしてつけるだけでassociated objectにアクセスしやすくするというエントリーを書きました

しかしながら、この記事にはバグがありました。 それは getProperty が必ずnilになってしまい、デフォルト値が必ず返ってきていました。

原因

該当するコード

public extension ExtensionProperty {
    public func getProperty<K: ExtensionPropertyKey, V>(key: K, initialValue: V) -> V {
        var keyString = key.keyString

        if let variable = objc_getAssociatedObject(self, &keyString) as? V {
            return variable
        }

        setProperty(key: key, newValue: initialValue)
        return initialValue
    }

    public func setProperty<K: ExtensionPropertyKey, V>(key: K, newValue: V) {
        objc_setAssociatedObject(self, key.keyString, newValue, .OBJC_ASSOCIATION_RETAIN)
    }
}

var keyString = key.keyString

Stringをコピーしていたため、インスタンスのメモリの番地が違ってしまっていた。結果的にキーが違うのでnilが返ってくるという理屈でした。

対応

本当はExtensionPropertyKeyのkeyStringの参照を渡すことができれば容易なのですが、 inoutな引数にletな値を渡すことはできません。
そこで、UnsafeRawPointerを取得して渡すことで解決しました。これならenumをキーとして使いたいという当初の目的を達成しています。

internal extension ExtensionPropertyKey {
    var keyPointer: UnsafeRawPointer {
        return unsafeBitCast(self.keyString, to: UnsafeRawPointer.self)
    }
}


// MARK: - ExtensionProperty
public extension ExtensionProperty {
    public func getProperty<K: ExtensionPropertyKey, V>(key: K, defaultValue: V) -> V {
        if let variable = objc_getAssociatedObject(self, key.keyPointer) as? V {
            return variable
        }
        
        setProperty(key: key, newValue: defaultValue)
        return defaultValue
    }
    
    public func setProperty<K: ExtensionPropertyKey, V>(key: K, newValue: V, policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN) {
        objc_setAssociatedObject(self, key.keyPointer, newValue, policy)
    }
}

いや〜。ナマのポインタは最強ですね