Converting Dictionary Key type in Swift 5 with Dictionary(uniqueKeysWithValues:) - ios

I am writing an application with networking capabilities for iOS 13.4 (Swift 5, Xcode 11) using Alamofire 5. I have created my custom type typealias KeyedParameters = [ParameterKeys: Any] to be able to use my API Parameter Keys in a 'swifty' short way (i.e. .login instead of KeyedParameters.login.rawValue).
The problem is when I try do convert this type back to default Alamofire's Parameters, I receive following error: Cannot convert return expression of type 'Dictionary<ParameterKeys, Any>' to return type 'Parameters' (aka 'Dictionary<String, Any>').
Casting:
extension KeyedParameters {
var parameters: Parameters {
Dictionary(uniqueKeysWithValues: map { ($0.key.rawValue, $0.value) })
}
}
ParameterKeys:
enum ParameterKeys: String {
// MARK: - Auth and User
case id, login, password, email, name
case createdAt = "created_at"
...
}
How error looks:

I think this might be just a case of a bad error message.
Your extension KeyedParameters (a typealias for [ParameterKeys: Any]) is actually equivalent to:
extension Dictionary where Key == ParameterKeys, Value: Any { ...
Swift has some odd behaviour when calling an initializer for a generic type within the declaration of that type itself. If the generic types are different, it won't handle that properly.
Here's a simpler example without so many red herrings (type aliases, enum raw values, etc.) and dependencies:
extension Dictionary {
func returnADifferentDict() -> [Character: String] {
let words = [
"apple", "anchovies",
"bacon", "beer",
"celery"
]
return Dictionary(uniqueKeysWithValues:
words.map { ($0.first!, $0) }
)
// fixed:
// return Dictionary<Character, String>(uniqueKeysWithValues:
// words.map { ($0.first!, $0) }
// )
}
}
The solution is to explicitly specify the generic type parameters of the generic type you're initializing. In your case,
extension KeyedParameters {
var parameters: Parameters {
Dictionary<String, Any>(uniqueKeysWithValues: map { ($0.key.rawValue, $0.value) })
}
}

You'd better explicitly highlight type like this:
extension KeyedParameters {
var parameters: Parameters {
return Parameters(uniqueKeysWithValues:
self.map { (key, value) in (key.rawValue, value) }
)
}
}
Worked for me.

Related

Convert string JSON response to a boolean using Swift 4 Decodable

I'm refactoring some projects where I'd previously used third-party JSON parsers and I've encountered a goofy site that returns a boolean as a string.
This is the relevant snippet from the JSON response:
{
"delay": "false",
/* a bunch of other keys*/
}
My struct for Decoding looks like this:
struct MyJSONStruct: Decodable {
let delay: Bool
// the rest of the keys
}
How would I convert the string returned in the JSON response into a Bool to match my struct in Swift 4? While this post was helpful, I can't figure out how to turn a string response into a boolean value.
Basically you have to write a custom initializer but if there are many good keys but only one to map from a type to another a computed property might be useful
struct MyJSONStruct: Decodable {
var delay: String
// the rest of the keys
var boolDelay : Bool {
get { return delay == "true" }
set { delay = newValue ? "true" : "false" }
}
}
you need to set the boolEncoding: .literal in the URLEncoding initializer.
boolEncoding: .literal

Swift - initialize class by generic type with protocol

i want to call a method like this:
createModel(Model.Type, json)
The function should call the constructor for the given type in the parameter. The constructor is defined in a protocol (from ObjectMapper). I am not that familiar with the generic types in swift. This is what I have so far but it doesn't work.
func createModel<T: Mappable>(model: T.Type, json: JSON){
return model(JSON: json)
}
Here I have described it in the comments. There were some cases in your code were you were using JSON and json interchangeably. In the code I use JSON to identify the type alias and json as a variable name. Also the format in Swift is varName: type when in a parameter list
typealias JSON = [String: Any] // Assuming you have something like this
// Assuming you have this
protocol Mappable {
init(json: JSON) // format = init(nameOfVariable: typeOfVariable)
}
class Model: Mappable {
required init(json: JSON) {
print("a") // actually initialize it here
}
}
func createModel<T: Mappable>(model: T.Type, json: JSON) -> T {
// Initializing from a meta-type (in this case T.Type that we int know) must explicitly use init.
return model.init(json: json) // the return type must be T also
}
// use .self to get the Model.Type
createModel(model: Model.self, json: ["text": 2])

Swift Type Erasure with Generic Enum and Generic Protocol

I have had to use type erasure in Swift a few times however it always involved a generic protocol. In this case, it involves both a generic enum and and generic protocol and I'm stumped.
Here is my generic enum and generic protocol with the necessary extension:
enum UIState<T> {
case Loading
case Success([T])
case Failure(ErrorType)
}
protocol ModelsDelegate: class {
associatedtype Model
var state: UIState<[Model]> { get set }
}
extension ModelsDelegate {
func getNewState(state: UIState<[Model]>) -> UIState<[Model]> {
return state
}
func setNewState(models: UIState<[Model]>) {
state = models
}
}
And here is my type erased generic class:
class AnyModelsDelegate<T>: ModelsDelegate {
var state: UIState<[T]> {
get { return _getNewState(UIState<[T]>) } // Error #1
set { _setNewState(newValue) }
}
private let _getNewState: ((UIState<[T]>) -> UIState<[T]>)
private let _setNewState: (UIState<[T]> -> Void)
required init<U: ModelsDelegate where U.Model == T>(_ models: U) {
_getNewState = models.getNewState
_setNewState = models.setNewState
}
}
I'm getting the following errors (they are marked in the code sample):
Error #1:
Cannot convert value of type '(UIState<[T]>).Type' (aka 'UIState<Array<T>>.Type') to expected argument type 'UIState<[_]>' (aka 'UIState<Array<_>>')
I have been working on this for awhile and there have been quite a few variations on this code that "almost worked". The error always has to do with the getter.
The problem that causes this error, as #dan has pointed out, is that on this line you're trying to pass a type as an argument, instead of an instance of that type:
get { return _getNewState(UIState<[T]>) }
However, I would question your use of an argument to this function in the first place, surely a getting function should have no argument at all? In this case you'll simply want your _getNewState function to have the signature () -> UIState<[T]>, and call it like so:
get { return _getNewState() }
Also, if your getNewState and setNewState(_:) functions in your protocol extension only exist in order to forward the getting and setting of your state property to the type-erasure – you can simplify your code by getting rid of them entirely and use closure expressions in the type-erasure's init instead:
_getNewState = { models.state }
_setNewState = { models.state = $0 }
(These work by capturing a reference to the models argument, for more info see Closures: Capturing Values)
Finally, I suspect that you mean to refer to UIState<T> rather than UIState<[T]> throughout your code, as T in this case refers to an element in the array that your .Success case has as an associated value (unless you want a 2D array here).
All in all, with the above proposed changes, you'll want your code to look something like this:
enum UIState<T> {
case Loading
case Success([T])
case Failure(ErrorType)
}
protocol ModelsDelegate: class {
associatedtype Model
var state: UIState<Model> { get set }
}
class AnyModelsDelegate<T>: ModelsDelegate {
var state: UIState<T> {
get { return _getNewState() }
set { _setNewState(newValue) }
}
private let _getNewState: () -> UIState<T>
private let _setNewState: (UIState<T>) -> Void
required init<U: ModelsDelegate where U.Model == T>(_ models: U) {
_getNewState = { models.state }
_setNewState = { models.state = $0 }
}
}

Calling async function in lazy var property

Is there a way to call an async function from a lazy or computed property?
struct Item {
lazy var name: String = {
API.requestThing({ (string: String) in // Xcode didn't like this
return string // this would not work here
})
}()
}
class API {
class func requestThing(completion: String -> Void) {
completion("string")
}
}
Your completion handler in API.requestThing returns a String, yet it is supposed to have no return value:
(completion: String -> Void)
I got this to work:
struct Item {
lazy var name: String = {
API.requestThing({ (string: String) in
return string
})
}()
}
class API {
class func requestThing(completion: String -> String) -> String {
return completion("string")
}
}
There is no good reason to use "lazy" in this case. lazy is for initialization. Just create a normal func and pass a completion handler.
First, requestThing returns () (ie void) and not String. So the type of the following expression is also () and not String:
API.requestThing { string in
return string
}
Second, the call to requestThing is asynchronous, so even if you defined name as a lazy var, the call to the var body function is still synchronousand will return immediately.
So if you can transform name into a function like this:
func name(completion: String -> ()) {
API.requestThing { string in
completion(string)
}
}
// Later you call it in this way
myItem.name { name in
// use the value of name
}
If in addition you want to cache the retrieved value you can modify Item to a class and use the following code
class Item {
private var nameValue: String?
func name(completion: String -> ()) {
if let value = nameValue {
// return cached value
return completion(value)
} else {
// request value
API.requestThing { string in
// cache retrieved value
self.nameValue = string
// return retrieved value
completion(string)
}
}
}
}
There's probably no compelling reason to do this, but the following approach seems to be reasonable:
Instead having a variable of type String - we sometimes require a "Future" of that thing, e.g. Future<String>. A future represents the eventual result of an asynchronous operation - that is exactly what's given in your question.
The future itself is a "normal" variable and can be lazy evaluated, too. It just doesn't yet have its eventual value. That means, the underlying task will only be started when explicitly requested (e.g. lazily). From a design or architectural point of view, this may make sense.
func fetchString() -> Future<String> { ... }
lazy var name: Future<String> = fetchString()
Later in your code, you obtain the variable as follows:
item.name.map { string in
print(string)
}
If this is the first access to the lazy property, it will start the underlying asynchronous operation which calculates the string. Then, when the variable is available, the mapping function as provided in the map function will be called with the variable as an argument - possibly some time later, too.
Otherwise (if this is not the first access), it will just provide the string in the parameter when it is available, possibly immediately.
Since operations may fail, a "Future" also provides means to handle this:
item.name.map { string in
print(string)
}.onFailure { error in
print("Error: \(error)")
}
See also: https://en.wikipedia.org/wiki/Futures_and_promises
There are implementations for Futures in Swift and Objective-C, also often called "Promise".

Create a Swift Object from a Dictionary

How do you instantiate a type dynamically based upon a lookup value in a dictionary in Swift?
Hopefully this is useful to others. It took some research to figure this out. The goal is to avoid the anti-pattern of giant if or switch statements to create each object type from a value.
class NamedItem : CustomStringConvertible {
let name : String
required init() {
self.name = "Base"
}
init(name : String) {
self.name = name
}
var description : String { // implement Printable
return name
}
}
class File : NamedItem {
required init() {
super.init(name: "File")
}
}
class Folder : NamedItem {
required init() {
super.init(name: "Folder")
}
}
// using self to instantiate.
let y = Folder.self
"\(y.init())"
let z = File.self
"\(z.init())"
// now put it in a dictionary.
enum NamedItemType {
case folder
case file
}
var typeMap : [NamedItemType : NamedItem.Type] = [.folder : Folder.self,
.file : File.self]
let p = typeMap[.folder]
"\(p!.init())"
let q = typeMap[.file]
"\(q!.init())"
Interesting aspects:
use of "required" for initializers
use of .Type to get the type for the dictionary value.
use of .self to get the "class" that can be instantiated
use of () to instantiate the dynamic object.
use of Printable protocol to get implicit string values.
how to init using a non parameterized init and get the values from subclass initialization.
Updated to Swift 3.0 syntax

Resources