How to get the parameter name in the enum? - swift

My code is like this:
enum API {
case login(phone:String, password:String, deviceID:String)
}
extension API:TargetType {
var task: Task {
switch self {
case let .login(phone, password, deviceID):
///How to get the parameter name here?
///For example:"phone", "password", "deviceID"
///Can this be generated automatically?
let parameters =
["phone":phone,
"password:":password,
"deviceID":deviceID]
return .requestParameters(parameters, encoding: JSONEncoding.default);
}
}
}
How to get the parameter name in Switch case?
For example:"phone", "password", "deviceID"
Can this be generated automatically?
How to avoid writing "phone" and the other dictionary keys literally, and make the compiler generate them from the associated value labels.
Maybe after the completion is like this
func parameters(_ api:API) -> [String, Any] {
}
switch self {
case .login:
return .requestParameters(parameters(self), encoding: JSONEncoding.default);
}
It seems that it is impossible to complete temporarily.
Who is the hero?

You can assign all associated values of the enum case to a single variable and then access the separate values using their labels.
enum API {
case login(phone:String, password:String, deviceID:String)
}
extension API:TargetType {
var task: Task {
switch self {
case let .token(params)
let parameters =
["phone":params.phone,
"password:":params.password,
"deviceID":params.deviceID]
return .requestParameters(parameters, encoding: JSONEncoding.default);
}
}
}
Btw shouldn't that .token be .login? There's no .token case in your API enum defined.
If you want to generate the Dictionary keys to match the String representation of the associated value labels, that cannot be done automatically, but as a workaround, you can define another enum with a String raw value and use that for the Dictionary keys.
enum API {
case login(phone:String, password:String, deviceID:String)
enum ParameterNames: String {
case phone, password, deviceID
}
}
extension API:TargetType {
var task: Task {
switch self {
case let .token(params)
let parameters =
["\(API.ParameterNames.phone)" : params.phone,
"\(API.ParameterNames.phone)" : params.password,
"\(API.ParameterNames.deviceID)" : params.deviceID]
return .requestParameters(parameters, encoding: JSONEncoding.default);
}
}
}

There is code where you could get the label plus all the value(s) of an enum.
public extension Enum {
public var associated: (label: String, value: Any?, values: Dictionary<String,Any>?) {
get {
let mirror = Mirror(reflecting: self)
if mirror.displayStyle == .enum {
if let associated = mirror.children.first {
let values = Mirror(reflecting: associated.value).children
var dict = Dictionary<String,Any>()
for i in values {
dict[i.label ?? ""] = i.value
}
return (associated.label!, associated.value, dict)
}
print("WARNING: Enum option of \(self) does not have an associated value")
return ("\(self)", nil, nil)
}
print("WARNING: You can only extend an enum with the EnumExtension")
return ("\(self)", nil, nil)
}
}
}
You will then be able to get the .associated.label and .associated.value of your enum. In your case your .value will be a tupple. Then you would need to use the .associated.values. Unfortunately you won't get the field names for these values. Because it's a tupple you will get field names like .0, .1 and .2. As far as I know there is no way to get the actual field names.
So in your case your code will be something like this:
enum API {
case login(phone:String, password:String, deviceID:String)
}
extension API:TargetType {
var task: Task {
return .requestParameters(self.associated.values, encoding: JSONEncoding.default);
}
}
But then you still need some functionality for going from the self.associated.values where the keys are .0, .1 and .2 to the names you like. I think the only option is for you to do this mapping yourself. You could extend your enum with a function for that.
If you want to see some more enum helpers, then have a look at Stuff/Enum

Your switch should look like this:
switch self {
case .login(let phone, let password, let deviceID)
let parameters =
["phone":phone,
"password:":password,
"deviceID":deviceID]
return .requestParameters(parameters, encoding: JSONEncoding.default);
}
Swift automatically generates the declared variables for you

You can take a look about Reflection in Swift
And you can make it automatic generate parameter like this:
class ParameterAble {
func getParameters() -> [String: Any] {
var param = [String: Any]()
let childen = Mirror(reflecting: self).children
for item in childen {
guard let key = item.label else {
continue
}
param[key] = item.value
}
return param
}
}
class LoginData: ParameterAble {
var phone: String
var password: String
var deviceID: String
init(phone: String, password: String, deviceID: String) {
self.phone = phone
self.password = password
self.deviceID = deviceID
}
}
enum API {
case login(data: LoginData)
}
extension API {
var task: [String: Any] {
switch self {
case let .login(data):
return data.getParameters()
}
}
}
let loginData = LoginData(phone: "fooPhone", password: "fooPass", deviceID:
"fooId")
let login = API.login(data: loginData)
print(login.task)
This is output: ["phone": "fooPhone", "deviceID": "fooId", "password": "fooPass"]
You can try it in Playground

Related

Property Wrapped Value if accessed from other files give non optional type

