Most specific generic function not called - swift

I'm using #propertyWrapper to reduce my UserDefaults boilerplate as follows…
enum PreferenceKey: String, CaseIterable {
case enumName, stringName
}
#propertyWrapper
struct Prefs<T> {
let key: PreferenceKey
var wrappedValue: T? {
get {
UserDefaults.object(for: key)
}
set {
UserDefaults.set(newValue, for: key)
}
}
}
struct Preferences {
#Prefs(key: .enumName) static var enumName: Name?
#Prefs(key: .stringName) static var stringName: String?
}
extension UserDefaults {
static func object<T>(for key: PreferenceKey) -> T? {
standard.object(forKey: key.rawValue) as? T
}
static func object<T: RawRepresentable>(for key: PreferenceKey) -> T? where T.RawValue == String {
if let value = standard.object(forKey: key.rawValue) as? String {
return T(rawValue: value)
}
return nil
}
static func set<T: RawRepresentable>(_ value: T, for key: PreferenceKey) {
print("Set Raw Value \(value)")
standard.set(value.rawValue, forKey: key.rawValue)
}
static func set<T>(_ value: T, for key: PreferenceKey) {
print("Set Value \(value)")
standard.set(value, forKey: key.rawValue)
}
}
This works fine when setting a regular property list type…
Preferences.stringName = "Fred"
// Set Value Optional("Fred")
print(Preferences.stringName)
// Optional("Fred")
But when trying to set a value that is RawRepresentable, it fails…
Preferences.enumName = .Fred
// Set Value Optional(__lldb_expr_10.Name.Fred)
// libc++abi.dylib: terminating with uncaught exception of type NSException
Rather than calling the most specific version of UserDefaults.set(, it calls the non-specific one.
Just calling
UserDefaults.set(Name.Fred, for: .enumName)
works fine. In this case it calls the most specific function.
With further testing, and it seems that this isn't a #propertyWrapper issue. The following top level function also fails to call the more specific generic function. It seems like some type information is being lost somewhere
func set<T>(_ value: T?) {
UserDefaults.set(value, for: .enumName)
}
set(Name.Fred)
// Set Value Optional(__lldb_expr_5.Name.Fred)
// libc++abi.dylib: terminating with uncaught exception of type NSException
What am I missing? Any thoughts as to how I can resolve this?

What am I missing?
Swift is essentially a statically typed language and selecting which of your function overloads to call is determined at compile time.
In your working example:
UserDefaults.set(Name.Fred, for: .enumName)
the type of the first argument is known by the compiler. This type implements RawRepresentable and the compiler uses that to select the overload you expect.
Now consider your failing example:
func set<T>(_ value: T?) {
UserDefaults.set(value, for: .enumName)
}
set(Name.Fred)
When the compiler compiles the set function the only thing it knows about the argument value is that is has a type which it can reference as T. There are no constraints on T, at runtime a value of any type can be passed, so in determining which overload of UserDefaults.set to compile a call to the compiler can only select the overload which also has no constraints and accepts a value of any type.
Any thoughts as to how I can resolve this?
You already know one solution, you overloaded UserDefaults.set, you could overload your set function. However you might wish to consider your design here in the light of Swift's compile-time resolution of overloads – you may not want layers of overloaded functions calling each other.
HTH

Related

Is it possible to type-check against a list of types?

I know that it's possible to check if one value is an instance of a potential supertype:
func isInstance<T, U>(_ instance: T, of: U.Type) -> Bool {
return instance is U
}
However, what if you want to check against an entire array? Since you can't have an array of generics, the above approach doesn't really work. What I want to do is something like:
func isInstance<T>(_ instance: T, of types: [Any.Type]) -> Bool {
return types.allSatisfy { instance is $0 }
}
However, a variable (such as $0) isn't allowed as the RHS of an is expression. Is this kind of type check possible?
I saw one type check that used Mirror(reflecting:) and superclassMirror to search the inheritance hierarchy, but that only works for an array of classes, and I need this check to work when the array contains protocols as well.
It is possible, but only for classes and #objc protocols, and only if the object conforms to NSObjectProtocol. It is not possible in the general case for Swift protocols or value types.
import ObjectiveC
func isInstance(_ instance: some NSObjectProtocol, of types: [Any.Type]) -> Bool {
return types.allSatisfy { type in
if let `class` = type as? AnyClass {
return instance.isKind(of: `class`)
} else if let `protocol` = type as AnyObject as? Protocol {
return instance.conforms(to: `protocol`)
} else {
print("Cannot type-check instance against \(type)")
return false
}
}
}

How can I translate this utility function into an extension function?

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.

Error: Trying to put the stack in unreadable memory at:

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.

Swift where condition to check if a property is implemented

I just found another way to make a great use of protocols and protocol extensions in Swift by extending the Optional protocol to add a function so I can provide default values.
I wrote a blog post about this here: https://janthielemann.de/random-stuff/providing-default-values-optional-string-empty-optional-string-swift-3-1/
The gist of the post is that I needed a clean and easy way to provide default values for optional String which are nil or empty. To do this, I created a Emptyable protocol end extended the Optional protocol like so:
protocol Emptyable {
var isEmpty: Bool { get }
}
extension Optional where Wrapped: Emptyable {
func orWhenNilOrEmpty<T: Emptyable>(_ defaultValue: T) -> T {
switch(self) {
case .none:
return defaultValue
case .some(let value) where value.isEmpty:
return defaultValue
case .some(let value):
return value as! T
}
}
}
extension String: Emptyable {}
Now the question is: Is there a way I can get rid of the Emptyable protocol and instead have a conditional check whether or not a property or function is implemented by the generic type so that I automatically get orWhenNilOrEmpty() for each and every type which has isEmpty?
UPDATE
As suggested by Paulo, the T generic is actually not needed and I created a operator for even quicker access and more convenient usage (at least I think so. Feel free to correct me, I'm always happy to learn new things and improve myself).
I call it the "not empty nil coalescing" operator (who can come up with a better names? I feel like I suck at naming things :/ ). Hopefully some day it helps somebody:
protocol Emptyable {
var isEmpty: Bool { get }
}
infix operator ???: NilCoalescingPrecedence
extension Optional where Wrapped: Emptyable {
func orWhenNilOrEmpty(_ defaultValue: Wrapped) -> Wrapped {
switch(self) {
case .none:
return defaultValue
case .some(let value) where value.isEmpty:
return defaultValue
case .some(let value):
return value
}
}
static func ???(left: Wrapped?, right: Wrapped) -> Wrapped {
return left.orWhenNilOrEmpty(right)
}
}
extension String: Emptyable {}
extension Array: Emptyable {}
extension MyStruct: Emptyable {
let text: String
let number: Int
var isEmpty: Bool { return text.isEmpty && number == 0 }
init(text: String, number: Int) {
self.text = text
self.number = number
}
}
let mandatoryNotEmptyString = optionalOrEmptyString ??? "Default Value"
let mandatoryNotEmptyStruct = optionalOrEmptyStruct ??? MyStruct(name: "Hello World", number: 1)
No, you cannot query if an object or value has a certain property as a constraint on an extension without using a protocol. That would require reflection in a way that is currently not implemented in Swift. Also, an isEmpty property could have different meanings for different types, so testing for the existence of a method or property instead of a protocol could lead to unexpected behaviour.
You could just write
if let unwrappedString = optionalString, !unwrappedString.isEmpty {
// Do stuff
} else {
// Use default value
}
No protocol or extension required and very readable.
In Swift 4, which is coming out this fall, String will conform to BidirectionalCollection, which inherits from Collection. The Collection protocol provides an isEmpty property, so your extension could be
extension Optional where Wrapped: Collection {
// ...
}
But even then you should consider to set empty strings to nil when storing them in the first place, because you now have two states (nil and empty) which seem to represent the exact same thing.

Generic method causes "cannot pass immutable value of type AnyObject? as inout argument"

I'm working on a Swift version of a Keychain wrapper class. I'm a bit puzzled why this works:
private func executeFetch(query: KeyStoreObject) throws -> AnyObject? {
var result: AnyObject?
try executeQuery(query) { SecItemCopyMatching(query.data, &result) }
return result
}
And this doesn't:
private func executeFetch<T: AnyObject>(query: KeyStoreObject) throws -> T? {
var result: T?
try executeQuery(query) { SecItemCopyMatching(query.data, &result) }
return result
}
I believe that the error is that SecItemCopyMatching may attempt to assign anything of type AnyObject (i.e. anything at all) to result. However, in the second example result is not necessarily of type AnyObject; it is some particular type T that is a subclass of AnyObject. Thus, SecItemCopyMatching may be unable to correctly set result. For instance, what if T is Int, but SecItemCopyMatching wants to set result to a String? When result is of type AnyObject this is no longer an issue.