It turns out that within a Dictionary extension, the subscript is quite useless since it says Ambiguous reference to member 'subscript'. It seems I'll either have to do what Swift does in its subscript(Key) or call a function. Any ideas?
For example,
public extension Dictionary {
public func bool(_ key: String) -> Bool? {
return self[key] as? Bool
}
}
won't work, since the subscript is said to be ambiguous.
ADDED My misunderstanding came from the fact that I assumed that Key is an AssociatedType instead of a generic parameter.
Swift type Dictionary has two generic parameters Key and Value, and Key may not be String.
This works:
public extension Dictionary {
public func bool(_ key: Key) -> Bool? {
return self[key] as? Bool
}
}
let dict: [String: Any] = [
"a": true,
"b": 0,
]
if let a = dict.bool("a") {
print(a) //->true
}
if let b = dict.bool("b") {
print(b) //not executed
}
For ADDED part.
If you introduce a new generic parameter T in extension of Dictionary, the method needs to work for all possible combination of Key(:Hashable), Value and T(:Hashable). Key and T may not be the same type.
(For example, Key may be String and T may be Int (both Hashable). You know you cannot subscript with Int when Key is String.)
So, you cannot subscript with key of type T.
For updated ADDED part.
Seems to be a reasonable misunderstanding. And you have found a good example that explains protocol with associated type is not just a generic protocol.
Related
I wrote this utility function in Swift 4:
func insert<Key, Element>(_ value: Element, into dictionary: inout [Key : Set<Element>], at key: Key) {
if let _ = dictionary[key] {
dictionary[key]?.insert(value)
}
else {
var set = Set<Element>()
set.insert(value)
dictionary[key] = set
}
}
This is used like this:
insert("foo", into: &myDictionary, at: "bar")
... but I want to use it like this:
myDictionary.insert("foo", at: "bar")
I tried declaring it like this:
extension Dictionary where Value == Set<AnyHashable> {
mutating func insert(_ value: Value.Element, at key: Key) { // Error here
if let _ = self[key] {
self[key]?.insert(value)
} else {
var set = Set<Value.Element>() // Error here
set.insert(value)
self[key] = set
}
}
}
... but I get the following errors:
/path/to/Sequence Extensions.swift:2:41: error: 'Element' is not a member type of 'Dictionary.Value'
mutating func insert(_ value: Value.Element, at key: Key) {
~~~~~ ^
Swift.Set:608:22: note: did you mean 'Element'?
public typealias Element = Element
^
Swift._IndexableBase:3:22: note: did you mean '_Element'?
public typealias _Element = Self.Element
/path/to/Sequence Extensions.swift:6:23: error: type 'Value.Element' does not conform to protocol 'Hashable'
var set = Set<Value.Element>()
^
Unfortunately, Swift doesn't currently support parameterised extensions (the ability to introduce type variables in extension declarations), so you cannot currently directly express the notion of "an extension with a constraint to some Set<T>". However, it is a part of the generics manifesto, so hopefully it's something that makes its way into a future version of the language.
Even if your extension with Value constrained to Set<AnyHashable> compiled, it wouldn't be terribly useful. You would need to first convert your desired dictionary to a temporary [Key: Set<AnyHashable>], then call the mutating method on it, and then convert it back to its original type (using as!).
This is because the extension is on a Dictionary with heterogenous Set values. It would've been perfectly legal for the extension method to insert arbitrary Hashable elements into one of the values of the dictionary. But that's not what you wanted to express.
In simple cases, I would argue that there's no need for an extension in the first place. You can just say:
var dict = [String: Set<String>]()
dict["key", default: []].insert("someValue")
using Dictionary's subscript overload that takes a default value, as introduced in SE-0165.
If you still want an extension, I would advise simply making it more generic. For example, instead of constraining Value to Set; constrain it to the protocol SetAlgebra (which Set conforms to).
It represents types that can perform set-like operations, and also derives from ExpressibleByArrayLiteral meaning that you can implement your method using the exact syntax as above:
extension Dictionary where Value : SetAlgebra {
mutating func insert(_ value: Value.Element, at key: Key) {
self[key, default: []].insert(value)
}
}
Although one additional thing to consider here is the copy-on-write behaviour of Swift's collection types such as Set. In the above method, the dictionary will be queried for a given key, giving back either an existing set for that key, or a new empty one. Your value will then be inserted into this temporary set, and it will be re-inserted back into the dictionary.
The use of a temporary here means that if the set is already in the dictionary, the value will not be inserted into it in-place, the set's buffer will be copied first in order to preserve value semantics; which could be a performance concern (this is explored in more detail in this Q&A and this Q&A).
However that being said, I am currently looking to fix this for Dictionary's subscript(_:default:) in this pull request, such that the set can be mutated in-place.
Until fixed though, the solution is to first remove the set from the dictionary before mutating:
extension Dictionary where Value : SetAlgebra {
mutating func insert(_ value: Value.Element, at key: Key) {
var set = removeValue(forKey: key) ?? []
set.insert(value)
self[key] = set
}
}
In which case, the use of an extension is fully justified.
It's worth noting that the use of a protocol constraint here is the general solution (or workaround in some cases) to the problem of not having parameterised extensions. It allows you to realise the placeholders you need as associated types of that protocol. See this Q&A for an example of how you can create your own protocol to serve that purpose.
You could do it using a protocol to identify Sets:
protocol SetType
{
associatedtype Element:Hashable
init()
mutating func insert(_ : Element) -> (inserted: Bool, memberAfterInsert: Element)
}
extension Set:SetType
{}
extension Dictionary where Value : SetType
{
mutating func insert(_ value:Value.Element, at key:Key)
{
var valueSet:Value = self[key] ?? Value()
valueSet.insert(value)
self[key] = valueSet
}
}
var oneToMany:[String:Set<String>] = [:]
oneToMany.insert("Dog", at: "Animal")
oneToMany.insert("Cat", at: "Animal")
oneToMany.insert("Tomato", at: "Vegetable")
This will produce a dictionary of sets:
["Animal": Set(["Dog", "Cat"]), "Vegetable": Set(["Tomato"])]
A more appropriate implementation would use the same return value as a Set's insert() function however:
extension Dictionary where Value : SetType
{
#discardableResult
mutating func insert(_ value:Value.Element, at key:Key) -> (inserted: Bool, memberAfterInsert: Value.Element)
{
var valueSet:Value = self[key] ?? Value()
let result = valueSet.insert(value)
if result.inserted
{ self[key] = valueSet }
return result
}
}
[EDIT] I just read all of Hamish's response and realized that he had already given the same answer (essentially) and made use of SetAlgebra ( which I wasn't aware of) that does the same thing as the SetType I "reinvented". You should accept Hamish's answer.
I am trying to add additional properties to UIViewController.
Code:
protocol AdditionalStoredProperties
{
associatedtype Title
func getAssociatedObject<Title>(key: UnsafePointer<Title> ,
defValue : Title)->Title
}
extension AdditionalStoredProperties
{
func getAssociatedObject<Title>( key: UnsafePointer<Title> , defValue : Title)->Title
{
guard let actual_value = objc_getAssociatedObject(self as! AnyObject, key) as? Title else
{
return defValue
}
return actual_value
}
}
extension UIViewController:AdditionalStoredProperties
{
typealias Title = String
var previousPage : String
{
get { return getAssociatedObject(&self.previousPage, defValue: self.previousPage) }
set { objc_setAssociatedObject(self, &self.previousPage, newValue, .OBJC_ASSOCIATION_RETAIN)}
}
}
But I am getting the following error:
Error: Trying to put the stack in unreadable memory at:
I know that we cannot directly add stored properties to extensions so I am trying it add using objc_setAssociatedObject()
If someone has the below scenario
If your method is getting called recursively, you may get this error.
There are a number of things wrong with what you're doing:
Attempting to access self.previousPage within its own getter will call itself recursively.
You cannot use &self.previousPage as a stable or unique pointer value, as it'll be a pointer to a temporary variable (because you're dealing a computed property). You cannot therefore use it as the key for an associated object. Swift only guarantees stable and unique pointer values for static and global stored variables (see this Q&A for more info).
You should make AdditionalStoredProperties a class-bound protocol (with : class), as you can only add associated objects to Objective-C classes (which, on Apple platforms, Swift classes are built on top of). While you can bridge, for example, a struct to AnyObject (it'll get boxed in an opaque Obj-C compatible wrapper), it is merely that; a bridge. There's no guarantee you'll get the same instance back, therefore no guarantee the associated objects will persist.
You probably didn't mean for Title to be an associated type of your protocol; you're not using it for anything (the generic placeholder Title defined by getAssociatedObject(key:defValue:) is completely unrelated).
Bearing those points in mind, here's a fixed version of your code:
protocol AdditionalStoredProperties : class {
func getAssociatedObject<T>(ofType: T.Type, key: UnsafeRawPointer,
defaultValue: #autoclosure () -> T) -> T
}
extension AdditionalStoredProperties {
func getAssociatedObject<T>(ofType: T.Type, key: UnsafeRawPointer,
defaultValue: #autoclosure () -> T) -> T {
// or: return objc_getAssociatedObject(self, key) as? T ?? defaultValue()
guard let actualValue = objc_getAssociatedObject(self, key) as? T else {
return defaultValue()
}
return actualValue
}
}
extension UIViewController : AdditionalStoredProperties {
private enum AssociatedObjectKeys {
static var previousPage: Never?
}
var previousPage: String {
get {
// return the associated object with a default of "" (feel free to change)
return getAssociatedObject(ofType: String.self,
key: &AssociatedObjectKeys.previousPage,
defaultValue: "")
}
set {
objc_setAssociatedObject(self, &AssociatedObjectKeys.previousPage,
newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
}
Note that we're:
Using a static stored property in order to get a pointer value to use as the key for our associated object. Again, this works because Swift guarantees stable and unique pointer values for static and global stored variables.
Using #autoclosure for the defaultValue: parameter, as it may not need to be evaluated if an associated object is already present.
Having the key: parameter take an UnsafeRawPointer, as the type of the pointee is irrelevant; it's merely the location in memory that's used as the key.
Explicitly satisfying the generic placeholder with an ofType: parameter. This is mainly a matter of preference, but I prefer to spell these things out explicitly rather than relying on type inference.
Using camelCase instead of snake_case, as is Swift convention.
I tried to implement the following method to remove double entries in an array of dictionaries by comparing their specific keys. However, this extension method will not work due to the error:
Binary operator == cannot be applied to two 'Equatable' operands
These are obviously equatable and same type (Iterator.Element.Value), so why doesn't it work?
I see that it treats Equatable as a specific type, not a constraint. I could not make it work with generic type or by writing where Iterator.Element == [String: Any], Iterator.Element.Value: Equatable.
Do you guys have any clues about how to solve this?
extension Sequence where Iterator.Element == [String: Equatable] {
public func removeDoubles(byKey uniqueKey: String) -> [Iterator.Element] {
var uniqueValues: [Iterator.Element.Value] = []
var noDoubles: [Iterator.Element] = []
for item in self {
if let itemValue = item[uniqueKey] {
if (uniqueValues.contains { element in
return itemValue == element
}) {
uniqueValues.append(itemValue)
noDoubles.append(item)
}
}
}
return noDoubles
}
}
A [String: Equatable] is a mapping of strings to any Equatable type. There is no promise that each value be the same equatable type. That said, it's not actually possible to create such a dictionary (since Equatable has an associated type), so this extension cannot apply to any actual type in Swift. (The fact that you don't receive an error here is IMO a bug in the compiler.)
The feature you'd need to make this work is SE-0142, which is accepted, but not implemented. You currently cannot constrain an extension based on type constraints this way.
There are many ways to achieve what you're trying to do. One straightforward way is to pass your equality function:
extension Sequence {
public func removeDoubles(with equal: (Iterator.Element, Iterator.Element) -> Bool) -> [Iterator.Element] {
var noDoubles: [Iterator.Element] = []
for item in self {
if !noDoubles.contains(where: { equal($0, item) }) {
noDoubles.append(item)
}
}
return noDoubles
}
}
let noDupes = dict.removeDoubles(with: { $0["name"] == $1["name"] })
This is slightly different than your code in how it behaves when name is missing, but slight tweaks could get what you want.
That said, the need for this strongly suggests an incorrect data model. If you have this sequence of dictionaries, and you're trying to build an extension on that, you almost certainly meant to have a sequence of structs. Then this becomes more straightforward. The point of a dictionary is an arbitrary mapping of keys to values. If you have a small set of known keys that are legal, that's really a struct.
In Swift 2.x, I had a nice little setup that allowed me to store and retrieve dictionary values using enum members:
public enum UserDefaultsKey : String {
case mainWindowFrame
case selectedTabIndex
case recentSearches
}
extension Dictionary where Key : String {
public subscript(key: UserDefaultsKey) -> Value? {
get { return self[key.rawValue] }
set { self[key.rawValue] = newValue }
}
}
This allowed me to access values like this:
let dict = userDefaults.dictionaryForKey("SearchPrefs")
if let recentSearches = dict?[.recentSearches] as? [String] {
// Populate "Recent" menu items
}
… instead of having to access values like this:
let dict = userDefaults.dictionaryForKey("SearchPrefs")
if let recentSearches = dict?[UserDefaultsKey.recentSearches.rawValue] as? [String] {
// Populate "Recent" menu items
}
Note: The use of a string literal to access the dictionary from NSUserDefaults is for example purposes only. I wouldn't actually go out of my way to use an enum for dictionary keys, only to use a string literal to access the dictionary itself. :-)
Anyway, this has worked great for my needs, and it made reading and maintaining code involving NSUserDefaults a lot more pleasant.
Since migrating my project to Swift 3, however, I'm getting the following error:
extension Dictionary where Key: String {
public subscript(key: UserDefaultsKey) -> Value? { <---- Use of undeclared type 'Value'
~~~~~~
get {
return self[key.rawValue]
}
set {
self[key.rawValue] = newValue
}
}
}
I looked at the generated headers for Dictionary, and the generic Key and Value arguments are still present in the Generic Argument Clause of the Dictionary struct, so I'm not too sure what the issue is.
Do I need to rewrite the where clause to conform to some new Swift 3 grammar I'm unaware of? Or … can one no longer access generic placeholder types in extensions?
I just don't know what to do!
My project has only 28 migration errors left to resolve. I'm so close to actually getting to use Swift 3, so I'd love any pointers (as long as they're not Unsafe and/or Raw).
Thanks!
A generic parameter of a concrete type cannot be constrained to a concrete type, currently. This means that something like
extension Dictionary where Key == String
won't compile. It's a limitation of the generics system, and it hopefully won't be a problem in Swift 4.
There is a workaround though, but it's a bit hacky:
protocol StringConvertible {
init(_ string: String)
}
extension String: StringConvertible {}
extension Dictionary where Key: StringConvertible {
subscript(key: UserDefaultsKey) -> Value? {
get { return self[Key(key.rawValue)] }
set { self[Key(key.rawValue)] = newValue }
}
}
One of the things that bugs me about Swift and Cocoa together is working with NSUserDefaults, because there is no type information and it is always necessary to cast the result of objectForKey to what you are expecting to get. It is unsafe and impractical. I decided to tackle this problem, making NSUserDefaults more practical in Swift-land, and hopefully learning something along the way. Here were my goals in the beginning:
Complete type safety: each key has one type associated with it. When setting a value, only a value of that type should be accepted and when getting a value the result should come out with the correct type
Global list of keys which are clear in meaning and content. The list should be easy to create, modify and extend
Clean syntax, using subscripts if possible. For example, this would
be perfect:
3.1. set: UserDefaults[.MyKey] = value
3.2. get: let value = UserDefaults[.MyKey]
Support for classes that conform to the NSCoding protocol by
automatically [un]archiving them
Support for all property list types accepted by NSUserDefaults
I started by creating this generic struct:
struct UDKey <T> {
init(_ n: String) { name = n }
let name: String
}
Then I created this other struct that serves as a container for all the keys in an application:
struct UDKeys {}
This can then be extended to add keys wherever needed:
extension UDKeys {
static let MyKey1 = UDKey<Int>("MyKey1")
static let MyKey2 = UDKey<[String]>("MyKey2")
}
Note how each key has a type associated with it. It represents the type of the information to be saved. Also, the name property is the string that is to be used as a key for NSUserDefaults.
The keys can be listed all in one constants file, or added using extensions on a per-file basis close to where they are being used for storing data.
Then I created an "UserDefaults" class responsible for handling the getting/setting of information:
class UserDefaultsClass {
let storage = NSUserDefaults.standardUserDefaults()
init(storage: NSUserDefaults) { self.storage = storage }
init() {}
// ...
}
let UserDefaults = UserDefaultsClass() // or UserDefaultsClass(storage: ...) for further customisation
The idea is that one instance for a particular domain is created and then every method is accessed in this way:
let value = UserDefaults.myMethod(...)
I prefer this approach to things like UserDefaults.sharedInstance.myMethod(...) (too long!) or using class methods for everything. Also, this allows interacting with various domains at the same time by using more than one UserDefaultsClass with different storage values.
So far, items 1 and 2 have been taken care of, but now the difficult part is starting: how to actually design the methods on UserDefaultsClass in order to comply with the rest.
For example, let's start with item 4. First I tried this (this code is inside UserDefaultsClass):
subscript<T: NSCoding>(key: UDKey<T>) -> T? {
set { storage.setObject(NSKeyedArchiver.archivedDataWithRootObject(newValue), forKey: key.name) }
get {
if let data = storage.objectForKey(key.name) as? NSData {
return NSKeyedUnarchiver.unarchiveObjectWithData(data) as? T
} else { return nil }
}
}
But then I find out that Swift doesn't allow generic subscripts!! Alright, then I guess I'll have to use functions then. There goes half of item 3...
func set <T: NSCoding>(key: UDKey<T>, _ value: T) {
storage.setObject(NSKeyedArchiver.archivedDataWithRootObject(value), forKey: key.name)
}
func get <T: NSCoding>(key: UDKey<T>) -> T? {
if let data = storage.objectForKey(key.name) as? NSData {
return NSKeyedUnarchiver.unarchiveObjectWithData(data) as? T
} else { return nil }
}
And that works just fine:
extension UDKeys { static let MyKey = UDKey<NSNotification>("MyKey") }
UserDefaults.set(UDKeys.MyKey, NSNotification(name: "Hello!", object: nil))
let n = UserDefaults.get(UDKeys.MyKey)
Note how I can't call UserDefaults.get(.MyKey). I have to use UDKeys.MyKey. And I can't do that because it's not yet possible to have static variables on a generic struct!!
Next, let's try number 5. Now that has been an headache and that's where I need lots of help.
Property list types are, as per the docs:
A default object must be a property list, that is, an instance of (or
for collections a combination of instances of): NSData, NSString,
NSNumber, NSDate, NSArray, or NSDictionary.
That in Swift means Int, [Int], [[String:Bool]], [[String:[Double]]], etc are all property list types. At first I thought that I could just write this and trust whoever is using this code to remember that only plist types are allowed:
func set <T: AnyObject>(key: UDKey<T>, _ value: T) {
storage.setObject(value, forKey: key.name)
}
func get <T: AnyObject>(key: UDKey<T>) -> T? {
return storage.objectForKey(key.name) as? T
}
But as you'll notice, while this works fine:
extension UDKeys { static let MyKey = UDKey<NSData>("MyKey") }
UserDefaults.set(UDKeys.MyKey, NSData())
let d = UserDefaults.get(UDKeys.MyKey)
This doesn't:
extension UDKeys { static let MyKey = UDKey<[NSData]>("MyKey") }
UserDefaults.set(UDKeys.MyKey, [NSData()])
And this doesn't either:
extension UDKeys { static let MyKey = UDKey<[Int]>("MyKey") }
UserDefaults.set(UDKeys.MyKey, [0])
Not even this:
extension UDKeys { static let MyKey = UDKey<Int>("MyKey") }
UserDefaults.set(UDKeys.MyKey, 1)
The problem is that they are all valid property list types yet Swift obviously interprets arrays and ints as structs, not as their Objective-C class counterparts. However:
func set <T: Any>(key: UDKey<T>, _ value: T)
won't work either, because then any value type, not just the ones that have a class cousin courtesy of Obj-C, is accepted, and storage.setObject(value, forKey: key.name) is no longer valid because value has to be a reference type.
If a protocol existed in Swift that accepted any reference type and any value type that can be converted to a reference type in objective-c (like [Int] and the other examples I mention) this problem would be solved:
func set <T: AnyObjectiveCObject>(key: UDKey<T>, _ value: T) {
storage.setObject(value, forKey: key.name)
}
func get <T: AnyObjectiveCObject>(key: UDKey<T>) -> T? {
return storage.objectForKey(key.name) as? T
}
AnyObjectiveCObject would accept any swift classes and swift arrays, dictionaries, numbers (ints, floats, bools, etc that convert to NSNumber), strings...
Unfortunately, AFAIK this doesn't exist.
Question:
How can I have write a generic function (or collection of overloaded generic functions) whose generic type T can be any reference type or any value type that Swift can convert to a reference type in Objective-C?
Solved: With the help of the answers I got, I arrived at what I wanted. In case anyone wants to take a look at my solution, here it is.
I don't mean to brag but ... oh who am I kidding, I totally do!
Preferences.set([NSData()], forKey: "MyKey1")
Preferences.get("MyKey1", type: type([NSData]))
Preferences.get("MyKey1") as [NSData]?
func crunch1(value: [NSData])
{
println("Om nom 1!")
}
crunch1(Preferences.get("MyKey1")!)
Preferences.set(NSArray(object: NSData()), forKey: "MyKey2")
Preferences.get("MyKey2", type: type(NSArray))
Preferences.get("MyKey2") as NSArray?
func crunch2(value: NSArray)
{
println("Om nom 2!")
}
crunch2(Preferences.get("MyKey2")!)
Preferences.set([[String:[Int]]](), forKey: "MyKey3")
Preferences.get("MyKey3", type: type([[String:[Int]]]))
Preferences.get("MyKey3") as [[String:[Int]]]?
func crunch3(value: [[String:[Int]]])
{
println("Om nom 3!")
}
crunch3(Preferences.get("MyKey3")!)
I'd like to introduce my idea. (Sorry for my poor English in advance.)
let plainKey = UDKey("Message", string)
let mixedKey
= UDKey("Mixed"
, array(dictionary(
string, tuple(
array(integer),
optional(date)))))
let ud = UserDefaults(NSUserDefaults.standardUserDefaults())
ud.set(plainKey, "Hello")
ud.set(plainKey, 2525) // <-- compile error
ud.set(mixedKey, [ [ "(^_^;)": ([1, 2, 3], .Some(NSDate()))] ])
ud.set(mixedKey, [ [ "(^_^;)": ([1, 2, 3], .Some(NSData()))] ]) // <-- compile error
The only difference is that UDKey() now requires #2 argument, a value of BiMap class. I've uncoupled the work originally of UDKey into BiMap which converts a value of a type to/from a value of another type.
public class BiMap<A, B> {
public func AtoB(a: A) -> B?
public func BtoA(b: B) -> A?
}
Consequently, types that set/get can accepts are conducted by BiMap, and no longer limited to types as can automatically cast
from/to AnyObject (more specifically, types NSUserDefaults can accepts.).
Because BiMap is a generic class, you can easily create subtypes of that, interchanging arbitrary two types you want.
Here is full source code. (But there are bugs yet to be fixed..)
https://gist.github.com/hisui/47f170a9e193168dc946