specialized extension of Dictionary - swift

How can I declare an extension that will work only for a particular type?
I tried this:
extension Dictionary where
Key : CustomStringConvertible,
Value: CustomStringConvertible
{
func queryString() -> String {
var paramArray = Array<String>()
for (key, value) in self {
paramArray.append("\(key.description)=\(value.description)")
}
return "&".join(paramArray)
}
}
And it compiles fine. But when I try to use it
var d = Dictionary<String, String>()
var q = d.queryString() // <-- ERROR
I get the error:
Cannot invoke 'queryString' with no arguments
What is wrong here? I want to be able to call queryString on a Dictionary but only when it is Dictionary<String, String>
Any help is highly appreciated.
Edit
As #jtbandes said, String does not conform to CustomStringConvertible. CustomStringConvertible Protocol Reference suggests to use String() constructor to get a string rather than using the protocol as a constrain.
NOTE: String(instance) will work for an instance of any type, returning its description if the instance happens to be CustomStringConvertible. Using CustomStringConvertible as a generic constraint, or accessing a conforming type's description directly, is therefore discouraged.
extension Dictionary {
public func queryString() -> String {
var paramArray = Array<String>()
for (key, value) in self {
paramArray.append("\(String(key))=\(String(value))")
}
return "&".join(paramArray)
}
}
Edit2
This is my final version.
extension Dictionary {
public func queryString() -> String {
var queryItems = Array<NSURLQueryItem>()
for (key, value) in self {
queryItems.append(NSURLQueryItem(name: String(key), value: String(value)))
}
let comps = NSURLComponents();
comps.queryItems = queryItems
return comps.percentEncodedQuery!
}
}

String is not CustomStringConvertible. You can use:
extension String: CustomStringConvertible {
public var description: String { return self }
}
Or, I would recommend making your own protocol for this case:
protocol QueryStringConvertible {
var queryStringRepresentation: String { get }
}
extension String: QueryStringConvertible {
var queryStringRepresentation: String { return self /*probably should escape the string here*/ }
}
extension Dictionary where
Key : QueryStringConvertible,
Value : QueryStringConvertible ...
But in reality, I think you might want to take a look at NSURLQueryItem and NSURLComponents :)

Related

swift5 - enum protocol - Self and self and a variable

I like to separate soms function and var from a enum and thought this was a way. (Just sample code)
This results in a compile error "Type 'Self' has no member 'allCases'"
public protocol EnumFunctions: Hashable {
static var numOfCases: Self { get }
}
public extension EnumFunctions {
static var numOfCases: Self {
return self.allCases.count
}
}
my Enum Cooking Timer
public struct Cook_Time {
// struct naming for the dump command like
// dump(Cook_Time(), name: Cook_Time().structname)
let structname : String = "Cook_Time"
let a = "a"
let allValues = PFTime.allValues
public enum PFTime : String , CaseIterable, EnumFunctions {
case t1m = "1mim"
case t3m = "3min"
case t5m = "5min"
case t10m = "10min"
....
static let allValues = PFTimer.allCases.map { $0.rawValue }
}
}
How can I fix this? what is my false mind setting about this? I do need Self instead of self, rigth?
Also if I make an extension for PFTime, in a separated file, why does I get the error "Cannot find type 'PFTime' in scope"?
In order to access allCases property of CaseIterable protocol, you need to change your EnumFunctions protocol to something like this:
public protocol EnumFunctions: Hashable, CaseIterable {
static var numOfCases: Int { get }
}
public extension EnumFunctions {
static var numOfCases: Int {
return allCases.count
}
}
Also you can create an extension to PFTime just by adding Cook_Time. as prefix because PFTime is placed inside Cook_Time struct:
extension Cook_Time.PFTime {
}

Swift - KeyPath extension - enforce conformance to protocol