struct HarkAppPreference {
#UserPreferenceField(key: .userSelectedTags)
static var userSelectedTags: [MLQTag]
#UserPreferenceField(key: .userLastActivePlalist)
static var userLastActivePlaylist: [PlayableEntity]
#UserPreferenceField(key: .userLastActivePlayableEntityMeta)
static var userLastActivePlayableEntityMeta: LastPlayableEntityMeta
#UserPreferenceField(key: .currentUSer)
static var user: User
}
enum UserPreferenceFieldKey : String {
case userSelectedTags = "MLQUserSelectedTags"
case userLastActivePlalist = "MLQUserLastActivePlalist"
case userLastActivePlayableEntityMeta = "MLQUserLastActivePlayableEntityMeta"
case currentUSer
}
struct User : Codable {
let name : String
let phone: String
}
#propertyWrapper
struct UserPreferenceField<T : Codable> {
let key: UserPreferenceFieldKey
var wrappedValue : T? {
get {
guard let decodedData = UserDefaults.standard.value(forKey: key.rawValue) as? Data else { return nil }
return try? JSONDecoder().decode(T.self, from: decodedData)
}
set {
do {
let encodedValue = try JSONEncoder().encode(newValue)
UserDefaults.standard.set(encodedValue, forKey: key.rawValue)
} catch {
print("Error in saving codable value - \(error.localizedDescription)")
}
}
}
init(key:UserPreferenceFieldKey) {
self.key = key
}
}
This is my PropertyWrapper which I created to use for saving data to UserDefault, this worked to my expectations to use the value with optional chaining if used from within the file where I have written the PropertyWrapper code.
But this given an error Initializer for conditional binding must have Optional type, not 'User' when used in another file like below
class ABC {
func sss(){
if let _ = HarkAppPreference.user { // here is gives error
}
}
}
But if the same is used in the same file it works fine with optional chaining.
As per the reply from Apple Developers Team on Dev forum, It's not happening in Xcode 11.3 and worked fine when added optional to declared property.

Decode string or int value in structure will return string("1") in Swift 4

I have a structure that some of the values will get Integer or string so I used this structure in my structure to parse JSON in the correct way but the problem is that when I want to print the value it will print string("1") instead of 1.
public struct player1_result_sheet : Decodable {
let ans_1 : QuantumValue?
let ans_2 : QuantumValue?
let ans_3 : QuantumValue?
let ans_4 : QuantumValue?
}
enum QuantumValue: Decodable {
case int(Int), string(String)
init(from decoder: Decoder) throws {
if let int = try? decoder.singleValueContainer().decode(Int.self) {
self = .int(int)
return
}
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
throw QuantumError.missingValue
}
enum QuantumError:Error {
case missingValue
}
}
Here is the printing after decode:
(self.res?.response?.detailData?[indexPath.row].player1_result_sheet?.ans_1!)!
If you want to print the value without the enum wrapper, just implement description:
extension QuantumValue: CustomStringConvertible {
public var description: String {
switch self {
case let .string(string):
return string
case let .int(number):
return "\(number)"
}
}
}
Your enum case is string(String) so it prints case(value)
string("1")
You can solve it by creating varible inside enum which returns you a value depends on case of QuantumValue
var value: Any {
switch self {
case .int(let value):
return value
case .string(let value):
return value
}
}
then you can use it like this:
...ans_1!.value)!
1
Also note that type of value is Any so if you want to work with it as String, you have to downcast it
if let string = ...ans_1.value)! as? String {
...
}
QuantumValue is declared as enum, both good cases have associated values.
So printing a value prints both, the case and the associated value.
You could add two properties intValue and stringValue inside QuantumValue
var intValue : Int? {
guard case .int(let num) = self else { return nil }
return num
}
var stringValue : String? {
guard case .string(let string) = self else { return nil }
return string
}
Then you can print
player1_result_sheet?.ans_1?.intValue
By the way the name player1_result_sheet is pretty php-ish.
Please conform to the naming convention.
Structs, classes and enums are UpperCamelCased → Player1ResultSheet
Variables and functions are lowerCamelCased → ans1
And please consolidate your optionals, 6 (six) question and exclamation marks in one line is pretty weird
(self.res?.response?.detailData?[indexPath.row].player1_result_sheet?.ans_1!)!

Getter Setter With Type of Any Swift

