NSMutableDictionary "Argument type 'String' does not conform to expected type 'NSCopying'"

var str = "Hello, playground"
var dict = NSMutableDictionary()
dict.setObject("Hi", forKey: str) // error
dict[str] = "Hi" // no error

How come .setObject generates, "Argument type 'String' does not conform to expected type 'NSCopying'?"

There's a difference in function signature, from NSMutableDictionary

func setObject(_ anObject: Any, forKey aKey: NSCopying)
@objc override dynamic subscript(key: Any) -> Any? { get set }

And subscript is Swift-only implementation. Could be historical.

Anyhow, NSString conforms to NSCopying, so you can do

dict.setObject("Hi", forKey: str as NSString)
1 Like

Thanks @Lantua, it sure would be neat if Swift implicitly converted String to NSString to overcome these errors!

Well it used to, until SE-0072 Fully eliminate implicit bridging conversions from Swift that is.

A better solution is to evaluate why you’re using NSMutabkeDictionary in the first place and try not to.

Jon, solving the error was not the question. Surely you can imagine a scenario requiring using " NSMutabkeDictionary "

There aren't a lot of scenarios actually, mostly you can bridge them to Dictionary. Unless you want the reference-type behavior NS provides, or when you cross back-and-forth between ObjC and Swift very frequently.

Like Lantua said, there really aren't any such scenarios. Early in Swift's history there were Obj-C or CoreFoundation APIs which didn't play well with bridged Dictionary values, but those issues have been fixed. Nowadays, even if you do want to end up with an NSDictionary value, it's usually best to bridge from Dictionary anyway, as it makes building the dictionary much easier. I can't think of any scenarios where you have to use NSDictionary, much less NSMutableDictionary, directly off the top of my head.

Thread.threadDictionary being a notable exception.

I just had to do:

commit 57f4dd393911fb62202239050056cacc41e1387d (HEAD -> master, origin/master)
Author: Dr. Michael Lauer <mickey@vanille-media.de>
Date:   Fri Dec 10 10:00:32 2021 +0100

    Thread+Number: and fix it for Apple platforms.

    Gotta love all these Foundation differences *sigh*

diff --git a/Sources/CornucopiaCore/Extensions/Thread/Thread+Number.swift b/Sources/CornucopiaCore/Extensions/Thread/Thread+Number.swift
index 1c43486..fd6c863 100644
--- a/Sources/CornucopiaCore/Extensions/Thread/Thread+Number.swift
+++ b/Sources/CornucopiaCore/Extensions/Thread/Thread+Number.swift
@@ -7,7 +7,11 @@ extension Thread {

     private static var lock: NSLock = .init()
     private static var threadNumber: Int = 1
+    #if canImport(ObjectiveC)
+    private static let key: NSString = "dev.cornucopia.core.thread-number"
+    #else
     private static let key: String = "dev.cornucopia.core.thread-number"
+    #endif

to make guard let threadNumber = Thread.current.threadDictionary.object(forKey: Self.key) else ... working.

Is there a better way?