ideally, I'd like to get the name of the property referenced by a KeyPath. But this seems not to be possible out-of-the-box in Swift.
So my thinking is that the KeyPath could provide this information based on protocol extension added by a developer. Then I'd like to design an API with an initializer/function that accepts a KeyPath conforming to that protocol (that adds a computed property).
So far I was only able to define the protocol and conditional conformance of the protocol. The following code compiles fine.
protocol KeyPathPropertyNameProviding {
var propertyName: String {get}
}
struct User {
var name: String
var age: Int
}
struct Person {
var name: String
var age: Int
}
extension KeyPath: KeyPathPropertyNameProviding where Root == Person {
var propertyName: String {
switch self {
case \Person.name: return "name"
case \Person.age: return "age"
default: return ""
}
}
}
struct PropertyWrapper<Model> {
var propertyName: String = ""
init<T>(property: KeyPath<Model, T>) {
if let property = property as? KeyPathPropertyNameProviding {
self.propertyName = property.propertyName
}
}
}
let userAge = \User.age as? KeyPathPropertyNameProviding
print(userAge?.propertyName) // "nil"
let personAge = \Person.age as? KeyPathPropertyNameProviding
print(personAge?.propertyName) // "age"
let wrapper = PropertyWrapper<Person>(property: \.age)
print(wrapper.propertyName) // "age"
But I am unable to restrict the API so that initialization parameter property has to be a KeyPath AND must conform to a certain protocol.
For example, the following would result in a compilation error but should work from my understanding (but probably I miss a key detail ;) )
struct PropertyWrapper<Model> {
var propertyName: String = ""
init<T>(property: KeyPath<Model, T> & KeyPathPropertyNameProviding) {
self.propertyName = property.propertyName // compilation error "Property 'propertyName' requires the types 'Model' and 'Person' be equivalent"
}
}
Any tips are highly appreciated!
You are misunderstanding conditional conformance. You seem to want to do this in the future:
extension KeyPath: KeyPathPropertyNameProviding where Root == Person {
var propertyName: String {
switch self {
case \Person.name: return "name"
case \Person.age: return "age"
default: return ""
}
}
}
extension KeyPath: KeyPathPropertyNameProviding where Root == User {
var propertyName: String {
...
}
}
extension KeyPath: KeyPathPropertyNameProviding where Root == AnotherType {
var propertyName: String {
...
}
}
But you can't. You are trying to specify multiple conditions to conform to the same protocol. See here for more info on why this is not in Swift.
Somehow, one part of the compiler thinks that the conformance to KeyPathPropertyNameProviding is not conditional, so KeyPath<Model, T> & KeyPathPropertyNameProviding is actually the same as KeyPath<Model, T>, because somehow KeyPath<Model, T> already "conforms" to KeyPathPropertyNameProviding as far as the compiler is concerned, it's just that the property propertyName will only be available sometimes.
If I rewrite the initialiser this way...
init<T, KeyPathType: KeyPath<Model, T> & KeyPathPropertyNameProviding>(property: KeyPathType) {
self.propertyName = property.propertyName
}
This somehow makes the error disappear and produces a warning:
Redundant conformance constraint 'KeyPathType': 'KeyPathPropertyNameProviding'
Key paths are hashable, so I recommend a dictionary instead. It's especially easy to put it together with strong typing if you're able to use a CodingKey type.
struct Person: Codable {
var name: String
var age: Int
enum CodingKey: Swift.CodingKey {
case name
case age
}
}
extension PartialKeyPath where Root == Person {
var label: String {
[ \Root.name: Root.CodingKey.name,
\Root.age: .age
].mapValues(\.stringValue)[self]!
}
}
Then use parentheses instead of the cast you demonstrated. No need for a protocol so far…
(\Person.name).label // "name"
(\Person.age).label // "age"
This will probably all be cleaner due to built-in support someday. https://forums.swift.org/t/keypaths-and-codable/13945

Accessing Typed Value of Generic Argument in Swift