Is it possible to do a getter and setter for an attribute that has a type of 'Any'
Here is my thought:
private var _valueObject: Any?
public var valueObject: Any? {
set {
if newValue is String {
self._valueObject = newValue as? String
} else if newValue is BFSignature {
self._valueObject = newValue as? BFSignature
}
}
get {
if self._valueObject is String {
return self._valueObject as? String
} else if self._valueObject is BFSignature {
return self._valueObject as? BFSignature
} else {
return self._valueObject
}
}
}
When I try to use it through out my code though I get errors stating:
Cannot compare String to type Any
Is there a way to use something like this without casting the 'valueObject' to a string whenever I need it. A way to use it and it already knows its a 'String' or 'BFSignature' instead of 'Any'.
Here is an example of the error:
I would rather it just know that cellValue is a 'String.' Instead of casting it each time I use it.
You shouldn't use Any
In my opinion, you should make a common representation of the API call result instead of using Any. You know exactly what the API is going to return, don't you? It's either a String or something that you turn into your custom object BFSignature.
Therefore, you can make an enum to represent your API call result:
enum APIResult {
case signature(BFASignature)
case justString(String)
}
and use it like
private var _valueObject: APIResult?
if let stringValue = newValue as? String {
self._valueObject = .justString(stringValue)
}
if let signatureValue = newValue as? BFSignature {
self._valueObject = .signature(signatureValue)
}
If there are a fixed number of types that you need to use here, you can use an enum:
struct BFSignature {
var a: Int
}
enum Either {
case bfSig(BFSignature)
case string(String)
}
var a: Either
var b: Either
a = .bfSig(BFSignature(a: 7))
b = .string("Stack Overflow")
a = b
Usage:
switch (b) {
case Either.bfSig(let signature):
print(signature.a) // Output integeral value
case Either.string(let str):
print(str) //Output string value
}

Can I use Swift's map() on Protocols?

I have some model code where I have some Thoughts that i want to read and write to plists. I have the following code:
protocol Note {
var body: String { get }
var author: String { get }
var favorite: Bool { get set }
var creationDate: Date { get }
var id: UUID { get }
var plistRepresentation: [String: Any] { get }
init(plist: [String: Any])
}
struct Thought: Note {
let body: String
let author: String
var favorite: Bool
let creationDate: Date
let id: UUID
}
extension Thought {
var plistRepresentation: [String: Any] {
return [
"body": body as Any,
"author": author as Any,
"favorite": favorite as Any,
"creationDate": creationDate as Any,
"id": id.uuidString as Any
]
}
init(plist: [String: Any]) {
body = plist["body"] as! String
author = plist["author"] as! String
favorite = plist["favorite"] as! Bool
creationDate = plist["creationDate"] as! Date
id = UUID(uuidString: plist["id"] as! String)!
}
}
for my data model, then down in my data write controller I have this method:
func fetchNotes() -> [Note] {
guard let notePlists = NSArray(contentsOf: notesFileURL) as? [[String: Any]] else {
return []
}
return notePlists.map(Note.init(plist:))
}
For some reason the line return notePlists.map(Note.init(plist:)) gives the error 'map' produces '[T]', not the expected contextual result type '[Note]'
However, If I replace the line with return notePlists.map(Thought.init(plist:)) I have no issues. Clearly I can't map the initializer of a protocol? Why not and what's an alternate solution?
If you expect to have multiple types conforming to Note and would like to know which type of note it is stored in your dictionary you need to add an enumeration to your protocol with all your note types.
enum NoteType {
case thought
}
add it to your protocol.
protocol Note {
var noteType: NoteType { get }
// ...
}
and add it to your Note objects:
struct Thought: Note {
let noteType: NoteType = .thought
// ...
}
This way you can read this property from your dictionary and map it accordingly.

Converting a rawValue into a string in Swift

Apple has made changes from Swift 3 to 4. When I run the following code:
let metadata = [ PDFDocumentAttribute.titleAttribute,
PDFDocumentAttribute.authorAttribute,
PDFDocumentAttribute.subjectAttribute,
PDFDocumentAttribute.creatorAttribute,
PDFDocumentAttribute.producerAttribute,
PDFDocumentAttribute.creationDateAttribute,
PDFDocumentAttribute.modificationDateAttribute,
PDFDocumentAttribute.keywordsAttribute ]
if var attributes = pdfDoc.documentAttributes {
for (index, value) in metadata.enumerated() {
if attributes[value] != nil {
print("\(metadata[index])): \(String(describing: attributes[value]!))")
} else {
print("\(metadata[index]): nil")
}
}
I now get: PDFDocumentAttribute(_rawValue: Title) instead of "Title", which I got before as the value of metadata[index].
How do I get rid of the rawValue stuff?
The PDFDocumentAttribute type has a property called rawValue that contains the old string value. So you can say
print("\(metadata[index].rawValue): \(String(describing: attributes[value]!))")
As an aside, instead of force-unwrapping the attribute you can use an if let, as in
if let attr = attributes[value] {
print("\(metadata[index].rawValue): \(attr)")
} else {
print("\(metadata[index].rawValue): nil")
}
If you add this extension:
extension PDFDocumentAttribute: CustomStringConvertible {
public var description: String {
return self.rawValue
}
}
Now you can just do:
// Forcing the downcast has little risk here
// but you may want to use `as?` and test for the optional instead
let attributes = pdfDoc.documentAttributes as! [PDFDocumentAttribute:Any]
for meta in metadata {
print("\(meta): \(attributes[meta] ?? "nil")")
}
Note that you can also do:
for attribute in attributes {
print("\(attribute.key): \(attribute.value)")
}
Which will just print out the attributes that exist on the document.