I am trying to make a dispatch function which can take Payload as an argument. The Payload can be Int, String or any other type. I tried the following approaches but inside the dispatch function the payload.value is always T and not Int or String. Casting is an option but I thought that was the whole point of generics.
struct Payload<T> {
let value: T
}
func dispatch<T>(payload: Payload<T>) {
print(payload.value) // get the value as Int, String or some other type
}
let payload = Payload<Int>(value: 100)
let payload2 = Payload<String>(value: "FOO")
dispatch(payload: payload)
dispatch(payload: payload2)
As you already know T is unconstrained so it can be any type. Your only option is to cast the value to the types you are expecting. You can simply switch the value:
switch payload.value {
case let value as Int:
print("Int:", value)
case let value as String:
print("String:", value)
default:
print("some other type", payload.value)
}
Depends on what you want to get inside your dispatch, you can create a protocol and requires T to be conformed to that protocol.
protocol Printable {
func printValue() -> String
}
struct Payload<T> {
let value: T
}
func dispatch<T: Printable>(payload: Payload<T>) {
print(payload.value.printValue()) // get the value as Int, String or some other type
}
extension String: Printable {
func printValue() -> String {
return self
}
}
extension Int: Printable {
func printValue() -> String {
return "\(self)"
}
}

Swift Enum How to conform to a protocol's variables' set & get?

my code:
public protocol ApiRequestBaseObjProtocol {
var param:[String:Any] { get set }
var path:String {get}
}
extension ApiRequestBaseObjProtocol {
var param: [String : Any] {
get {
var key = "\(self.path)"
return (objc_getAssociatedObject(self, &key) as? [String : Any]) ?? [:]
}
set(newValue) {
var key = "\(self.path)"
objc_setAssociatedObject(self, &key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
enum MeApi : ApiRequestBaseObjProtocol {
public var path: String {
return "test/api/xxx"
}
}
at param get{} method : objc_getAssociatedObject(self, &key) aways be nil. I want to know why? thank you!!
You are creating two separate keys so your &key points to two separate things in getter and setter..
Try creating your key as a static constant.
Per NSHipster:
It is often recommended that they key be a static char—or better yet, the pointer to one. Basically, an arbitrary value that is guaranteed to be constant, unique, and scoped for use within getters and setters:

Swift subscript setter that accepts a different type than the getter's return value

I have a custom collection that can receive values of any type and converts them to strings. For example:
collection["key"] = 10
let value = collection["key"] // value is a String
Is there a way to do this? I tried implementing two subscript methods but Swift doesn't support write-only subscripts.
subscript(key: String) -> String {
get { ... }
}
// Doesn't compile
subscript(key: String) -> AnyObject {
set { ... }
}
You can use two different subscript implementations and disable the getter for one of them:
subscript(key: String) -> String {
get { return "howdy" } // put real implementation here
}
subscript(key: String) -> AnyObject {
get { fatalError("Oooops") }
set { }
}
However, this still leaves open the question of how to distinguish between these two subscript calls in context. It would be better to give them different signatures through their external parameter names:
subscript(key: String) -> String {
get { return "howdy" } // put real implementation here
}
subscript(# any: String) -> AnyObject {
get { fatalError("Oooops") }
set { }
}
And here's how to use it:
let m = MyClass()
m[any:"thing"] = 1
println(m["thing"]) // "1", presumably
Define subscript to return AnyObject (or Any as needed) and at the point you use the getter cast the result to String. You may already need to deal with subscript returning an optional so the coercion is just all part of extracting your desired value.
if let value = collection["key"] as String { ... }
else {...}
You could also define your own type and make it conform to the IntegerLiteralConvertible and the StringLiteralConvertible protocols.
Technically you could also write an extension for String to make it conform to IntegerLiteralConvertible but that might get confusing, since it will be available in your entire project.
I was facing a similar problem here and I solved it using a generic type for my variable and returning the type I want on its getter. You can try doing something like this:
class StorageClass {
private var _value: String?
public var value: Any? {
set {
if let num = newValue as? Int {
self._value = String(format: "%d",num)
}
}
get {
return self._value
}
}
}
By doing this, it is possible to do something like:
var storage = StorageClass()
storage.value = 10 /* setting value as an Integer */
let aString = storage.value as! String /* receiving a String value back */